blob: 41cd5c952b4598acefdc5d85f68283d380a5529c [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
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +010068#ifdef WIN3264
69# ifndef HPCON
70# define HPCON VOID*
71# endif
72# ifndef EXTENDED_STARTUPINFO_PRESENT
73# define EXTENDED_STARTUPINFO_PRESENT 0x00080000
74# endif
75# ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
76# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
77# endif
78typedef struct _DYN_STARTUPINFOEXW
79{
80 STARTUPINFOW StartupInfo;
81 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
82} DYN_STARTUPINFOEXW, *PDYN_STARTUPINFOEXW;
83#endif
84
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020085/* typedef term_T in structs.h */
86struct terminal_S {
87 term_T *tl_next;
88
89 VTerm *tl_vterm;
90 job_T *tl_job;
91 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010092#if defined(FEAT_GUI)
93 int tl_system; /* when non-zero used for :!cmd output */
94 int tl_toprow; /* row with first line of system terminal */
95#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020096
97 /* Set when setting the size of a vterm, reset after redrawing. */
98 int tl_vterm_size_changed;
99
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200100 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
101 int tl_channel_closed;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +0200102 int tl_channel_recently_closed; // still need to handle tl_finish
103
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100104 int tl_finish;
105#define TL_FINISH_UNSET NUL
106#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
107#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
108#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200109 char_u *tl_opencmd;
110 char_u *tl_eof_chars;
111
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100112 char_u *tl_arg0_cmd; // To format the status bar
113
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200114#ifdef WIN3264
115 void *tl_winpty_config;
116 void *tl_winpty;
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200117
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100118 HPCON tl_conpty;
119 DYN_STARTUPINFOEXW tl_siex; // Structure that always needs to be hold
120
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200121 FILE *tl_out_fd;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200122#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100123#if defined(FEAT_SESSION)
124 char_u *tl_command;
125#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100126 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200127
128 /* last known vterm size */
129 int tl_rows;
130 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200131
132 char_u *tl_title; /* NULL or allocated */
133 char_u *tl_status_text; /* NULL or allocated */
134
135 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200136 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200137 int tl_dirty_row_end; /* row below last one to update */
Bram Moolenaar56bc8e22018-05-10 18:05:56 +0200138 int tl_dirty_snapshot; /* text updated after making snapshot */
139#ifdef FEAT_TIMERS
140 int tl_timer_set;
141 proftime_T tl_timer_due;
142#endif
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200143 int tl_postponed_scroll; /* to be scrolled up */
144
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200145 garray_T tl_scrollback;
146 int tl_scrollback_scrolled;
147 cellattr_T tl_default_color;
148
Bram Moolenaard96ff162018-02-18 22:13:29 +0100149 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
150 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
151
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200152 VTermPos tl_cursor_pos;
153 int tl_cursor_visible;
154 int tl_cursor_blink;
155 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
156 char_u *tl_cursor_color; /* NULL or allocated */
157
158 int tl_using_altscreen;
159};
160
161#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
162#define TMODE_LOOP 2 /* CTRL-W N used */
163
164/*
165 * List of all active terminals.
166 */
167static term_T *first_term = NULL;
168
169/* Terminal active in terminal_loop(). */
170static term_T *in_terminal_loop = NULL;
171
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100172#ifdef WIN3264
173static BOOL has_winpty = FALSE;
174static BOOL has_conpty = FALSE;
175#endif
176
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200177#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
178#define KEY_BUF_LEN 200
179
180/*
181 * Functions with separate implementation for MS-Windows and Unix-like systems.
182 */
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200183static 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 +0200184static int create_pty_only(term_T *term, jobopt_T *opt);
185static void term_report_winsize(term_T *term, int rows, int cols);
186static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100187#ifdef FEAT_GUI
188static void update_system_term(term_T *term);
189#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200190
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100191/* The character that we know (or assume) that the terminal expects for the
192 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200193static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200194
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100195/* "Terminal" highlight group colors. */
196static int term_default_cterm_fg = -1;
197static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200198
Bram Moolenaard317b382018-02-08 22:33:31 +0100199/* Store the last set and the desired cursor properties, so that we only update
200 * them when needed. Doing it unnecessary may result in flicker. */
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200201static char_u *last_set_cursor_color = NULL;
202static char_u *desired_cursor_color = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +0100203static int last_set_cursor_shape = -1;
204static int desired_cursor_shape = -1;
205static int last_set_cursor_blink = -1;
206static int desired_cursor_blink = -1;
207
208
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200209/**************************************
210 * 1. Generic code for all systems.
211 */
212
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200213 static int
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200214cursor_color_equal(char_u *lhs_color, char_u *rhs_color)
215{
216 if (lhs_color != NULL && rhs_color != NULL)
217 return STRCMP(lhs_color, rhs_color) == 0;
218 return lhs_color == NULL && rhs_color == NULL;
219}
220
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200221 static void
222cursor_color_copy(char_u **to_color, char_u *from_color)
223{
224 // Avoid a free & alloc if the value is already right.
225 if (cursor_color_equal(*to_color, from_color))
226 return;
227 vim_free(*to_color);
228 *to_color = (from_color == NULL) ? NULL : vim_strsave(from_color);
229}
230
231 static char_u *
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200232cursor_color_get(char_u *color)
233{
234 return (color == NULL) ? (char_u *)"" : color;
235}
236
237
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200238/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200239 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200240 * current window.
241 * Sets "rows" and/or "cols" to zero when it should follow the window size.
242 * Return TRUE if the size is the minimum size: "24*80".
243 */
244 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200245parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200246{
247 int minsize = FALSE;
248
249 *rows = 0;
250 *cols = 0;
251
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200252 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200253 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200254 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200255
256 /* Syntax of value was already checked when it's set. */
257 if (p == NULL)
258 {
259 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200260 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200261 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200262 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200263 *cols = atoi((char *)p + 1);
264 }
265 return minsize;
266}
267
268/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200269 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200270 */
271 static void
272set_term_and_win_size(term_T *term)
273{
Bram Moolenaar13568252018-03-16 20:46:58 +0100274#ifdef FEAT_GUI
275 if (term->tl_system)
276 {
277 /* Use the whole screen for the system command. However, it will start
278 * at the command line and scroll up as needed, using tl_toprow. */
279 term->tl_rows = Rows;
280 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200281 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100282 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100283#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200284 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200285 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200286 if (term->tl_rows != 0)
287 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
288 if (term->tl_cols != 0)
289 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200290 }
291 if (term->tl_rows == 0)
292 term->tl_rows = curwin->w_height;
293 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200294 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200295 if (term->tl_cols == 0)
296 term->tl_cols = curwin->w_width;
297 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200298 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200299}
300
301/*
302 * Initialize job options for a terminal job.
303 * Caller may overrule some of them.
304 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100305 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200306init_job_options(jobopt_T *opt)
307{
308 clear_job_options(opt);
309
310 opt->jo_mode = MODE_RAW;
311 opt->jo_out_mode = MODE_RAW;
312 opt->jo_err_mode = MODE_RAW;
313 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
314}
315
316/*
317 * Set job options mandatory for a terminal job.
318 */
319 static void
320setup_job_options(jobopt_T *opt, int rows, int cols)
321{
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200322#ifndef WIN3264
323 /* Win32: Redirecting the job output won't work, thus always connect stdout
324 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200325 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200326#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200327 {
328 /* Connect stdout to the terminal. */
329 opt->jo_io[PART_OUT] = JIO_BUFFER;
330 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
331 opt->jo_modifiable[PART_OUT] = 0;
332 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
333 }
334
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200335#ifndef WIN3264
336 /* Win32: Redirecting the job output won't work, thus always connect stderr
337 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200338 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200339#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200340 {
341 /* Connect stderr to the terminal. */
342 opt->jo_io[PART_ERR] = JIO_BUFFER;
343 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
344 opt->jo_modifiable[PART_ERR] = 0;
345 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
346 }
347
348 opt->jo_pty = TRUE;
349 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
350 opt->jo_term_rows = rows;
351 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
352 opt->jo_term_cols = cols;
353}
354
355/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100356 * Close a terminal buffer (and its window). Used when creating the terminal
357 * fails.
358 */
359 static void
360term_close_buffer(buf_T *buf, buf_T *old_curbuf)
361{
362 free_terminal(buf);
363 if (old_curbuf != NULL)
364 {
365 --curbuf->b_nwindows;
366 curbuf = old_curbuf;
367 curwin->w_buffer = curbuf;
368 ++curbuf->b_nwindows;
369 }
370
371 /* Wiping out the buffer will also close the window and call
372 * free_terminal(). */
373 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
374}
375
376/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200377 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100378 * Use either "argvar" or "argv", the other must be NULL.
379 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
380 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200381 * Returns NULL when failed.
382 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100383 buf_T *
384term_start(
385 typval_T *argvar,
386 char **argv,
387 jobopt_T *opt,
388 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200389{
390 exarg_T split_ea;
391 win_T *old_curwin = curwin;
392 term_T *term;
393 buf_T *old_curbuf = NULL;
394 int res;
395 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100396 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200397 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200398
399 if (check_restricted() || check_secure())
400 return NULL;
401
402 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
403 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
404 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
405 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
406 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100407 emsg(_(e_invarg));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200408 return NULL;
409 }
410
411 term = (term_T *)alloc_clear(sizeof(term_T));
412 if (term == NULL)
413 return NULL;
414 term->tl_dirty_row_end = MAX_ROW;
415 term->tl_cursor_visible = TRUE;
416 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
417 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100418#ifdef FEAT_GUI
419 term->tl_system = (flags & TERM_START_SYSTEM);
420#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200421 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
422
423 vim_memset(&split_ea, 0, sizeof(split_ea));
424 if (opt->jo_curwin)
425 {
426 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100427 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200428 {
429 no_write_message();
430 vim_free(term);
431 return NULL;
432 }
433 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100434 ECMD_HIDE
435 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
436 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200437 {
438 vim_free(term);
439 return NULL;
440 }
441 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100442 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200443 {
444 buf_T *buf;
445
446 /* Create a new buffer without a window. Make it the current buffer for
447 * a moment to be able to do the initialisations. */
448 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
449 BLN_NEW | BLN_LISTED);
450 if (buf == NULL || ml_open(buf) == FAIL)
451 {
452 vim_free(term);
453 return NULL;
454 }
455 old_curbuf = curbuf;
456 --curbuf->b_nwindows;
457 curbuf = buf;
458 curwin->w_buffer = buf;
459 ++curbuf->b_nwindows;
460 }
461 else
462 {
463 /* Open a new window or tab. */
464 split_ea.cmdidx = CMD_new;
465 split_ea.cmd = (char_u *)"new";
466 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100467 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200468 {
469 split_ea.line2 = opt->jo_term_rows;
470 split_ea.addr_count = 1;
471 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100472 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200473 {
474 split_ea.line2 = opt->jo_term_cols;
475 split_ea.addr_count = 1;
476 }
477
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100478 if (vertical)
479 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200480 ex_splitview(&split_ea);
481 if (curwin == old_curwin)
482 {
483 /* split failed */
484 vim_free(term);
485 return NULL;
486 }
487 }
488 term->tl_buffer = curbuf;
489 curbuf->b_term = term;
490
491 if (!opt->jo_hidden)
492 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100493 /* Only one size was taken care of with :new, do the other one. With
494 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100495 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200496 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100497 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200498 win_setwidth(opt->jo_term_cols);
499 }
500
501 /* Link the new terminal in the list of active terminals. */
502 term->tl_next = first_term;
503 first_term = term;
504
505 if (opt->jo_term_name != NULL)
506 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100507 else if (argv != NULL)
508 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200509 else
510 {
511 int i;
512 size_t len;
513 char_u *cmd, *p;
514
515 if (argvar->v_type == VAR_STRING)
516 {
517 cmd = argvar->vval.v_string;
518 if (cmd == NULL)
519 cmd = (char_u *)"";
520 else if (STRCMP(cmd, "NONE") == 0)
521 cmd = (char_u *)"pty";
522 }
523 else if (argvar->v_type != VAR_LIST
524 || argvar->vval.v_list == NULL
525 || argvar->vval.v_list->lv_len < 1
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100526 || (cmd = tv_get_string_chk(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200527 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
528 cmd = (char_u*)"";
529
530 len = STRLEN(cmd) + 10;
531 p = alloc((int)len);
532
533 for (i = 0; p != NULL; ++i)
534 {
535 /* Prepend a ! to the command name to avoid the buffer name equals
536 * the executable, otherwise ":w!" would overwrite it. */
537 if (i == 0)
538 vim_snprintf((char *)p, len, "!%s", cmd);
539 else
540 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
541 if (buflist_findname(p) == NULL)
542 {
543 vim_free(curbuf->b_ffname);
544 curbuf->b_ffname = p;
545 break;
546 }
547 }
548 }
549 curbuf->b_fname = curbuf->b_ffname;
550
551 if (opt->jo_term_opencmd != NULL)
552 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
553
554 if (opt->jo_eof_chars != NULL)
555 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
556
557 set_string_option_direct((char_u *)"buftype", -1,
558 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar7da1fb52018-08-04 16:54:11 +0200559 // Avoid that 'buftype' is reset when this buffer is entered.
560 curbuf->b_p_initialized = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200561
562 /* Mark the buffer as not modifiable. It can only be made modifiable after
563 * the job finished. */
564 curbuf->b_p_ma = FALSE;
565
566 set_term_and_win_size(term);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200567#ifdef WIN3264
568 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
569#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200570 setup_job_options(opt, term->tl_rows, term->tl_cols);
571
Bram Moolenaar13568252018-03-16 20:46:58 +0100572 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100573 return curbuf;
574
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100575#if defined(FEAT_SESSION)
576 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100577 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100578 {
579 term->tl_command = vim_strsave((char_u *)"NONE");
580 }
581 else if (argvar->v_type == VAR_STRING)
582 {
583 char_u *cmd = argvar->vval.v_string;
584
585 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
586 term->tl_command = vim_strsave(cmd);
587 }
588 else if (argvar->v_type == VAR_LIST
589 && argvar->vval.v_list != NULL
590 && argvar->vval.v_list->lv_len > 0)
591 {
592 garray_T ga;
593 listitem_T *item;
594
595 ga_init2(&ga, 1, 100);
596 for (item = argvar->vval.v_list->lv_first;
597 item != NULL; item = item->li_next)
598 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100599 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100600 char_u *p;
601
602 if (s == NULL)
603 break;
604 p = vim_strsave_fnameescape(s, FALSE);
605 if (p == NULL)
606 break;
607 ga_concat(&ga, p);
608 vim_free(p);
609 ga_append(&ga, ' ');
610 }
611 if (item == NULL)
612 {
613 ga_append(&ga, NUL);
614 term->tl_command = ga.ga_data;
615 }
616 else
617 ga_clear(&ga);
618 }
619#endif
620
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100621 if (opt->jo_term_kill != NULL)
622 {
623 char_u *p = skiptowhite(opt->jo_term_kill);
624
625 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
626 }
627
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200628 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100629 if (argv == NULL
630 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200631 && argvar->vval.v_string != NULL
632 && STRCMP(argvar->vval.v_string, "NONE") == 0)
633 res = create_pty_only(term, opt);
634 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200635 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200636
637 newbuf = curbuf;
638 if (res == OK)
639 {
640 /* Get and remember the size we ended up with. Update the pty. */
641 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
642 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100643#ifdef FEAT_GUI
644 if (term->tl_system)
645 {
646 /* display first line below typed command */
647 term->tl_toprow = msg_row + 1;
648 term->tl_dirty_row_end = 0;
649 }
650#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200651
652 /* Make sure we don't get stuck on sending keys to the job, it leads to
653 * a deadlock if the job is waiting for Vim to read. */
654 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
655
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200656 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200657 {
658 --curbuf->b_nwindows;
659 curbuf = old_curbuf;
660 curwin->w_buffer = curbuf;
661 ++curbuf->b_nwindows;
662 }
663 }
664 else
665 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100666 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200667 return NULL;
668 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100669
Bram Moolenaar13568252018-03-16 20:46:58 +0100670 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200671 return newbuf;
672}
673
674/*
675 * ":terminal": open a terminal window and execute a job in it.
676 */
677 void
678ex_terminal(exarg_T *eap)
679{
680 typval_T argvar[2];
681 jobopt_T opt;
682 char_u *cmd;
683 char_u *tofree = NULL;
684
685 init_job_options(&opt);
686
687 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100688 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200689 {
690 char_u *p, *ep;
691
692 cmd += 2;
693 p = skiptowhite(cmd);
694 ep = vim_strchr(cmd, '=');
695 if (ep != NULL && ep < p)
696 p = ep;
697
698 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
699 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100700 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
701 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200702 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
703 opt.jo_term_finish = 'o';
704 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
705 opt.jo_curwin = 1;
706 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
707 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100708 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
709 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100710 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
711 && ep != NULL)
712 {
713 opt.jo_set2 |= JO2_TERM_KILL;
714 opt.jo_term_kill = ep + 1;
715 p = skiptowhite(cmd);
716 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200717 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
718 && ep != NULL && isdigit(ep[1]))
719 {
720 opt.jo_set2 |= JO2_TERM_ROWS;
721 opt.jo_term_rows = atoi((char *)ep + 1);
722 p = skiptowhite(cmd);
723 }
724 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
725 && ep != NULL && isdigit(ep[1]))
726 {
727 opt.jo_set2 |= JO2_TERM_COLS;
728 opt.jo_term_cols = atoi((char *)ep + 1);
729 p = skiptowhite(cmd);
730 }
731 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
732 && ep != NULL)
733 {
734 char_u *buf = NULL;
735 char_u *keys;
736
737 p = skiptowhite(cmd);
738 *p = NUL;
739 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
740 opt.jo_set2 |= JO2_EOF_CHARS;
741 opt.jo_eof_chars = vim_strsave(keys);
742 vim_free(buf);
743 *p = ' ';
744 }
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100745 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "winpty", 6) == 0)
746 {
747 opt.jo_set2 |= JO2_TERM_MODE;
748 opt.jo_term_mode = 'w';
749 }
750 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "conpty", 6) == 0)
751 {
752 opt.jo_set2 |= JO2_TERM_MODE;
753 opt.jo_term_mode = 'c';
754 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200755 else
756 {
757 if (*p)
758 *p = NUL;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100759 semsg(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100760 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200761 }
762 cmd = skipwhite(p);
763 }
764 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100765 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200766 /* Make a copy of 'shell', an autocommand may change the option. */
767 tofree = cmd = vim_strsave(p_sh);
768
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100769 /* default to close when the shell exits */
770 if (opt.jo_term_finish == NUL)
771 opt.jo_term_finish = 'c';
772 }
773
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200774 if (eap->addr_count > 0)
775 {
776 /* Write lines from current buffer to the job. */
777 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
778 opt.jo_io[PART_IN] = JIO_BUFFER;
779 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
780 opt.jo_in_top = eap->line1;
781 opt.jo_in_bot = eap->line2;
782 }
783
784 argvar[0].v_type = VAR_STRING;
785 argvar[0].vval.v_string = cmd;
786 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100787 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200788 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100789
790theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200791 vim_free(opt.jo_eof_chars);
792}
793
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100794#if defined(FEAT_SESSION) || defined(PROTO)
795/*
796 * Write a :terminal command to the session file to restore the terminal in
797 * window "wp".
798 * Return FAIL if writing fails.
799 */
800 int
801term_write_session(FILE *fd, win_T *wp)
802{
803 term_T *term = wp->w_buffer->b_term;
804
805 /* Create the terminal and run the command. This is not without
806 * risk, but let's assume the user only creates a session when this
807 * will be OK. */
808 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
809 term->tl_cols, term->tl_rows) < 0)
810 return FAIL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100811#ifdef WIN3264
812 if (*wp->w_p_tmod != NUL)
813 if (fprintf(fd, "++%s ", wp->w_p_tmod) < 0)
814 return FAIL;
815#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100816 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
817 return FAIL;
818
819 return put_eol(fd);
820}
821
822/*
823 * Return TRUE if "buf" has a terminal that should be restored.
824 */
825 int
826term_should_restore(buf_T *buf)
827{
828 term_T *term = buf->b_term;
829
830 return term != NULL && (term->tl_command == NULL
831 || STRCMP(term->tl_command, "NONE") != 0);
832}
833#endif
834
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200835/*
836 * Free the scrollback buffer for "term".
837 */
838 static void
839free_scrollback(term_T *term)
840{
841 int i;
842
843 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
844 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
845 ga_clear(&term->tl_scrollback);
846}
847
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100848
849// Terminals that need to be freed soon.
850term_T *terminals_to_free = NULL;
851
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200852/*
853 * Free a terminal and everything it refers to.
854 * Kills the job if there is one.
855 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100856 * The actual terminal structure is freed later in free_unused_terminals(),
857 * because callbacks may wipe out a buffer while the terminal is still
858 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200859 */
860 void
861free_terminal(buf_T *buf)
862{
863 term_T *term = buf->b_term;
864 term_T *tp;
865
866 if (term == NULL)
867 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100868
869 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200870 if (first_term == term)
871 first_term = term->tl_next;
872 else
873 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
874 if (tp->tl_next == term)
875 {
876 tp->tl_next = term->tl_next;
877 break;
878 }
879
880 if (term->tl_job != NULL)
881 {
882 if (term->tl_job->jv_status != JOB_ENDED
883 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100884 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200885 job_stop(term->tl_job, NULL, "kill");
886 job_unref(term->tl_job);
887 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100888 term->tl_next = terminals_to_free;
889 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200890
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200891 buf->b_term = NULL;
892 if (in_terminal_loop == term)
893 in_terminal_loop = NULL;
894}
895
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100896 void
897free_unused_terminals()
898{
899 while (terminals_to_free != NULL)
900 {
901 term_T *term = terminals_to_free;
902
903 terminals_to_free = term->tl_next;
904
905 free_scrollback(term);
906
907 term_free_vterm(term);
908 vim_free(term->tl_title);
909#ifdef FEAT_SESSION
910 vim_free(term->tl_command);
911#endif
912 vim_free(term->tl_kill);
913 vim_free(term->tl_status_text);
914 vim_free(term->tl_opencmd);
915 vim_free(term->tl_eof_chars);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100916 vim_free(term->tl_arg0_cmd);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100917#ifdef WIN3264
918 if (term->tl_out_fd != NULL)
919 fclose(term->tl_out_fd);
920#endif
921 vim_free(term->tl_cursor_color);
922 vim_free(term);
923 }
924}
925
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200926/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100927 * Get the part that is connected to the tty. Normally this is PART_IN, but
928 * when writing buffer lines to the job it can be another. This makes it
929 * possible to do "1,5term vim -".
930 */
931 static ch_part_T
932get_tty_part(term_T *term)
933{
934#ifdef UNIX
935 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
936 int i;
937
938 for (i = 0; i < 3; ++i)
939 {
940 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
941
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +0100942 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100943 return parts[i];
944 }
945#endif
946 return PART_IN;
947}
948
949/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200950 * Write job output "msg[len]" to the vterm.
951 */
952 static void
953term_write_job_output(term_T *term, char_u *msg, size_t len)
954{
955 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100956 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200957
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100958 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200959
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100960 /* flush vterm buffer when vterm responded to control sequence */
961 if (prevlen != vterm_output_get_buffer_current(vterm))
962 {
963 char buf[KEY_BUF_LEN];
964 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
965
966 if (curlen > 0)
967 channel_send(term->tl_job->jv_channel, get_tty_part(term),
968 (char_u *)buf, (int)curlen, NULL);
969 }
970
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200971 /* this invokes the damage callbacks */
972 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
973}
974
975 static void
976update_cursor(term_T *term, int redraw)
977{
978 if (term->tl_normal_mode)
979 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100980#ifdef FEAT_GUI
981 if (term->tl_system)
982 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
983 term->tl_cursor_pos.col);
984 else
985#endif
986 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200987 if (redraw)
988 {
989 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
990 cursor_on();
991 out_flush();
992#ifdef FEAT_GUI
993 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100994 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200995 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100996 gui_mch_flush();
997 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200998#endif
999 }
1000}
1001
1002/*
1003 * Invoked when "msg" output from a job was received. Write it to the terminal
1004 * of "buffer".
1005 */
1006 void
1007write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1008{
1009 size_t len = STRLEN(msg);
1010 term_T *term = buffer->b_term;
1011
Bram Moolenaarf25329c2018-05-06 21:49:32 +02001012#ifdef WIN3264
1013 /* Win32: Cannot redirect output of the job, intercept it here and write to
1014 * the file. */
1015 if (term->tl_out_fd != NULL)
1016 {
1017 ch_log(channel, "Writing %d bytes to output file", (int)len);
1018 fwrite(msg, len, 1, term->tl_out_fd);
1019 return;
1020 }
1021#endif
1022
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001023 if (term->tl_vterm == NULL)
1024 {
1025 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1026 return;
1027 }
1028 ch_log(channel, "writing %d bytes to terminal", (int)len);
1029 term_write_job_output(term, msg, len);
1030
Bram Moolenaar13568252018-03-16 20:46:58 +01001031#ifdef FEAT_GUI
1032 if (term->tl_system)
1033 {
1034 /* show system output, scrolling up the screen as needed */
1035 update_system_term(term);
1036 update_cursor(term, TRUE);
1037 }
1038 else
1039#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001040 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
1041 * contents, thus no screen update is needed. */
1042 if (!term->tl_normal_mode)
1043 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001044 // Don't use update_screen() when editing the command line, it gets
1045 // cleared.
1046 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001047 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001048 if (buffer == curbuf && (State & CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001049 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001050 update_screen(VALID_NO_UPDATE);
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001051 /* update_screen() can be slow, check the terminal wasn't closed
1052 * already */
1053 if (buffer == curbuf && curbuf->b_term != NULL)
1054 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001055 }
1056 else
1057 redraw_after_callback(TRUE);
1058 }
1059}
1060
1061/*
1062 * Send a mouse position and click to the vterm
1063 */
1064 static int
1065term_send_mouse(VTerm *vterm, int button, int pressed)
1066{
1067 VTermModifier mod = VTERM_MOD_NONE;
1068
1069 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +02001070 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001071 if (button != 0)
1072 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001073 return TRUE;
1074}
1075
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001076static int enter_mouse_col = -1;
1077static int enter_mouse_row = -1;
1078
1079/*
1080 * Handle a mouse click, drag or release.
1081 * Return TRUE when a mouse event is sent to the terminal.
1082 */
1083 static int
1084term_mouse_click(VTerm *vterm, int key)
1085{
1086#if defined(FEAT_CLIPBOARD)
1087 /* For modeless selection mouse drag and release events are ignored, unless
1088 * they are preceded with a mouse down event */
1089 static int ignore_drag_release = TRUE;
1090 VTermMouseState mouse_state;
1091
1092 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1093 if (mouse_state.flags == 0)
1094 {
1095 /* Terminal is not using the mouse, use modeless selection. */
1096 switch (key)
1097 {
1098 case K_LEFTDRAG:
1099 case K_LEFTRELEASE:
1100 case K_RIGHTDRAG:
1101 case K_RIGHTRELEASE:
1102 /* Ignore drag and release events when the button-down wasn't
1103 * seen before. */
1104 if (ignore_drag_release)
1105 {
1106 int save_mouse_col, save_mouse_row;
1107
1108 if (enter_mouse_col < 0)
1109 break;
1110
1111 /* mouse click in the window gave us focus, handle that
1112 * click now */
1113 save_mouse_col = mouse_col;
1114 save_mouse_row = mouse_row;
1115 mouse_col = enter_mouse_col;
1116 mouse_row = enter_mouse_row;
1117 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1118 mouse_col = save_mouse_col;
1119 mouse_row = save_mouse_row;
1120 }
1121 /* FALLTHROUGH */
1122 case K_LEFTMOUSE:
1123 case K_RIGHTMOUSE:
1124 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1125 ignore_drag_release = TRUE;
1126 else
1127 ignore_drag_release = FALSE;
1128 /* Should we call mouse_has() here? */
1129 if (clip_star.available)
1130 {
1131 int button, is_click, is_drag;
1132
1133 button = get_mouse_button(KEY2TERMCAP1(key),
1134 &is_click, &is_drag);
1135 if (mouse_model_popup() && button == MOUSE_LEFT
1136 && (mod_mask & MOD_MASK_SHIFT))
1137 {
1138 /* Translate shift-left to right button. */
1139 button = MOUSE_RIGHT;
1140 mod_mask &= ~MOD_MASK_SHIFT;
1141 }
1142 clip_modeless(button, is_click, is_drag);
1143 }
1144 break;
1145
1146 case K_MIDDLEMOUSE:
1147 if (clip_star.available)
1148 insert_reg('*', TRUE);
1149 break;
1150 }
1151 enter_mouse_col = -1;
1152 return FALSE;
1153 }
1154#endif
1155 enter_mouse_col = -1;
1156
1157 switch (key)
1158 {
1159 case K_LEFTMOUSE:
1160 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1161 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1162 case K_LEFTRELEASE:
1163 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1164 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1165 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1166 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1167 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1168 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1169 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1170 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1171 }
1172 return TRUE;
1173}
1174
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001175/*
1176 * Convert typed key "c" into bytes to send to the job.
1177 * Return the number of bytes in "buf".
1178 */
1179 static int
1180term_convert_key(term_T *term, int c, char *buf)
1181{
1182 VTerm *vterm = term->tl_vterm;
1183 VTermKey key = VTERM_KEY_NONE;
1184 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001185 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001186
1187 switch (c)
1188 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001189 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1190
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001191 /* don't use VTERM_KEY_BACKSPACE, it always
1192 * becomes 0x7f DEL */
1193 case K_BS: c = term_backspace_char; break;
1194
1195 case ESC: key = VTERM_KEY_ESCAPE; break;
1196 case K_DEL: key = VTERM_KEY_DEL; break;
1197 case K_DOWN: key = VTERM_KEY_DOWN; break;
1198 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1199 key = VTERM_KEY_DOWN; break;
1200 case K_END: key = VTERM_KEY_END; break;
1201 case K_S_END: mod = VTERM_MOD_SHIFT;
1202 key = VTERM_KEY_END; break;
1203 case K_C_END: mod = VTERM_MOD_CTRL;
1204 key = VTERM_KEY_END; break;
1205 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1206 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1207 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1208 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1209 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1210 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1211 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1212 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1213 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1214 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1215 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1216 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1217 case K_HOME: key = VTERM_KEY_HOME; break;
1218 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1219 key = VTERM_KEY_HOME; break;
1220 case K_C_HOME: mod = VTERM_MOD_CTRL;
1221 key = VTERM_KEY_HOME; break;
1222 case K_INS: key = VTERM_KEY_INS; break;
1223 case K_K0: key = VTERM_KEY_KP_0; break;
1224 case K_K1: key = VTERM_KEY_KP_1; break;
1225 case K_K2: key = VTERM_KEY_KP_2; break;
1226 case K_K3: key = VTERM_KEY_KP_3; break;
1227 case K_K4: key = VTERM_KEY_KP_4; break;
1228 case K_K5: key = VTERM_KEY_KP_5; break;
1229 case K_K6: key = VTERM_KEY_KP_6; break;
1230 case K_K7: key = VTERM_KEY_KP_7; break;
1231 case K_K8: key = VTERM_KEY_KP_8; break;
1232 case K_K9: key = VTERM_KEY_KP_9; break;
1233 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1234 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1235 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1236 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1237 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1238 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1239 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1240 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1241 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1242 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1243 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1244 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1245 case K_LEFT: key = VTERM_KEY_LEFT; break;
1246 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1247 key = VTERM_KEY_LEFT; break;
1248 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1249 key = VTERM_KEY_LEFT; break;
1250 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1251 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1252 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1253 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1254 key = VTERM_KEY_RIGHT; break;
1255 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1256 key = VTERM_KEY_RIGHT; break;
1257 case K_UP: key = VTERM_KEY_UP; break;
1258 case K_S_UP: mod = VTERM_MOD_SHIFT;
1259 key = VTERM_KEY_UP; break;
1260 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001261 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1262 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001263
Bram Moolenaara42ad572017-11-16 13:08:04 +01001264 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1265 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001266 case K_MOUSELEFT: /* TODO */ return 0;
1267 case K_MOUSERIGHT: /* TODO */ return 0;
1268
1269 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001270 case K_LEFTMOUSE_NM:
1271 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001272 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001273 case K_LEFTRELEASE_NM:
1274 case K_MOUSEMOVE:
1275 case K_MIDDLEMOUSE:
1276 case K_MIDDLEDRAG:
1277 case K_MIDDLERELEASE:
1278 case K_RIGHTMOUSE:
1279 case K_RIGHTDRAG:
1280 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1281 return 0;
1282 other = TRUE;
1283 break;
1284
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001285 case K_X1MOUSE: /* TODO */ return 0;
1286 case K_X1DRAG: /* TODO */ return 0;
1287 case K_X1RELEASE: /* TODO */ return 0;
1288 case K_X2MOUSE: /* TODO */ return 0;
1289 case K_X2DRAG: /* TODO */ return 0;
1290 case K_X2RELEASE: /* TODO */ return 0;
1291
1292 case K_IGNORE: return 0;
1293 case K_NOP: return 0;
1294 case K_UNDO: return 0;
1295 case K_HELP: return 0;
1296 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1297 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1298 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1299 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1300 case K_SELECT: return 0;
1301#ifdef FEAT_GUI
1302 case K_VER_SCROLLBAR: return 0;
1303 case K_HOR_SCROLLBAR: return 0;
1304#endif
1305#ifdef FEAT_GUI_TABLINE
1306 case K_TABLINE: return 0;
1307 case K_TABMENU: return 0;
1308#endif
1309#ifdef FEAT_NETBEANS_INTG
1310 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1311#endif
1312#ifdef FEAT_DND
1313 case K_DROP: return 0;
1314#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001315 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001316 case K_PS: vterm_keyboard_start_paste(vterm);
1317 other = TRUE;
1318 break;
1319 case K_PE: vterm_keyboard_end_paste(vterm);
1320 other = TRUE;
1321 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001322 }
1323
1324 /*
1325 * Convert special keys to vterm keys:
1326 * - Write keys to vterm: vterm_keyboard_key()
1327 * - Write output to channel.
1328 * TODO: use mod_mask
1329 */
1330 if (key != VTERM_KEY_NONE)
1331 /* Special key, let vterm convert it. */
1332 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001333 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001334 /* Normal character, let vterm convert it. */
1335 vterm_keyboard_unichar(vterm, c, mod);
1336
1337 /* Read back the converted escape sequence. */
1338 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1339}
1340
1341/*
1342 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001343 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001344 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001345 */
1346 static int
1347term_job_running_check(term_T *term, int check_job_status)
1348{
1349 /* Also consider the job finished when the channel is closed, to avoid a
1350 * race condition when updating the title. */
1351 if (term != NULL
1352 && term->tl_job != NULL
1353 && channel_is_open(term->tl_job->jv_channel))
1354 {
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001355 job_T *job = term->tl_job;
1356
1357 // Careful: Checking the job status may invoked callbacks, which close
1358 // the buffer and terminate "term". However, "job" will not be freed
1359 // yet.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001360 if (check_job_status)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001361 job_status(job);
1362 return (job->jv_status == JOB_STARTED
1363 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001364 }
1365 return FALSE;
1366}
1367
1368/*
1369 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001370 */
1371 int
1372term_job_running(term_T *term)
1373{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001374 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001375}
1376
1377/*
1378 * Return TRUE if "term" has an active channel and used ":term NONE".
1379 */
1380 int
1381term_none_open(term_T *term)
1382{
1383 /* Also consider the job finished when the channel is closed, to avoid a
1384 * race condition when updating the title. */
1385 return term != NULL
1386 && term->tl_job != NULL
1387 && channel_is_open(term->tl_job->jv_channel)
1388 && term->tl_job->jv_channel->ch_keep_open;
1389}
1390
1391/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001392 * Used when exiting: kill the job in "buf" if so desired.
1393 * Return OK when the job finished.
1394 * Return FAIL when the job is still running.
1395 */
1396 int
1397term_try_stop_job(buf_T *buf)
1398{
1399 int count;
1400 char *how = (char *)buf->b_term->tl_kill;
1401
1402#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1403 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1404 {
1405 char_u buff[DIALOG_MSG_SIZE];
1406 int ret;
1407
1408 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1409 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1410 if (ret == VIM_YES)
1411 how = "kill";
1412 else if (ret == VIM_CANCEL)
1413 return FAIL;
1414 }
1415#endif
1416 if (how == NULL || *how == NUL)
1417 return FAIL;
1418
1419 job_stop(buf->b_term->tl_job, NULL, how);
1420
Bram Moolenaar9172d232019-01-29 23:06:54 +01001421 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001422 for (count = 0; count < 100; ++count)
1423 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001424 job_T *job;
1425
1426 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001427 if (!buf_valid(buf)
1428 || buf->b_term == NULL
1429 || buf->b_term->tl_job == NULL)
1430 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001431 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001432
Bram Moolenaar9172d232019-01-29 23:06:54 +01001433 // Call job_status() to update jv_status. It may cause the job to be
1434 // cleaned up but it won't be freed.
1435 job_status(job);
1436 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001437 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001438
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001439 ui_delay(10L, FALSE);
1440 mch_check_messages();
1441 parse_queued_messages();
1442 }
1443 return FAIL;
1444}
1445
1446/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001447 * Add the last line of the scrollback buffer to the buffer in the window.
1448 */
1449 static void
1450add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1451{
1452 buf_T *buf = term->tl_buffer;
1453 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1454 linenr_T lnum = buf->b_ml.ml_line_count;
1455
1456#ifdef WIN3264
1457 if (!enc_utf8 && enc_codepage > 0)
1458 {
1459 WCHAR *ret = NULL;
1460 int length = 0;
1461
1462 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1463 &ret, &length);
1464 if (ret != NULL)
1465 {
1466 WideCharToMultiByte_alloc(enc_codepage, 0,
1467 ret, length, (char **)&text, &len, 0, 0);
1468 vim_free(ret);
1469 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1470 vim_free(text);
1471 }
1472 }
1473 else
1474#endif
1475 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1476 if (empty)
1477 {
1478 /* Delete the empty line that was in the empty buffer. */
1479 curbuf = buf;
1480 ml_delete(1, FALSE);
1481 curbuf = curwin->w_buffer;
1482 }
1483}
1484
1485 static void
1486cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1487{
1488 attr->width = cell->width;
1489 attr->attrs = cell->attrs;
1490 attr->fg = cell->fg;
1491 attr->bg = cell->bg;
1492}
1493
1494 static int
1495equal_celattr(cellattr_T *a, cellattr_T *b)
1496{
1497 /* Comparing the colors should be sufficient. */
1498 return a->fg.red == b->fg.red
1499 && a->fg.green == b->fg.green
1500 && a->fg.blue == b->fg.blue
1501 && a->bg.red == b->bg.red
1502 && a->bg.green == b->bg.green
1503 && a->bg.blue == b->bg.blue;
1504}
1505
Bram Moolenaard96ff162018-02-18 22:13:29 +01001506/*
1507 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1508 * line at this position. Otherwise at the end.
1509 */
1510 static int
1511add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1512{
1513 if (ga_grow(&term->tl_scrollback, 1) == OK)
1514 {
1515 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1516 + term->tl_scrollback.ga_len;
1517
1518 if (lnum > 0)
1519 {
1520 int i;
1521
1522 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1523 {
1524 *line = *(line - 1);
1525 --line;
1526 }
1527 }
1528 line->sb_cols = 0;
1529 line->sb_cells = NULL;
1530 line->sb_fill_attr = *fill_attr;
1531 ++term->tl_scrollback.ga_len;
1532 return OK;
1533 }
1534 return FALSE;
1535}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001536
1537/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001538 * Remove the terminal contents from the scrollback and the buffer.
1539 * Used before adding a new scrollback line or updating the buffer for lines
1540 * displayed in the terminal.
1541 */
1542 static void
1543cleanup_scrollback(term_T *term)
1544{
1545 sb_line_T *line;
1546 garray_T *gap;
1547
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001548 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001549 gap = &term->tl_scrollback;
1550 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1551 && gap->ga_len > 0)
1552 {
1553 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1554 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1555 vim_free(line->sb_cells);
1556 --gap->ga_len;
1557 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001558 curbuf = curwin->w_buffer;
1559 if (curbuf == term->tl_buffer)
1560 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001561}
1562
1563/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001564 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001565 */
1566 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001567update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001568{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001569 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001570 int len;
1571 int lines_skipped = 0;
1572 VTermPos pos;
1573 VTermScreenCell cell;
1574 cellattr_T fill_attr, new_fill_attr;
1575 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001576
1577 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1578 "Adding terminal window snapshot to buffer");
1579
1580 /* First remove the lines that were appended before, they might be
1581 * outdated. */
1582 cleanup_scrollback(term);
1583
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001584 screen = vterm_obtain_screen(term->tl_vterm);
1585 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001586 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1587 {
1588 len = 0;
1589 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1590 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1591 && cell.chars[0] != NUL)
1592 {
1593 len = pos.col + 1;
1594 new_fill_attr = term->tl_default_color;
1595 }
1596 else
1597 /* Assume the last attr is the filler attr. */
1598 cell2cellattr(&cell, &new_fill_attr);
1599
1600 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1601 ++lines_skipped;
1602 else
1603 {
1604 while (lines_skipped > 0)
1605 {
1606 /* Line was skipped, add an empty line. */
1607 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001608 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001609 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001610 }
1611
1612 if (len == 0)
1613 p = NULL;
1614 else
1615 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1616 if ((p != NULL || len == 0)
1617 && ga_grow(&term->tl_scrollback, 1) == OK)
1618 {
1619 garray_T ga;
1620 int width;
1621 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1622 + term->tl_scrollback.ga_len;
1623
1624 ga_init2(&ga, 1, 100);
1625 for (pos.col = 0; pos.col < len; pos.col += width)
1626 {
1627 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1628 {
1629 width = 1;
1630 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1631 if (ga_grow(&ga, 1) == OK)
1632 ga.ga_len += utf_char2bytes(' ',
1633 (char_u *)ga.ga_data + ga.ga_len);
1634 }
1635 else
1636 {
1637 width = cell.width;
1638
1639 cell2cellattr(&cell, &p[pos.col]);
1640
Bram Moolenaara79fd562018-12-20 20:47:32 +01001641 // Each character can be up to 6 bytes.
1642 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001643 {
1644 int i;
1645 int c;
1646
1647 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1648 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1649 (char_u *)ga.ga_data + ga.ga_len);
1650 }
1651 }
1652 }
1653 line->sb_cols = len;
1654 line->sb_cells = p;
1655 line->sb_fill_attr = new_fill_attr;
1656 fill_attr = new_fill_attr;
1657 ++term->tl_scrollback.ga_len;
1658
1659 if (ga_grow(&ga, 1) == FAIL)
1660 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1661 else
1662 {
1663 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1664 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1665 }
1666 ga_clear(&ga);
1667 }
1668 else
1669 vim_free(p);
1670 }
1671 }
1672
Bram Moolenaarf3aea592018-11-11 22:18:21 +01001673 // Add trailing empty lines.
1674 for (pos.row = term->tl_scrollback.ga_len;
1675 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1676 ++pos.row)
1677 {
1678 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1679 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1680 }
1681
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001682 term->tl_dirty_snapshot = FALSE;
1683#ifdef FEAT_TIMERS
1684 term->tl_timer_set = FALSE;
1685#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001686}
1687
1688/*
1689 * If needed, add the current lines of the terminal to scrollback and to the
1690 * buffer. Called after the job has ended and when switching to
1691 * Terminal-Normal mode.
1692 * When "redraw" is TRUE redraw the windows that show the terminal.
1693 */
1694 static void
1695may_move_terminal_to_buffer(term_T *term, int redraw)
1696{
1697 win_T *wp;
1698
1699 if (term->tl_vterm == NULL)
1700 return;
1701
1702 /* Update the snapshot only if something changes or the buffer does not
1703 * have all the lines. */
1704 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
1705 <= term->tl_scrollback_scrolled)
1706 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001707
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001708 /* Obtain the current background color. */
1709 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1710 &term->tl_default_color.fg, &term->tl_default_color.bg);
1711
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001712 if (redraw)
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001713 FOR_ALL_WINDOWS(wp)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001714 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001715 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001716 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001717 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1718 wp->w_cursor.col = 0;
1719 wp->w_valid = 0;
1720 if (wp->w_cursor.lnum >= wp->w_height)
1721 {
1722 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001723
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001724 if (wp->w_topline < min_topline)
1725 wp->w_topline = min_topline;
1726 }
1727 redraw_win_later(wp, NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001728 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001729 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001730}
1731
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001732#if defined(FEAT_TIMERS) || defined(PROTO)
1733/*
1734 * Check if any terminal timer expired. If so, copy text from the terminal to
1735 * the buffer.
1736 * Return the time until the next timer will expire.
1737 */
1738 int
1739term_check_timers(int next_due_arg, proftime_T *now)
1740{
1741 term_T *term;
1742 int next_due = next_due_arg;
1743
1744 for (term = first_term; term != NULL; term = term->tl_next)
1745 {
1746 if (term->tl_timer_set && !term->tl_normal_mode)
1747 {
1748 long this_due = proftime_time_left(&term->tl_timer_due, now);
1749
1750 if (this_due <= 1)
1751 {
1752 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001753 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001754 }
1755 else if (next_due == -1 || next_due > this_due)
1756 next_due = this_due;
1757 }
1758 }
1759
1760 return next_due;
1761}
1762#endif
1763
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001764 static void
1765set_terminal_mode(term_T *term, int normal_mode)
1766{
1767 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001768 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001769 if (term->tl_buffer == curbuf)
1770 maketitle();
1771}
1772
1773/*
1774 * Called after the job if finished and Terminal mode is not active:
1775 * Move the vterm contents into the scrollback buffer and free the vterm.
1776 */
1777 static void
1778cleanup_vterm(term_T *term)
1779{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001780 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001781 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001782 term_free_vterm(term);
1783 set_terminal_mode(term, FALSE);
1784}
1785
1786/*
1787 * Switch from Terminal-Job mode to Terminal-Normal mode.
1788 * Suspends updating the terminal window.
1789 */
1790 static void
1791term_enter_normal_mode(void)
1792{
1793 term_T *term = curbuf->b_term;
1794
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001795 set_terminal_mode(term, TRUE);
1796
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001797 /* Append the current terminal contents to the buffer. */
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001798 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001799
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001800 /* Move the window cursor to the position of the cursor in the
1801 * terminal. */
1802 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1803 + term->tl_cursor_pos.row + 1;
1804 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02001805 if (coladvance(term->tl_cursor_pos.col) == FAIL)
1806 coladvance(MAXCOL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001807
1808 /* Display the same lines as in the terminal. */
1809 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1810}
1811
1812/*
1813 * Returns TRUE if the current window contains a terminal and we are in
1814 * Terminal-Normal mode.
1815 */
1816 int
1817term_in_normal_mode(void)
1818{
1819 term_T *term = curbuf->b_term;
1820
1821 return term != NULL && term->tl_normal_mode;
1822}
1823
1824/*
1825 * Switch from Terminal-Normal mode to Terminal-Job mode.
1826 * Restores updating the terminal window.
1827 */
1828 void
1829term_enter_job_mode()
1830{
1831 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001832
1833 set_terminal_mode(term, FALSE);
1834
1835 if (term->tl_channel_closed)
1836 cleanup_vterm(term);
1837 redraw_buf_and_status_later(curbuf, NOT_VALID);
1838}
1839
1840/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001841 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001842 * Note: while waiting a terminal may be closed and freed if the channel is
1843 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001844 */
1845 static int
1846term_vgetc()
1847{
1848 int c;
1849 int save_State = State;
1850
1851 State = TERMINAL;
1852 got_int = FALSE;
1853#ifdef WIN3264
1854 ctrl_break_was_pressed = FALSE;
1855#endif
1856 c = vgetc();
1857 got_int = FALSE;
1858 State = save_State;
1859 return c;
1860}
1861
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001862static int mouse_was_outside = FALSE;
1863
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001864/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001865 * Send keys to terminal.
1866 * Return FAIL when the key needs to be handled in Normal mode.
1867 * Return OK when the key was dropped or sent to the terminal.
1868 */
1869 int
1870send_keys_to_term(term_T *term, int c, int typed)
1871{
1872 char msg[KEY_BUF_LEN];
1873 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001874 int dragging_outside = FALSE;
1875
1876 /* Catch keys that need to be handled as in Normal mode. */
1877 switch (c)
1878 {
1879 case NUL:
1880 case K_ZERO:
1881 if (typed)
1882 stuffcharReadbuff(c);
1883 return FAIL;
1884
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001885 case K_TABLINE:
1886 stuffcharReadbuff(c);
1887 return FAIL;
1888
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001889 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001890 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001891 return FAIL;
1892
1893 case K_LEFTDRAG:
1894 case K_MIDDLEDRAG:
1895 case K_RIGHTDRAG:
1896 case K_X1DRAG:
1897 case K_X2DRAG:
1898 dragging_outside = mouse_was_outside;
1899 /* FALLTHROUGH */
1900 case K_LEFTMOUSE:
1901 case K_LEFTMOUSE_NM:
1902 case K_LEFTRELEASE:
1903 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001904 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001905 case K_MIDDLEMOUSE:
1906 case K_MIDDLERELEASE:
1907 case K_RIGHTMOUSE:
1908 case K_RIGHTRELEASE:
1909 case K_X1MOUSE:
1910 case K_X1RELEASE:
1911 case K_X2MOUSE:
1912 case K_X2RELEASE:
1913
1914 case K_MOUSEUP:
1915 case K_MOUSEDOWN:
1916 case K_MOUSELEFT:
1917 case K_MOUSERIGHT:
1918 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001919 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001920 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001921 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001922 || dragging_outside)
1923 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001924 /* click or scroll outside the current window or on status line
1925 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001926 if (typed)
1927 {
1928 stuffcharReadbuff(c);
1929 mouse_was_outside = TRUE;
1930 }
1931 return FAIL;
1932 }
1933 }
1934 if (typed)
1935 mouse_was_outside = FALSE;
1936
1937 /* Convert the typed key to a sequence of bytes for the job. */
1938 len = term_convert_key(term, c, msg);
1939 if (len > 0)
1940 /* TODO: if FAIL is returned, stop? */
1941 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1942 (char_u *)msg, (int)len, NULL);
1943
1944 return OK;
1945}
1946
1947 static void
1948position_cursor(win_T *wp, VTermPos *pos)
1949{
1950 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1951 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1952 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1953}
1954
1955/*
1956 * Handle CTRL-W "": send register contents to the job.
1957 */
1958 static void
1959term_paste_register(int prev_c UNUSED)
1960{
1961 int c;
1962 list_T *l;
1963 listitem_T *item;
1964 long reglen = 0;
1965 int type;
1966
1967#ifdef FEAT_CMDL_INFO
1968 if (add_to_showcmd(prev_c))
1969 if (add_to_showcmd('"'))
1970 out_flush();
1971#endif
1972 c = term_vgetc();
1973#ifdef FEAT_CMDL_INFO
1974 clear_showcmd();
1975#endif
1976 if (!term_use_loop())
1977 /* job finished while waiting for a character */
1978 return;
1979
1980 /* CTRL-W "= prompt for expression to evaluate. */
1981 if (c == '=' && get_expr_register() != '=')
1982 return;
1983 if (!term_use_loop())
1984 /* job finished while waiting for a character */
1985 return;
1986
1987 l = (list_T *)get_reg_contents(c, GREG_LIST);
1988 if (l != NULL)
1989 {
1990 type = get_reg_type(c, &reglen);
1991 for (item = l->lv_first; item != NULL; item = item->li_next)
1992 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01001993 char_u *s = tv_get_string(&item->li_tv);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001994#ifdef WIN3264
1995 char_u *tmp = s;
1996
1997 if (!enc_utf8 && enc_codepage > 0)
1998 {
1999 WCHAR *ret = NULL;
2000 int length = 0;
2001
2002 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2003 (int)STRLEN(s), &ret, &length);
2004 if (ret != NULL)
2005 {
2006 WideCharToMultiByte_alloc(CP_UTF8, 0,
2007 ret, length, (char **)&s, &length, 0, 0);
2008 vim_free(ret);
2009 }
2010 }
2011#endif
2012 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2013 s, (int)STRLEN(s), NULL);
2014#ifdef WIN3264
2015 if (tmp != s)
2016 vim_free(s);
2017#endif
2018
2019 if (item->li_next != NULL || type == MLINE)
2020 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2021 (char_u *)"\r", 1, NULL);
2022 }
2023 list_free(l);
2024 }
2025}
2026
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002027/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002028 * Return TRUE when waiting for a character in the terminal, the cursor of the
2029 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002030 */
2031 int
2032terminal_is_active()
2033{
2034 return in_terminal_loop != NULL;
2035}
2036
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002037#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002038 cursorentry_T *
2039term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2040{
2041 term_T *term = in_terminal_loop;
2042 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002043 int id;
2044 guicolor_T term_fg, term_bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002045
2046 vim_memset(&entry, 0, sizeof(entry));
2047 entry.shape = entry.mshape =
2048 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2049 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2050 SHAPE_BLOCK;
2051 entry.percentage = 20;
2052 if (term->tl_cursor_blink)
2053 {
2054 entry.blinkwait = 700;
2055 entry.blinkon = 400;
2056 entry.blinkoff = 250;
2057 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002058
2059 /* The "Terminal" highlight group overrules the defaults. */
2060 id = syn_name2id((char_u *)"Terminal");
2061 if (id != 0)
2062 {
2063 syn_id2colors(id, &term_fg, &term_bg);
2064 *fg = term_bg;
2065 }
2066 else
2067 *fg = gui.back_pixel;
2068
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002069 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002070 {
2071 if (id != 0)
2072 *bg = term_fg;
2073 else
2074 *bg = gui.norm_pixel;
2075 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002076 else
2077 *bg = color_name2handle(term->tl_cursor_color);
2078 entry.name = "n";
2079 entry.used_for = SHAPE_CURSOR;
2080
2081 return &entry;
2082}
2083#endif
2084
Bram Moolenaard317b382018-02-08 22:33:31 +01002085 static void
2086may_output_cursor_props(void)
2087{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002088 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
Bram Moolenaard317b382018-02-08 22:33:31 +01002089 || last_set_cursor_shape != desired_cursor_shape
2090 || last_set_cursor_blink != desired_cursor_blink)
2091 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002092 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002093 last_set_cursor_shape = desired_cursor_shape;
2094 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002095 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002096 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
2097 /* this will restore the initial cursor style, if possible */
2098 ui_cursor_shape_forced(TRUE);
2099 else
2100 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2101 }
2102}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002103
Bram Moolenaard317b382018-02-08 22:33:31 +01002104/*
2105 * Set the cursor color and shape, if not last set to these.
2106 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002107 static void
2108may_set_cursor_props(term_T *term)
2109{
2110#ifdef FEAT_GUI
2111 /* For the GUI the cursor properties are obtained with
2112 * term_get_cursor_shape(). */
2113 if (gui.in_use)
2114 return;
2115#endif
2116 if (in_terminal_loop == term)
2117 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002118 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002119 desired_cursor_shape = term->tl_cursor_shape;
2120 desired_cursor_blink = term->tl_cursor_blink;
2121 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002122 }
2123}
2124
Bram Moolenaard317b382018-02-08 22:33:31 +01002125/*
2126 * Reset the desired cursor properties and restore them when needed.
2127 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002128 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002129prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002130{
2131#ifdef FEAT_GUI
2132 if (gui.in_use)
2133 return;
2134#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002135 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002136 desired_cursor_shape = -1;
2137 desired_cursor_blink = -1;
2138 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002139}
2140
2141/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002142 * Returns TRUE if the current window contains a terminal and we are sending
2143 * keys to the job.
2144 * If "check_job_status" is TRUE update the job status.
2145 */
2146 static int
2147term_use_loop_check(int check_job_status)
2148{
2149 term_T *term = curbuf->b_term;
2150
2151 return term != NULL
2152 && !term->tl_normal_mode
2153 && term->tl_vterm != NULL
2154 && term_job_running_check(term, check_job_status);
2155}
2156
2157/*
2158 * Returns TRUE if the current window contains a terminal and we are sending
2159 * keys to the job.
2160 */
2161 int
2162term_use_loop(void)
2163{
2164 return term_use_loop_check(FALSE);
2165}
2166
2167/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002168 * Called when entering a window with the mouse. If this is a terminal window
2169 * we may want to change state.
2170 */
2171 void
2172term_win_entered()
2173{
2174 term_T *term = curbuf->b_term;
2175
2176 if (term != NULL)
2177 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002178 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002179 {
2180 reset_VIsual_and_resel();
2181 if (State & INSERT)
2182 stop_insert_mode = TRUE;
2183 }
2184 mouse_was_outside = FALSE;
2185 enter_mouse_col = mouse_col;
2186 enter_mouse_row = mouse_row;
2187 }
2188}
2189
2190/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002191 * Wait for input and send it to the job.
2192 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2193 * when there is no more typahead.
2194 * Return when the start of a CTRL-W command is typed or anything else that
2195 * should be handled as a Normal mode command.
2196 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2197 * the terminal was closed.
2198 */
2199 int
2200terminal_loop(int blocking)
2201{
2202 int c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002203 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002204 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002205#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002206 int tty_fd = curbuf->b_term->tl_job->jv_channel
2207 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002208#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002209 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002210
2211 /* Remember the terminal we are sending keys to. However, the terminal
2212 * might be closed while waiting for a character, e.g. typing "exit" in a
2213 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2214 * stored reference. */
2215 in_terminal_loop = curbuf->b_term;
2216
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002217 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002218 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002219 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002220 if (termwinkey == Ctrl_W)
2221 termwinkey = 0;
2222 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002223 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2224 may_set_cursor_props(curbuf->b_term);
2225
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002226 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002227 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002228#ifdef FEAT_GUI
2229 if (!curbuf->b_term->tl_system)
2230#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002231 // TODO: skip screen update when handling a sequence of keys.
2232 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002233 while (must_redraw != 0)
2234 if (update_screen(0) == FAIL)
2235 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002236 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002237 /* job finished while redrawing */
2238 break;
2239
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002240 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002241 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002242
2243 c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002244 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002245 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002246 /* Job finished while waiting for a character. Push back the
2247 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002248 if (c != K_IGNORE)
2249 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002250 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002251 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002252 if (c == K_IGNORE)
2253 continue;
2254
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002255#ifdef UNIX
2256 /*
2257 * The shell or another program may change the tty settings. Getting
2258 * them for every typed character is a bit of overhead, but it's needed
2259 * for the first character typed, e.g. when Vim starts in a shell.
2260 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002261 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002262 {
2263 ttyinfo_T info;
2264
2265 /* Get the current backspace character of the pty. */
2266 if (get_tty_info(tty_fd, &info) == OK)
2267 term_backspace_char = info.backspace;
2268 }
2269#endif
2270
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002271#ifdef WIN3264
2272 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2273 * Use CTRL-BREAK to kill the job. */
2274 if (ctrl_break_was_pressed)
2275 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2276#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002277 /* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002278 * Not in a system terminal. */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002279 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002280#ifdef FEAT_GUI
2281 && !curbuf->b_term->tl_system
2282#endif
2283 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002284 {
2285 int prev_c = c;
2286
2287#ifdef FEAT_CMDL_INFO
2288 if (add_to_showcmd(c))
2289 out_flush();
2290#endif
2291 c = term_vgetc();
2292#ifdef FEAT_CMDL_INFO
2293 clear_showcmd();
2294#endif
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002295 if (!term_use_loop_check(TRUE)
2296 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002297 /* job finished while waiting for a character */
2298 break;
2299
2300 if (prev_c == Ctrl_BSL)
2301 {
2302 if (c == Ctrl_N)
2303 {
2304 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2305 term_enter_normal_mode();
2306 ret = FAIL;
2307 goto theend;
2308 }
2309 /* Send both keys to the terminal. */
2310 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2311 }
2312 else if (c == Ctrl_C)
2313 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002314 /* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002315 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2316 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002317 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002318 {
2319 /* "CTRL-W .": send CTRL-W to the job */
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002320 /* "'termwinkey' .": send 'termwinkey' to the job */
2321 c = termwinkey == 0 ? Ctrl_W : termwinkey;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002322 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002323 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002324 {
2325 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2326 c = Ctrl_BSL;
2327 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002328 else if (c == 'N')
2329 {
2330 /* CTRL-W N : go to Terminal-Normal mode. */
2331 term_enter_normal_mode();
2332 ret = FAIL;
2333 goto theend;
2334 }
2335 else if (c == '"')
2336 {
2337 term_paste_register(prev_c);
2338 continue;
2339 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002340 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002341 {
2342 stuffcharReadbuff(Ctrl_W);
2343 stuffcharReadbuff(c);
2344 ret = OK;
2345 goto theend;
2346 }
2347 }
2348# ifdef WIN3264
2349 if (!enc_utf8 && has_mbyte && c >= 0x80)
2350 {
2351 WCHAR wc;
2352 char_u mb[3];
2353
2354 mb[0] = (unsigned)c >> 8;
2355 mb[1] = c;
2356 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2357 c = wc;
2358 }
2359# endif
2360 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2361 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002362 if (c == K_MOUSEMOVE)
2363 /* We are sure to come back here, don't reset the cursor color
2364 * and shape to avoid flickering. */
2365 restore_cursor = FALSE;
2366
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002367 ret = OK;
2368 goto theend;
2369 }
2370 }
2371 ret = FAIL;
2372
2373theend:
2374 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002375 if (restore_cursor)
2376 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002377
2378 /* Move a snapshot of the screen contents to the buffer, so that completion
2379 * works in other buffers. */
Bram Moolenaar620020e2018-05-13 19:06:12 +02002380 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2381 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002382
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002383 return ret;
2384}
2385
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002386 static void
2387may_toggle_cursor(term_T *term)
2388{
2389 if (in_terminal_loop == term)
2390 {
2391 if (term->tl_cursor_visible)
2392 cursor_on();
2393 else
2394 cursor_off();
2395 }
2396}
2397
2398/*
2399 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002400 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002401 */
2402 static int
2403color2index(VTermColor *color, int fg, int *boldp)
2404{
2405 int red = color->red;
2406 int blue = color->blue;
2407 int green = color->green;
2408
Bram Moolenaar46359e12017-11-29 22:33:38 +01002409 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002410 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002411 /* First 16 colors and default: use the ANSI index, because these
2412 * colors can be redefined. */
2413 if (t_colors >= 16)
2414 return color->ansi_index;
2415 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002416 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002417 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002418 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002419 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2420 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2421 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002422 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002423 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2424 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2425 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2426 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2427 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2428 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2429 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2430 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2431 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2432 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2433 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002434 }
2435 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002436
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002437 if (t_colors >= 256)
2438 {
2439 if (red == blue && red == green)
2440 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002441 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002442 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002443 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2444 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2445 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002446 int i;
2447
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002448 if (red < 5)
2449 return 17; /* 00/00/00 */
2450 if (red > 245) /* ff/ff/ff */
2451 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002452 for (i = 0; i < 23; ++i)
2453 if (red < cutoff[i])
2454 return i + 233;
2455 return 256;
2456 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002457 {
2458 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2459 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002460
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002461 /* 216-color cube */
2462 for (ri = 0; ri < 5; ++ri)
2463 if (red < cutoff[ri])
2464 break;
2465 for (gi = 0; gi < 5; ++gi)
2466 if (green < cutoff[gi])
2467 break;
2468 for (bi = 0; bi < 5; ++bi)
2469 if (blue < cutoff[bi])
2470 break;
2471 return 17 + ri * 36 + gi * 6 + bi;
2472 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002473 }
2474 return 0;
2475}
2476
2477/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002478 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002479 */
2480 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002481vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002482{
2483 int attr = 0;
2484
2485 if (cellattrs.bold)
2486 attr |= HL_BOLD;
2487 if (cellattrs.underline)
2488 attr |= HL_UNDERLINE;
2489 if (cellattrs.italic)
2490 attr |= HL_ITALIC;
2491 if (cellattrs.strike)
2492 attr |= HL_STRIKETHROUGH;
2493 if (cellattrs.reverse)
2494 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002495 return attr;
2496}
2497
2498/*
2499 * Store Vterm attributes in "cell" from highlight flags.
2500 */
2501 static void
2502hl2vtermAttr(int attr, cellattr_T *cell)
2503{
2504 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2505 if (attr & HL_BOLD)
2506 cell->attrs.bold = 1;
2507 if (attr & HL_UNDERLINE)
2508 cell->attrs.underline = 1;
2509 if (attr & HL_ITALIC)
2510 cell->attrs.italic = 1;
2511 if (attr & HL_STRIKETHROUGH)
2512 cell->attrs.strike = 1;
2513 if (attr & HL_INVERSE)
2514 cell->attrs.reverse = 1;
2515}
2516
2517/*
2518 * Convert the attributes of a vterm cell into an attribute index.
2519 */
2520 static int
2521cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2522{
2523 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002524
2525#ifdef FEAT_GUI
2526 if (gui.in_use)
2527 {
2528 guicolor_T fg, bg;
2529
2530 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2531 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2532 return get_gui_attr_idx(attr, fg, bg);
2533 }
2534 else
2535#endif
2536#ifdef FEAT_TERMGUICOLORS
2537 if (p_tgc)
2538 {
2539 guicolor_T fg, bg;
2540
2541 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2542 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2543
2544 return get_tgc_attr_idx(attr, fg, bg);
2545 }
2546 else
2547#endif
2548 {
2549 int bold = MAYBE;
2550 int fg = color2index(&cellfg, TRUE, &bold);
2551 int bg = color2index(&cellbg, FALSE, &bold);
2552
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002553 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002554 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002555 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002556 if (fg == 0 && term_default_cterm_fg >= 0)
2557 fg = term_default_cterm_fg + 1;
2558 if (bg == 0 && term_default_cterm_bg >= 0)
2559 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002560 }
2561
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002562 /* with 8 colors set the bold attribute to get a bright foreground */
2563 if (bold == TRUE)
2564 attr |= HL_BOLD;
2565 return get_cterm_attr_idx(attr, fg, bg);
2566 }
2567 return 0;
2568}
2569
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002570 static void
2571set_dirty_snapshot(term_T *term)
2572{
2573 term->tl_dirty_snapshot = TRUE;
2574#ifdef FEAT_TIMERS
2575 if (!term->tl_normal_mode)
2576 {
2577 /* Update the snapshot after 100 msec of not getting updates. */
2578 profile_setlimit(100L, &term->tl_timer_due);
2579 term->tl_timer_set = TRUE;
2580 }
2581#endif
2582}
2583
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002584 static int
2585handle_damage(VTermRect rect, void *user)
2586{
2587 term_T *term = (term_T *)user;
2588
2589 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2590 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002591 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002592 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002593 return 1;
2594}
2595
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002596 static void
2597term_scroll_up(term_T *term, int start_row, int count)
2598{
2599 win_T *wp;
2600 VTermColor fg, bg;
2601 VTermScreenCellAttrs attr;
2602 int clear_attr;
2603
2604 /* Set the color to clear lines with. */
2605 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2606 &fg, &bg);
2607 vim_memset(&attr, 0, sizeof(attr));
2608 clear_attr = cell2attr(attr, fg, bg);
2609
2610 FOR_ALL_WINDOWS(wp)
2611 {
2612 if (wp->w_buffer == term->tl_buffer)
2613 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2614 }
2615}
2616
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002617 static int
2618handle_moverect(VTermRect dest, VTermRect src, void *user)
2619{
2620 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002621 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002622
2623 /* Scrolling up is done much more efficiently by deleting lines instead of
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002624 * redrawing the text. But avoid doing this multiple times, postpone until
2625 * the redraw happens. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002626 if (dest.start_col == src.start_col
2627 && dest.end_col == src.end_col
2628 && dest.start_row < src.start_row)
2629 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002630 if (dest.start_row == 0)
2631 term->tl_postponed_scroll += count;
2632 else
2633 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002634 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002635
2636 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2637 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002638 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002639
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002640 /* Note sure if the scrolling will work correctly, let's do a complete
2641 * redraw later. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002642 redraw_buf_later(term->tl_buffer, NOT_VALID);
2643 return 1;
2644}
2645
2646 static int
2647handle_movecursor(
2648 VTermPos pos,
2649 VTermPos oldpos UNUSED,
2650 int visible,
2651 void *user)
2652{
2653 term_T *term = (term_T *)user;
2654 win_T *wp;
2655
2656 term->tl_cursor_pos = pos;
2657 term->tl_cursor_visible = visible;
2658
2659 FOR_ALL_WINDOWS(wp)
2660 {
2661 if (wp->w_buffer == term->tl_buffer)
2662 position_cursor(wp, &pos);
2663 }
2664 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2665 {
2666 may_toggle_cursor(term);
2667 update_cursor(term, term->tl_cursor_visible);
2668 }
2669
2670 return 1;
2671}
2672
2673 static int
2674handle_settermprop(
2675 VTermProp prop,
2676 VTermValue *value,
2677 void *user)
2678{
2679 term_T *term = (term_T *)user;
2680
2681 switch (prop)
2682 {
2683 case VTERM_PROP_TITLE:
2684 vim_free(term->tl_title);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002685 // a blank title isn't useful, make it empty, so that "running" is
2686 // displayed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002687 if (*skipwhite((char_u *)value->string) == NUL)
2688 term->tl_title = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002689 // Same as blank
2690 else if (term->tl_arg0_cmd != NULL
2691 && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string,
2692 (int)STRLEN(term->tl_arg0_cmd)) == 0)
2693 term->tl_title = NULL;
2694 // Empty corrupted data of winpty
2695 else if (STRNCMP(" - ", (char_u *)value->string, 4) == 0)
2696 term->tl_title = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002697#ifdef WIN3264
2698 else if (!enc_utf8 && enc_codepage > 0)
2699 {
2700 WCHAR *ret = NULL;
2701 int length = 0;
2702
2703 MultiByteToWideChar_alloc(CP_UTF8, 0,
2704 (char*)value->string, (int)STRLEN(value->string),
2705 &ret, &length);
2706 if (ret != NULL)
2707 {
2708 WideCharToMultiByte_alloc(enc_codepage, 0,
2709 ret, length, (char**)&term->tl_title,
2710 &length, 0, 0);
2711 vim_free(ret);
2712 }
2713 }
2714#endif
2715 else
2716 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002717 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002718 if (term == curbuf->b_term)
2719 maketitle();
2720 break;
2721
2722 case VTERM_PROP_CURSORVISIBLE:
2723 term->tl_cursor_visible = value->boolean;
2724 may_toggle_cursor(term);
2725 out_flush();
2726 break;
2727
2728 case VTERM_PROP_CURSORBLINK:
2729 term->tl_cursor_blink = value->boolean;
2730 may_set_cursor_props(term);
2731 break;
2732
2733 case VTERM_PROP_CURSORSHAPE:
2734 term->tl_cursor_shape = value->number;
2735 may_set_cursor_props(term);
2736 break;
2737
2738 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002739 cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002740 may_set_cursor_props(term);
2741 break;
2742
2743 case VTERM_PROP_ALTSCREEN:
2744 /* TODO: do anything else? */
2745 term->tl_using_altscreen = value->boolean;
2746 break;
2747
2748 default:
2749 break;
2750 }
2751 /* Always return 1, otherwise vterm doesn't store the value internally. */
2752 return 1;
2753}
2754
2755/*
2756 * The job running in the terminal resized the terminal.
2757 */
2758 static int
2759handle_resize(int rows, int cols, void *user)
2760{
2761 term_T *term = (term_T *)user;
2762 win_T *wp;
2763
2764 term->tl_rows = rows;
2765 term->tl_cols = cols;
2766 if (term->tl_vterm_size_changed)
2767 /* Size was set by vterm_set_size(), don't set the window size. */
2768 term->tl_vterm_size_changed = FALSE;
2769 else
2770 {
2771 FOR_ALL_WINDOWS(wp)
2772 {
2773 if (wp->w_buffer == term->tl_buffer)
2774 {
2775 win_setheight_win(rows, wp);
2776 win_setwidth_win(cols, wp);
2777 }
2778 }
2779 redraw_buf_later(term->tl_buffer, NOT_VALID);
2780 }
2781 return 1;
2782}
2783
2784/*
2785 * Handle a line that is pushed off the top of the screen.
2786 */
2787 static int
2788handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2789{
2790 term_T *term = (term_T *)user;
2791
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002792 /* First remove the lines that were appended before, the pushed line goes
2793 * above it. */
2794 cleanup_scrollback(term);
2795
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002796 /* If the number of lines that are stored goes over 'termscrollback' then
2797 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002798 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002799 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002800 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002801 int i;
2802
2803 curbuf = term->tl_buffer;
2804 for (i = 0; i < todo; ++i)
2805 {
2806 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2807 ml_delete(1, FALSE);
2808 }
2809 curbuf = curwin->w_buffer;
2810
2811 term->tl_scrollback.ga_len -= todo;
2812 mch_memmove(term->tl_scrollback.ga_data,
2813 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2814 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
Bram Moolenaar4d6cd292018-05-15 23:53:26 +02002815 term->tl_scrollback_scrolled -= todo;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002816 }
2817
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002818 if (ga_grow(&term->tl_scrollback, 1) == OK)
2819 {
2820 cellattr_T *p = NULL;
2821 int len = 0;
2822 int i;
2823 int c;
2824 int col;
2825 sb_line_T *line;
2826 garray_T ga;
2827 cellattr_T fill_attr = term->tl_default_color;
2828
2829 /* do not store empty cells at the end */
2830 for (i = 0; i < cols; ++i)
2831 if (cells[i].chars[0] != 0)
2832 len = i + 1;
2833 else
2834 cell2cellattr(&cells[i], &fill_attr);
2835
2836 ga_init2(&ga, 1, 100);
2837 if (len > 0)
2838 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2839 if (p != NULL)
2840 {
2841 for (col = 0; col < len; col += cells[col].width)
2842 {
2843 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2844 {
2845 ga.ga_len = 0;
2846 break;
2847 }
2848 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2849 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2850 (char_u *)ga.ga_data + ga.ga_len);
2851 cell2cellattr(&cells[col], &p[col]);
2852 }
2853 }
2854 if (ga_grow(&ga, 1) == FAIL)
2855 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2856 else
2857 {
2858 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2859 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2860 }
2861 ga_clear(&ga);
2862
2863 line = (sb_line_T *)term->tl_scrollback.ga_data
2864 + term->tl_scrollback.ga_len;
2865 line->sb_cols = len;
2866 line->sb_cells = p;
2867 line->sb_fill_attr = fill_attr;
2868 ++term->tl_scrollback.ga_len;
2869 ++term->tl_scrollback_scrolled;
2870 }
2871 return 0; /* ignored */
2872}
2873
2874static VTermScreenCallbacks screen_callbacks = {
2875 handle_damage, /* damage */
2876 handle_moverect, /* moverect */
2877 handle_movecursor, /* movecursor */
2878 handle_settermprop, /* settermprop */
2879 NULL, /* bell */
2880 handle_resize, /* resize */
2881 handle_pushline, /* sb_pushline */
2882 NULL /* sb_popline */
2883};
2884
2885/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002886 * Do the work after the channel of a terminal was closed.
2887 * Must be called only when updating_screen is FALSE.
2888 * Returns TRUE when a buffer was closed (list of terminals may have changed).
2889 */
2890 static int
2891term_after_channel_closed(term_T *term)
2892{
2893 /* Unless in Terminal-Normal mode: clear the vterm. */
2894 if (!term->tl_normal_mode)
2895 {
2896 int fnum = term->tl_buffer->b_fnum;
2897
2898 cleanup_vterm(term);
2899
2900 if (term->tl_finish == TL_FINISH_CLOSE)
2901 {
2902 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002903 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002904
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002905 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002906 ch_log(NULL, "terminal job finished, closing window");
2907 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002908 // Avoid closing the window if we temporarily use it.
2909 if (do_set_w_closing)
2910 curwin->w_closing = TRUE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002911 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02002912 if (do_set_w_closing)
2913 curwin->w_closing = FALSE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002914 aucmd_restbuf(&aco);
2915 return TRUE;
2916 }
2917 if (term->tl_finish == TL_FINISH_OPEN
2918 && term->tl_buffer->b_nwindows == 0)
2919 {
2920 char buf[50];
2921
2922 /* TODO: use term_opencmd */
2923 ch_log(NULL, "terminal job finished, opening window");
2924 vim_snprintf(buf, sizeof(buf),
2925 term->tl_opencmd == NULL
2926 ? "botright sbuf %d"
2927 : (char *)term->tl_opencmd, fnum);
2928 do_cmdline_cmd((char_u *)buf);
2929 }
2930 else
2931 ch_log(NULL, "terminal job finished");
2932 }
2933
2934 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2935 return FALSE;
2936}
2937
2938/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002939 * Called when a channel has been closed.
2940 * If this was a channel for a terminal window then finish it up.
2941 */
2942 void
2943term_channel_closed(channel_T *ch)
2944{
2945 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002946 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002947 int did_one = FALSE;
2948
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002949 for (term = first_term; term != NULL; term = next_term)
2950 {
2951 next_term = term->tl_next;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002952 if (term->tl_job == ch->ch_job)
2953 {
2954 term->tl_channel_closed = TRUE;
2955 did_one = TRUE;
2956
Bram Moolenaard23a8232018-02-10 18:45:26 +01002957 VIM_CLEAR(term->tl_title);
2958 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar402c8392018-05-06 22:01:42 +02002959#ifdef WIN3264
2960 if (term->tl_out_fd != NULL)
2961 {
2962 fclose(term->tl_out_fd);
2963 term->tl_out_fd = NULL;
2964 }
2965#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002966
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002967 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002968 {
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002969 /* Cannot open or close windows now. Can happen when
2970 * 'lazyredraw' is set. */
2971 term->tl_channel_recently_closed = TRUE;
2972 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002973 }
2974
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002975 if (term_after_channel_closed(term))
2976 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002977 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002978 }
2979
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002980 if (did_one)
2981 {
2982 redraw_statuslines();
2983
2984 /* Need to break out of vgetc(). */
2985 ins_char_typebuf(K_IGNORE);
2986 typebuf_was_filled = TRUE;
2987
2988 term = curbuf->b_term;
2989 if (term != NULL)
2990 {
2991 if (term->tl_job == ch->ch_job)
2992 maketitle();
2993 update_cursor(term, term->tl_cursor_visible);
2994 }
2995 }
2996}
2997
2998/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02002999 * To be called after resetting updating_screen: handle any terminal where the
3000 * channel was closed.
3001 */
3002 void
3003term_check_channel_closed_recently()
3004{
3005 term_T *term;
3006 term_T *next_term;
3007
3008 for (term = first_term; term != NULL; term = next_term)
3009 {
3010 next_term = term->tl_next;
3011 if (term->tl_channel_recently_closed)
3012 {
3013 term->tl_channel_recently_closed = FALSE;
3014 if (term_after_channel_closed(term))
3015 // start over, the list may have changed
3016 next_term = first_term;
3017 }
3018 }
3019}
3020
3021/*
Bram Moolenaar13568252018-03-16 20:46:58 +01003022 * Fill one screen line from a line of the terminal.
3023 * Advances "pos" to past the last column.
3024 */
3025 static void
3026term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
3027{
3028 int off = screen_get_current_line_off();
3029
3030 for (pos->col = 0; pos->col < max_col; )
3031 {
3032 VTermScreenCell cell;
3033 int c;
3034
3035 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
3036 vim_memset(&cell, 0, sizeof(cell));
3037
3038 c = cell.chars[0];
3039 if (c == NUL)
3040 {
3041 ScreenLines[off] = ' ';
3042 if (enc_utf8)
3043 ScreenLinesUC[off] = NUL;
3044 }
3045 else
3046 {
3047 if (enc_utf8)
3048 {
3049 int i;
3050
3051 /* composing chars */
3052 for (i = 0; i < Screen_mco
3053 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3054 {
3055 ScreenLinesC[i][off] = cell.chars[i + 1];
3056 if (cell.chars[i + 1] == 0)
3057 break;
3058 }
3059 if (c >= 0x80 || (Screen_mco > 0
3060 && ScreenLinesC[0][off] != 0))
3061 {
3062 ScreenLines[off] = ' ';
3063 ScreenLinesUC[off] = c;
3064 }
3065 else
3066 {
3067 ScreenLines[off] = c;
3068 ScreenLinesUC[off] = NUL;
3069 }
3070 }
3071#ifdef WIN3264
3072 else if (has_mbyte && c >= 0x80)
3073 {
3074 char_u mb[MB_MAXBYTES+1];
3075 WCHAR wc = c;
3076
3077 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3078 (char*)mb, 2, 0, 0) > 1)
3079 {
3080 ScreenLines[off] = mb[0];
3081 ScreenLines[off + 1] = mb[1];
3082 cell.width = mb_ptr2cells(mb);
3083 }
3084 else
3085 ScreenLines[off] = c;
3086 }
3087#endif
3088 else
3089 ScreenLines[off] = c;
3090 }
3091 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
3092
3093 ++pos->col;
3094 ++off;
3095 if (cell.width == 2)
3096 {
3097 if (enc_utf8)
3098 ScreenLinesUC[off] = NUL;
3099
3100 /* don't set the second byte to NUL for a DBCS encoding, it
3101 * has been set above */
3102 if (enc_utf8 || !has_mbyte)
3103 ScreenLines[off] = NUL;
3104
3105 ++pos->col;
3106 ++off;
3107 }
3108 }
3109}
3110
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003111#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003112 static void
3113update_system_term(term_T *term)
3114{
3115 VTermPos pos;
3116 VTermScreen *screen;
3117
3118 if (term->tl_vterm == NULL)
3119 return;
3120 screen = vterm_obtain_screen(term->tl_vterm);
3121
3122 /* Scroll up to make more room for terminal lines if needed. */
3123 while (term->tl_toprow > 0
3124 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3125 {
3126 int save_p_more = p_more;
3127
3128 p_more = FALSE;
3129 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003130 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003131 p_more = save_p_more;
3132 --term->tl_toprow;
3133 }
3134
3135 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3136 && pos.row < Rows; ++pos.row)
3137 {
3138 if (pos.row < term->tl_rows)
3139 {
3140 int max_col = MIN(Columns, term->tl_cols);
3141
3142 term_line2screenline(screen, &pos, max_col);
3143 }
3144 else
3145 pos.col = 0;
3146
3147 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
3148 }
3149
3150 term->tl_dirty_row_start = MAX_ROW;
3151 term->tl_dirty_row_end = 0;
3152 update_cursor(term, TRUE);
3153}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003154#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003155
3156/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003157 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3158 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003159 * Terminal-Normal mode.
3160 */
3161 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003162term_do_update_window(win_T *wp)
3163{
3164 term_T *term = wp->w_buffer->b_term;
3165
3166 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3167}
3168
3169/*
3170 * Called to update a window that contains an active terminal.
3171 */
3172 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003173term_update_window(win_T *wp)
3174{
3175 term_T *term = wp->w_buffer->b_term;
3176 VTerm *vterm;
3177 VTermScreen *screen;
3178 VTermState *state;
3179 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003180 int rows, cols;
3181 int newrows, newcols;
3182 int minsize;
3183 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003184
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003185 vterm = term->tl_vterm;
3186 screen = vterm_obtain_screen(vterm);
3187 state = vterm_obtain_state(vterm);
3188
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003189 /* We use NOT_VALID on a resize or scroll, redraw everything then. With
3190 * SOME_VALID only redraw what was marked dirty. */
3191 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003192 {
3193 term->tl_dirty_row_start = 0;
3194 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003195
3196 if (term->tl_postponed_scroll > 0
3197 && term->tl_postponed_scroll < term->tl_rows / 3)
3198 /* Scrolling is usually faster than redrawing, when there are only
3199 * a few lines to scroll. */
3200 term_scroll_up(term, 0, term->tl_postponed_scroll);
3201 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003202 }
3203
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003204 /*
3205 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003206 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003207 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003208 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003209
Bram Moolenaar498c2562018-04-15 23:45:15 +02003210 newrows = 99999;
3211 newcols = 99999;
3212 FOR_ALL_WINDOWS(twp)
3213 {
3214 /* When more than one window shows the same terminal, use the
3215 * smallest size. */
3216 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003217 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02003218 newrows = MIN(newrows, twp->w_height);
3219 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003220 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02003221 }
3222 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3223 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3224
3225 if (term->tl_rows != newrows || term->tl_cols != newcols)
3226 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003227 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003228 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003229 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003230 newrows);
3231 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003232
3233 // Updating the terminal size will cause the snapshot to be cleared.
3234 // When not in terminal_loop() we need to restore it.
3235 if (term != in_terminal_loop)
3236 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003237 }
3238
3239 /* The cursor may have been moved when resizing. */
3240 vterm_state_get_cursorpos(state, &pos);
3241 position_cursor(wp, &pos);
3242
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003243 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3244 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003245 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003246 if (pos.row < term->tl_rows)
3247 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003248 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003249
Bram Moolenaar13568252018-03-16 20:46:58 +01003250 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003251 }
3252 else
3253 pos.col = 0;
3254
Bram Moolenaarf118d482018-03-13 13:14:00 +01003255 screen_line(wp->w_winrow + pos.row
3256#ifdef FEAT_MENU
3257 + winbar_height(wp)
3258#endif
3259 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003260 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003261 term->tl_dirty_row_start = MAX_ROW;
3262 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003263}
3264
3265/*
3266 * Return TRUE if "wp" is a terminal window where the job has finished.
3267 */
3268 int
3269term_is_finished(buf_T *buf)
3270{
3271 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3272}
3273
3274/*
3275 * Return TRUE if "wp" is a terminal window where the job has finished or we
3276 * are in Terminal-Normal mode, thus we show the buffer contents.
3277 */
3278 int
3279term_show_buffer(buf_T *buf)
3280{
3281 term_T *term = buf->b_term;
3282
3283 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3284}
3285
3286/*
3287 * The current buffer is going to be changed. If there is terminal
3288 * highlighting remove it now.
3289 */
3290 void
3291term_change_in_curbuf(void)
3292{
3293 term_T *term = curbuf->b_term;
3294
3295 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3296 {
3297 free_scrollback(term);
3298 redraw_buf_later(term->tl_buffer, NOT_VALID);
3299
3300 /* The buffer is now like a normal buffer, it cannot be easily
3301 * abandoned when changed. */
3302 set_string_option_direct((char_u *)"buftype", -1,
3303 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3304 }
3305}
3306
3307/*
3308 * Get the screen attribute for a position in the buffer.
3309 * Use a negative "col" to get the filler background color.
3310 */
3311 int
3312term_get_attr(buf_T *buf, linenr_T lnum, int col)
3313{
3314 term_T *term = buf->b_term;
3315 sb_line_T *line;
3316 cellattr_T *cellattr;
3317
3318 if (lnum > term->tl_scrollback.ga_len)
3319 cellattr = &term->tl_default_color;
3320 else
3321 {
3322 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3323 if (col < 0 || col >= line->sb_cols)
3324 cellattr = &line->sb_fill_attr;
3325 else
3326 cellattr = line->sb_cells + col;
3327 }
3328 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3329}
3330
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003331/*
3332 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003333 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003334 */
3335 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003336cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003337{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003338 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003339}
3340
3341/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003342 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003343 */
3344 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003345init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003346{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003347 VTermColor *fg, *bg;
3348 int fgval, bgval;
3349 int id;
3350
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003351 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3352 term->tl_default_color.width = 1;
3353 fg = &term->tl_default_color.fg;
3354 bg = &term->tl_default_color.bg;
3355
3356 /* Vterm uses a default black background. Set it to white when
3357 * 'background' is "light". */
3358 if (*p_bg == 'l')
3359 {
3360 fgval = 0;
3361 bgval = 255;
3362 }
3363 else
3364 {
3365 fgval = 255;
3366 bgval = 0;
3367 }
3368 fg->red = fg->green = fg->blue = fgval;
3369 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003370 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003371
3372 /* The "Terminal" highlight group overrules the defaults. */
3373 id = syn_name2id((char_u *)"Terminal");
3374
Bram Moolenaar46359e12017-11-29 22:33:38 +01003375 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003376#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3377 if (0
3378# ifdef FEAT_GUI
3379 || gui.in_use
3380# endif
3381# ifdef FEAT_TERMGUICOLORS
3382 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003383# ifdef FEAT_VTP
3384 /* Finally get INVALCOLOR on this execution path */
3385 || (!p_tgc && t_colors >= 256)
3386# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003387# endif
3388 )
3389 {
3390 guicolor_T fg_rgb = INVALCOLOR;
3391 guicolor_T bg_rgb = INVALCOLOR;
3392
3393 if (id != 0)
3394 syn_id2colors(id, &fg_rgb, &bg_rgb);
3395
3396# ifdef FEAT_GUI
3397 if (gui.in_use)
3398 {
3399 if (fg_rgb == INVALCOLOR)
3400 fg_rgb = gui.norm_pixel;
3401 if (bg_rgb == INVALCOLOR)
3402 bg_rgb = gui.back_pixel;
3403 }
3404# ifdef FEAT_TERMGUICOLORS
3405 else
3406# endif
3407# endif
3408# ifdef FEAT_TERMGUICOLORS
3409 {
3410 if (fg_rgb == INVALCOLOR)
3411 fg_rgb = cterm_normal_fg_gui_color;
3412 if (bg_rgb == INVALCOLOR)
3413 bg_rgb = cterm_normal_bg_gui_color;
3414 }
3415# endif
3416 if (fg_rgb != INVALCOLOR)
3417 {
3418 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3419
3420 fg->red = (unsigned)(rgb >> 16);
3421 fg->green = (unsigned)(rgb >> 8) & 255;
3422 fg->blue = (unsigned)rgb & 255;
3423 }
3424 if (bg_rgb != INVALCOLOR)
3425 {
3426 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3427
3428 bg->red = (unsigned)(rgb >> 16);
3429 bg->green = (unsigned)(rgb >> 8) & 255;
3430 bg->blue = (unsigned)rgb & 255;
3431 }
3432 }
3433 else
3434#endif
3435 if (id != 0 && t_colors >= 16)
3436 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003437 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003438 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003439 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003440 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003441 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003442 else
3443 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003444#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003445 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003446#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003447
3448 /* In an MS-Windows console we know the normal colors. */
3449 if (cterm_normal_fg_color > 0)
3450 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003451 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003452# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003453 tmp = fg->red;
3454 fg->red = fg->blue;
3455 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003456# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003457 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003458# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003459 else
3460 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003461# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003462
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003463 if (cterm_normal_bg_color > 0)
3464 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003465 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003466# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003467 tmp = bg->red;
3468 bg->red = bg->blue;
3469 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003470# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003471 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003472# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003473 else
3474 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003475# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003476 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003477}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003478
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003479#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3480/*
3481 * Set the 16 ANSI colors from array of RGB values
3482 */
3483 static void
3484set_vterm_palette(VTerm *vterm, long_u *rgb)
3485{
3486 int index = 0;
3487 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003488
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003489 for (; index < 16; index++)
3490 {
3491 VTermColor color;
3492 color.red = (unsigned)(rgb[index] >> 16);
3493 color.green = (unsigned)(rgb[index] >> 8) & 255;
3494 color.blue = (unsigned)rgb[index] & 255;
3495 vterm_state_set_palette_color(state, index, &color);
3496 }
3497}
3498
3499/*
3500 * Set the ANSI color palette from a list of colors
3501 */
3502 static int
3503set_ansi_colors_list(VTerm *vterm, list_T *list)
3504{
3505 int n = 0;
3506 long_u rgb[16];
3507 listitem_T *li = list->lv_first;
3508
3509 for (; li != NULL && n < 16; li = li->li_next, n++)
3510 {
3511 char_u *color_name;
3512 guicolor_T guicolor;
3513
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003514 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003515 if (color_name == NULL)
3516 return FAIL;
3517
3518 guicolor = GUI_GET_COLOR(color_name);
3519 if (guicolor == INVALCOLOR)
3520 return FAIL;
3521
3522 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3523 }
3524
3525 if (n != 16 || li != NULL)
3526 return FAIL;
3527
3528 set_vterm_palette(vterm, rgb);
3529
3530 return OK;
3531}
3532
3533/*
3534 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3535 */
3536 static void
3537init_vterm_ansi_colors(VTerm *vterm)
3538{
3539 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3540
3541 if (var != NULL
3542 && (var->di_tv.v_type != VAR_LIST
3543 || var->di_tv.vval.v_list == NULL
3544 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003545 semsg(_(e_invarg2), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003546}
3547#endif
3548
Bram Moolenaar52acb112018-03-18 19:20:22 +01003549/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003550 * Handles a "drop" command from the job in the terminal.
3551 * "item" is the file name, "item->li_next" may have options.
3552 */
3553 static void
3554handle_drop_command(listitem_T *item)
3555{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003556 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003557 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003558 int bufnr;
3559 win_T *wp;
3560 tabpage_T *tp;
3561 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003562 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003563
3564 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3565 FOR_ALL_TAB_WINDOWS(tp, wp)
3566 {
3567 if (wp->w_buffer->b_fnum == bufnr)
3568 {
3569 /* buffer is in a window already, go there */
3570 goto_tabpage_win(tp, wp);
3571 return;
3572 }
3573 }
3574
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003575 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003576
3577 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3578 && opt_item->li_tv.vval.v_dict != NULL)
3579 {
3580 dict_T *dict = opt_item->li_tv.vval.v_dict;
3581 char_u *p;
3582
Bram Moolenaar8f667172018-12-14 15:38:31 +01003583 p = dict_get_string(dict, (char_u *)"ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003584 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003585 p = dict_get_string(dict, (char_u *)"fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003586 if (p != NULL)
3587 {
3588 if (check_ff_value(p) == FAIL)
3589 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3590 else
3591 ea.force_ff = *p;
3592 }
Bram Moolenaar8f667172018-12-14 15:38:31 +01003593 p = dict_get_string(dict, (char_u *)"enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003594 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003595 p = dict_get_string(dict, (char_u *)"encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003596 if (p != NULL)
3597 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003598 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003599 if (ea.cmd != NULL)
3600 {
3601 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3602 ea.force_enc = 11;
3603 tofree = ea.cmd;
3604 }
3605 }
3606
Bram Moolenaar8f667172018-12-14 15:38:31 +01003607 p = dict_get_string(dict, (char_u *)"bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003608 if (p != NULL)
3609 get_bad_opt(p, &ea);
3610
3611 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3612 ea.force_bin = FORCE_BIN;
3613 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3614 ea.force_bin = FORCE_BIN;
3615 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3616 ea.force_bin = FORCE_NOBIN;
3617 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3618 ea.force_bin = FORCE_NOBIN;
3619 }
3620
3621 /* open in new window, like ":split fname" */
3622 if (ea.cmd == NULL)
3623 ea.cmd = (char_u *)"split";
3624 ea.arg = fname;
3625 ea.cmdidx = CMD_split;
3626 ex_splitview(&ea);
3627
3628 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003629}
3630
3631/*
3632 * Handles a function call from the job running in a terminal.
3633 * "item" is the function name, "item->li_next" has the arguments.
3634 */
3635 static void
3636handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3637{
3638 char_u *func;
3639 typval_T argvars[2];
3640 typval_T rettv;
3641 int doesrange;
3642
3643 if (item->li_next == NULL)
3644 {
3645 ch_log(channel, "Missing function arguments for call");
3646 return;
3647 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003648 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003649
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003650 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003651 {
3652 ch_log(channel, "Invalid function name: %s", func);
3653 return;
3654 }
3655
3656 argvars[0].v_type = VAR_NUMBER;
3657 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3658 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003659 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003660 2, argvars, /* argv_func */ NULL,
3661 /* firstline */ 1, /* lastline */ 1,
3662 &doesrange, /* evaluate */ TRUE,
3663 /* partial */ NULL, /* selfdict */ NULL) == OK)
3664 {
3665 clear_tv(&rettv);
3666 ch_log(channel, "Function %s called", func);
3667 }
3668 else
3669 ch_log(channel, "Calling function %s failed", func);
3670}
3671
3672/*
3673 * Called by libvterm when it cannot recognize an OSC sequence.
3674 * We recognize a terminal API command.
3675 */
3676 static int
3677parse_osc(const char *command, size_t cmdlen, void *user)
3678{
3679 term_T *term = (term_T *)user;
3680 js_read_T reader;
3681 typval_T tv;
3682 channel_T *channel = term->tl_job == NULL ? NULL
3683 : term->tl_job->jv_channel;
3684
3685 /* We recognize only OSC 5 1 ; {command} */
3686 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3687 return 0; /* not handled */
3688
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003689 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003690 if (reader.js_buf == NULL)
3691 return 1;
3692 reader.js_fill = NULL;
3693 reader.js_used = 0;
3694 if (json_decode(&reader, &tv, 0) == OK
3695 && tv.v_type == VAR_LIST
3696 && tv.vval.v_list != NULL)
3697 {
3698 listitem_T *item = tv.vval.v_list->lv_first;
3699
3700 if (item == NULL)
3701 ch_log(channel, "Missing command");
3702 else
3703 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003704 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003705
Bram Moolenaara997b452018-04-17 23:24:06 +02003706 /* Make sure an invoked command doesn't delete the buffer (and the
3707 * terminal) under our fingers. */
3708 ++term->tl_buffer->b_locked;
3709
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003710 item = item->li_next;
3711 if (item == NULL)
3712 ch_log(channel, "Missing argument for %s", cmd);
3713 else if (STRCMP(cmd, "drop") == 0)
3714 handle_drop_command(item);
3715 else if (STRCMP(cmd, "call") == 0)
3716 handle_call_command(term, channel, item);
3717 else
3718 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003719 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003720 }
3721 }
3722 else
3723 ch_log(channel, "Invalid JSON received");
3724
3725 vim_free(reader.js_buf);
3726 clear_tv(&tv);
3727 return 1;
3728}
3729
3730static VTermParserCallbacks parser_fallbacks = {
3731 NULL, /* text */
3732 NULL, /* control */
3733 NULL, /* escape */
3734 NULL, /* csi */
3735 parse_osc, /* osc */
3736 NULL, /* dcs */
3737 NULL /* resize */
3738};
3739
3740/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003741 * Use Vim's allocation functions for vterm so profiling works.
3742 */
3743 static void *
3744vterm_malloc(size_t size, void *data UNUSED)
3745{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003746 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003747}
3748
3749 static void
3750vterm_memfree(void *ptr, void *data UNUSED)
3751{
3752 vim_free(ptr);
3753}
3754
3755static VTermAllocatorFunctions vterm_allocator = {
3756 &vterm_malloc,
3757 &vterm_memfree
3758};
3759
3760/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003761 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003762 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01003763 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003764 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01003765create_vterm(term_T *term, int rows, int cols)
3766{
3767 VTerm *vterm;
3768 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003769 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003770 VTermValue value;
3771
Bram Moolenaar756ef112018-04-10 12:04:27 +02003772 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003773 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003774 if (vterm == NULL)
3775 return FAIL;
3776
3777 // Allocate screen and state here, so we can bail out if that fails.
3778 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003779 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003780 if (state == NULL || screen == NULL)
3781 {
3782 vterm_free(vterm);
3783 return FAIL;
3784 }
3785
Bram Moolenaar52acb112018-03-18 19:20:22 +01003786 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3787 /* TODO: depends on 'encoding'. */
3788 vterm_set_utf8(vterm, 1);
3789
3790 init_default_colors(term);
3791
3792 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003793 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01003794 &term->tl_default_color.fg,
3795 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003796
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003797 if (t_colors >= 16)
3798 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3799
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003800 /* Required to initialize most things. */
3801 vterm_screen_reset(screen, 1 /* hard */);
3802
3803 /* Allow using alternate screen. */
3804 vterm_screen_enable_altscreen(screen, 1);
3805
3806 /* For unix do not use a blinking cursor. In an xterm this causes the
3807 * cursor to blink if it's blinking in the xterm.
3808 * For Windows we respect the system wide setting. */
3809#ifdef WIN3264
3810 if (GetCaretBlinkTime() == INFINITE)
3811 value.boolean = 0;
3812 else
3813 value.boolean = 1;
3814#else
3815 value.boolean = 0;
3816#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003817 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3818 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003819
3820 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003821}
3822
3823/*
3824 * Return the text to show for the buffer name and status.
3825 */
3826 char_u *
3827term_get_status_text(term_T *term)
3828{
3829 if (term->tl_status_text == NULL)
3830 {
3831 char_u *txt;
3832 size_t len;
3833
3834 if (term->tl_normal_mode)
3835 {
3836 if (term_job_running(term))
3837 txt = (char_u *)_("Terminal");
3838 else
3839 txt = (char_u *)_("Terminal-finished");
3840 }
3841 else if (term->tl_title != NULL)
3842 txt = term->tl_title;
3843 else if (term_none_open(term))
3844 txt = (char_u *)_("active");
3845 else if (term_job_running(term))
3846 txt = (char_u *)_("running");
3847 else
3848 txt = (char_u *)_("finished");
3849 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3850 term->tl_status_text = alloc((int)len);
3851 if (term->tl_status_text != NULL)
3852 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3853 term->tl_buffer->b_fname, txt);
3854 }
3855 return term->tl_status_text;
3856}
3857
3858/*
3859 * Mark references in jobs of terminals.
3860 */
3861 int
3862set_ref_in_term(int copyID)
3863{
3864 int abort = FALSE;
3865 term_T *term;
3866 typval_T tv;
3867
3868 for (term = first_term; term != NULL; term = term->tl_next)
3869 if (term->tl_job != NULL)
3870 {
3871 tv.v_type = VAR_JOB;
3872 tv.vval.v_job = term->tl_job;
3873 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3874 }
3875 return abort;
3876}
3877
3878/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003879 * Cache "Terminal" highlight group colors.
3880 */
3881 void
3882set_terminal_default_colors(int cterm_fg, int cterm_bg)
3883{
3884 term_default_cterm_fg = cterm_fg - 1;
3885 term_default_cterm_bg = cterm_bg - 1;
3886}
3887
3888/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003889 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003890 * Returns NULL when the buffer is not for a terminal window and logs a message
3891 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003892 */
3893 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003894term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003895{
3896 buf_T *buf;
3897
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003898 (void)tv_get_number(&argvars[0]); /* issue errmsg if type error */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003899 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01003900 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003901 --emsg_off;
3902 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003903 {
3904 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003905 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003906 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003907 return buf;
3908}
3909
Bram Moolenaard96ff162018-02-18 22:13:29 +01003910 static int
3911same_color(VTermColor *a, VTermColor *b)
3912{
3913 return a->red == b->red
3914 && a->green == b->green
3915 && a->blue == b->blue
3916 && a->ansi_index == b->ansi_index;
3917}
3918
3919 static void
3920dump_term_color(FILE *fd, VTermColor *color)
3921{
3922 fprintf(fd, "%02x%02x%02x%d",
3923 (int)color->red, (int)color->green, (int)color->blue,
3924 (int)color->ansi_index);
3925}
3926
3927/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003928 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003929 *
3930 * Each screen cell in full is:
3931 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3932 * {characters} is a space for an empty cell
3933 * For a double-width character "+" is changed to "*" and the next cell is
3934 * skipped.
3935 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3936 * when "&" use the same as the previous cell.
3937 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3938 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3939 * {color-idx} is a number from 0 to 255
3940 *
3941 * Screen cell with same width, attributes and color as the previous one:
3942 * |{characters}
3943 *
3944 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3945 *
3946 * Repeating the previous screen cell:
3947 * @{count}
3948 */
3949 void
3950f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3951{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003952 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003953 term_T *term;
3954 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003955 int max_height = 0;
3956 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003957 stat_T st;
3958 FILE *fd;
3959 VTermPos pos;
3960 VTermScreen *screen;
3961 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003962 VTermState *state;
3963 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003964
3965 if (check_restricted() || check_secure())
3966 return;
3967 if (buf == NULL)
3968 return;
3969 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02003970 if (term->tl_vterm == NULL)
3971 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003972 emsg(_("E958: Job already finished"));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02003973 return;
3974 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003975
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003976 if (argvars[2].v_type != VAR_UNKNOWN)
3977 {
3978 dict_T *d;
3979
3980 if (argvars[2].v_type != VAR_DICT)
3981 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003982 emsg(_(e_dictreq));
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003983 return;
3984 }
3985 d = argvars[2].vval.v_dict;
3986 if (d != NULL)
3987 {
Bram Moolenaar8f667172018-12-14 15:38:31 +01003988 max_height = dict_get_number(d, (char_u *)"rows");
3989 max_width = dict_get_number(d, (char_u *)"columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003990 }
3991 }
3992
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003993 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003994 if (fname == NULL)
3995 return;
3996 if (mch_stat((char *)fname, &st) >= 0)
3997 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003998 semsg(_("E953: File exists: %s"), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003999 return;
4000 }
4001
Bram Moolenaard96ff162018-02-18 22:13:29 +01004002 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
4003 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004004 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004005 return;
4006 }
4007
4008 vim_memset(&prev_cell, 0, sizeof(prev_cell));
4009
4010 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004011 state = vterm_obtain_state(term->tl_vterm);
4012 vterm_state_get_cursorpos(state, &cursor_pos);
4013
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004014 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
4015 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004016 {
4017 int repeat = 0;
4018
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004019 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
4020 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004021 {
4022 VTermScreenCell cell;
4023 int same_attr;
4024 int same_chars = TRUE;
4025 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004026 int is_cursor_pos = (pos.col == cursor_pos.col
4027 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004028
4029 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4030 vim_memset(&cell, 0, sizeof(cell));
4031
4032 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4033 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01004034 int c = cell.chars[i];
4035 int pc = prev_cell.chars[i];
4036
4037 /* For the first character NUL is the same as space. */
4038 if (i == 0)
4039 {
4040 c = (c == NUL) ? ' ' : c;
4041 pc = (pc == NUL) ? ' ' : pc;
4042 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004043 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004044 same_chars = FALSE;
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004045 if (c == NUL || pc == NUL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004046 break;
4047 }
4048 same_attr = vtermAttr2hl(cell.attrs)
4049 == vtermAttr2hl(prev_cell.attrs)
4050 && same_color(&cell.fg, &prev_cell.fg)
4051 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004052 if (same_chars && cell.width == prev_cell.width && same_attr
4053 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004054 {
4055 ++repeat;
4056 }
4057 else
4058 {
4059 if (repeat > 0)
4060 {
4061 fprintf(fd, "@%d", repeat);
4062 repeat = 0;
4063 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004064 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004065
4066 if (cell.chars[0] == NUL)
4067 fputs(" ", fd);
4068 else
4069 {
4070 char_u charbuf[10];
4071 int len;
4072
4073 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
4074 && cell.chars[i] != NUL; ++i)
4075 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02004076 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004077 fwrite(charbuf, len, 1, fd);
4078 }
4079 }
4080
4081 /* When only the characters differ we don't write anything, the
4082 * following "|", "@" or NL will indicate using the same
4083 * attributes. */
4084 if (cell.width != prev_cell.width || !same_attr)
4085 {
4086 if (cell.width == 2)
4087 {
4088 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004089 }
4090 else
4091 fputs("+", fd);
4092
4093 if (same_attr)
4094 {
4095 fputs("&", fd);
4096 }
4097 else
4098 {
4099 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
4100 if (same_color(&cell.fg, &prev_cell.fg))
4101 fputs("&", fd);
4102 else
4103 {
4104 fputs("#", fd);
4105 dump_term_color(fd, &cell.fg);
4106 }
4107 if (same_color(&cell.bg, &prev_cell.bg))
4108 fputs("&", fd);
4109 else
4110 {
4111 fputs("#", fd);
4112 dump_term_color(fd, &cell.bg);
4113 }
4114 }
4115 }
4116
4117 prev_cell = cell;
4118 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004119
4120 if (cell.width == 2)
4121 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004122 }
4123 if (repeat > 0)
4124 fprintf(fd, "@%d", repeat);
4125 fputs("\n", fd);
4126 }
4127
4128 fclose(fd);
4129}
4130
4131/*
4132 * Called when a dump is corrupted. Put a breakpoint here when debugging.
4133 */
4134 static void
4135dump_is_corrupt(garray_T *gap)
4136{
4137 ga_concat(gap, (char_u *)"CORRUPT");
4138}
4139
4140 static void
4141append_cell(garray_T *gap, cellattr_T *cell)
4142{
4143 if (ga_grow(gap, 1) == OK)
4144 {
4145 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4146 ++gap->ga_len;
4147 }
4148}
4149
4150/*
4151 * Read the dump file from "fd" and append lines to the current buffer.
4152 * Return the cell width of the longest line.
4153 */
4154 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01004155read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004156{
4157 int c;
4158 garray_T ga_text;
4159 garray_T ga_cell;
4160 char_u *prev_char = NULL;
4161 int attr = 0;
4162 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004163 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004164 term_T *term = curbuf->b_term;
4165 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004166 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004167
4168 ga_init2(&ga_text, 1, 90);
4169 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4170 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004171 vim_memset(&empty_cell, 0, sizeof(empty_cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01004172 cursor_pos->row = -1;
4173 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004174
4175 c = fgetc(fd);
4176 for (;;)
4177 {
4178 if (c == EOF)
4179 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02004180 if (c == '\r')
4181 {
4182 // DOS line endings? Ignore.
4183 c = fgetc(fd);
4184 }
4185 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004186 {
4187 /* End of a line: append it to the buffer. */
4188 if (ga_text.ga_data == NULL)
4189 dump_is_corrupt(&ga_text);
4190 if (ga_grow(&term->tl_scrollback, 1) == OK)
4191 {
4192 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
4193 + term->tl_scrollback.ga_len;
4194
4195 if (max_cells < ga_cell.ga_len)
4196 max_cells = ga_cell.ga_len;
4197 line->sb_cols = ga_cell.ga_len;
4198 line->sb_cells = ga_cell.ga_data;
4199 line->sb_fill_attr = term->tl_default_color;
4200 ++term->tl_scrollback.ga_len;
4201 ga_init(&ga_cell);
4202
4203 ga_append(&ga_text, NUL);
4204 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4205 ga_text.ga_len, FALSE);
4206 }
4207 else
4208 ga_clear(&ga_cell);
4209 ga_text.ga_len = 0;
4210
4211 c = fgetc(fd);
4212 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004213 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004214 {
4215 int prev_len = ga_text.ga_len;
4216
Bram Moolenaar9271d052018-02-25 21:39:46 +01004217 if (c == '>')
4218 {
4219 if (cursor_pos->row != -1)
4220 dump_is_corrupt(&ga_text); /* duplicate cursor */
4221 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4222 cursor_pos->col = ga_cell.ga_len;
4223 }
4224
Bram Moolenaard96ff162018-02-18 22:13:29 +01004225 /* normal character(s) followed by "+", "*", "|", "@" or NL */
4226 c = fgetc(fd);
4227 if (c != EOF)
4228 ga_append(&ga_text, c);
4229 for (;;)
4230 {
4231 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004232 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004233 || c == EOF || c == '\n')
4234 break;
4235 ga_append(&ga_text, c);
4236 }
4237
4238 /* save the character for repeating it */
4239 vim_free(prev_char);
4240 if (ga_text.ga_data != NULL)
4241 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4242 ga_text.ga_len - prev_len);
4243
Bram Moolenaar9271d052018-02-25 21:39:46 +01004244 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004245 {
4246 /* use all attributes from previous cell */
4247 }
4248 else if (c == '+' || c == '*')
4249 {
4250 int is_bg;
4251
4252 cell.width = c == '+' ? 1 : 2;
4253
4254 c = fgetc(fd);
4255 if (c == '&')
4256 {
4257 /* use same attr as previous cell */
4258 c = fgetc(fd);
4259 }
4260 else if (isdigit(c))
4261 {
4262 /* get the decimal attribute */
4263 attr = 0;
4264 while (isdigit(c))
4265 {
4266 attr = attr * 10 + (c - '0');
4267 c = fgetc(fd);
4268 }
4269 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004270
4271 /* is_bg == 0: fg, is_bg == 1: bg */
4272 for (is_bg = 0; is_bg <= 1; ++is_bg)
4273 {
4274 if (c == '&')
4275 {
4276 /* use same color as previous cell */
4277 c = fgetc(fd);
4278 }
4279 else if (c == '#')
4280 {
4281 int red, green, blue, index = 0;
4282
4283 c = fgetc(fd);
4284 red = hex2nr(c);
4285 c = fgetc(fd);
4286 red = (red << 4) + hex2nr(c);
4287 c = fgetc(fd);
4288 green = hex2nr(c);
4289 c = fgetc(fd);
4290 green = (green << 4) + hex2nr(c);
4291 c = fgetc(fd);
4292 blue = hex2nr(c);
4293 c = fgetc(fd);
4294 blue = (blue << 4) + hex2nr(c);
4295 c = fgetc(fd);
4296 if (!isdigit(c))
4297 dump_is_corrupt(&ga_text);
4298 while (isdigit(c))
4299 {
4300 index = index * 10 + (c - '0');
4301 c = fgetc(fd);
4302 }
4303
4304 if (is_bg)
4305 {
4306 cell.bg.red = red;
4307 cell.bg.green = green;
4308 cell.bg.blue = blue;
4309 cell.bg.ansi_index = index;
4310 }
4311 else
4312 {
4313 cell.fg.red = red;
4314 cell.fg.green = green;
4315 cell.fg.blue = blue;
4316 cell.fg.ansi_index = index;
4317 }
4318 }
4319 else
4320 dump_is_corrupt(&ga_text);
4321 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004322 }
4323 else
4324 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004325 }
4326 else
4327 dump_is_corrupt(&ga_text);
4328
4329 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004330 if (cell.width == 2)
4331 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004332 }
4333 else if (c == '@')
4334 {
4335 if (prev_char == NULL)
4336 dump_is_corrupt(&ga_text);
4337 else
4338 {
4339 int count = 0;
4340
4341 /* repeat previous character, get the count */
4342 for (;;)
4343 {
4344 c = fgetc(fd);
4345 if (!isdigit(c))
4346 break;
4347 count = count * 10 + (c - '0');
4348 }
4349
4350 while (count-- > 0)
4351 {
4352 ga_concat(&ga_text, prev_char);
4353 append_cell(&ga_cell, &cell);
4354 }
4355 }
4356 }
4357 else
4358 {
4359 dump_is_corrupt(&ga_text);
4360 c = fgetc(fd);
4361 }
4362 }
4363
4364 if (ga_text.ga_len > 0)
4365 {
4366 /* trailing characters after last NL */
4367 dump_is_corrupt(&ga_text);
4368 ga_append(&ga_text, NUL);
4369 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4370 ga_text.ga_len, FALSE);
4371 }
4372
4373 ga_clear(&ga_text);
4374 vim_free(prev_char);
4375
4376 return max_cells;
4377}
4378
4379/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004380 * Return an allocated string with at least "text_width" "=" characters and
4381 * "fname" inserted in the middle.
4382 */
4383 static char_u *
4384get_separator(int text_width, char_u *fname)
4385{
4386 int width = MAX(text_width, curwin->w_width);
4387 char_u *textline;
4388 int fname_size;
4389 char_u *p = fname;
4390 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004391 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004392
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004393 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004394 if (textline == NULL)
4395 return NULL;
4396
4397 fname_size = vim_strsize(fname);
4398 if (fname_size < width - 8)
4399 {
4400 /* enough room, don't use the full window width */
4401 width = MAX(text_width, fname_size + 8);
4402 }
4403 else if (fname_size > width - 8)
4404 {
4405 /* full name doesn't fit, use only the tail */
4406 p = gettail(fname);
4407 fname_size = vim_strsize(p);
4408 }
4409 /* skip characters until the name fits */
4410 while (fname_size > width - 8)
4411 {
4412 p += (*mb_ptr2len)(p);
4413 fname_size = vim_strsize(p);
4414 }
4415
4416 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4417 textline[i] = '=';
4418 textline[i++] = ' ';
4419
4420 STRCPY(textline + i, p);
4421 off = STRLEN(textline);
4422 textline[off] = ' ';
4423 for (i = 1; i < (width - fname_size) / 2; ++i)
4424 textline[off + i] = '=';
4425 textline[off + i] = NUL;
4426
4427 return textline;
4428}
4429
4430/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004431 * Common for "term_dumpdiff()" and "term_dumpload()".
4432 */
4433 static void
4434term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4435{
4436 jobopt_T opt;
4437 buf_T *buf;
4438 char_u buf1[NUMBUFLEN];
4439 char_u buf2[NUMBUFLEN];
4440 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004441 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004442 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004443 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004444 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004445 char_u *textline = NULL;
4446
4447 /* First open the files. If this fails bail out. */
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004448 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004449 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004450 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004451 if (fname1 == NULL || (do_diff && fname2 == NULL))
4452 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004453 emsg(_(e_invarg));
Bram Moolenaard96ff162018-02-18 22:13:29 +01004454 return;
4455 }
4456 fd1 = mch_fopen((char *)fname1, READBIN);
4457 if (fd1 == NULL)
4458 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004459 semsg(_(e_notread), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004460 return;
4461 }
4462 if (do_diff)
4463 {
4464 fd2 = mch_fopen((char *)fname2, READBIN);
4465 if (fd2 == NULL)
4466 {
4467 fclose(fd1);
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004468 semsg(_(e_notread), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004469 return;
4470 }
4471 }
4472
4473 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004474 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4475 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4476 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4477 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4478 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004479
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004480 if (opt.jo_term_name == NULL)
4481 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004482 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004483
Bram Moolenaarb571c632018-03-21 22:27:59 +01004484 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004485 if (fname_tofree != NULL)
4486 {
4487 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4488 opt.jo_term_name = fname_tofree;
4489 }
4490 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004491
Bram Moolenaar13568252018-03-16 20:46:58 +01004492 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004493 if (buf != NULL && buf->b_term != NULL)
4494 {
4495 int i;
4496 linenr_T bot_lnum;
4497 linenr_T lnum;
4498 term_T *term = buf->b_term;
4499 int width;
4500 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004501 VTermPos cursor_pos1;
4502 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004503
Bram Moolenaar52acb112018-03-18 19:20:22 +01004504 init_default_colors(term);
4505
Bram Moolenaard96ff162018-02-18 22:13:29 +01004506 rettv->vval.v_number = buf->b_fnum;
4507
4508 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004509 width = read_dump_file(fd1, &cursor_pos1);
4510
4511 /* position the cursor */
4512 if (cursor_pos1.row >= 0)
4513 {
4514 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4515 coladvance(cursor_pos1.col);
4516 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004517
4518 /* Delete the empty line that was in the empty buffer. */
4519 ml_delete(1, FALSE);
4520
4521 /* For term_dumpload() we are done here. */
4522 if (!do_diff)
4523 goto theend;
4524
4525 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4526
Bram Moolenaar4a696342018-04-05 18:45:26 +02004527 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004528 if (textline == NULL)
4529 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004530 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4531 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4532 vim_free(textline);
4533
4534 textline = get_separator(width, fname2);
4535 if (textline == NULL)
4536 goto theend;
4537 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4538 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004539 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004540
4541 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004542 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004543 if (width2 > width)
4544 {
4545 vim_free(textline);
4546 textline = alloc(width2 + 1);
4547 if (textline == NULL)
4548 goto theend;
4549 width = width2;
4550 textline[width] = NUL;
4551 }
4552 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4553
4554 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4555 {
4556 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4557 {
4558 /* bottom part has fewer rows, fill with "-" */
4559 for (i = 0; i < width; ++i)
4560 textline[i] = '-';
4561 }
4562 else
4563 {
4564 char_u *line1;
4565 char_u *line2;
4566 char_u *p1;
4567 char_u *p2;
4568 int col;
4569 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4570 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4571 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4572 ->sb_cells;
4573
4574 /* Make a copy, getting the second line will invalidate it. */
4575 line1 = vim_strsave(ml_get(lnum));
4576 if (line1 == NULL)
4577 break;
4578 p1 = line1;
4579
4580 line2 = ml_get(lnum + bot_lnum);
4581 p2 = line2;
4582 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4583 {
4584 int len1 = utfc_ptr2len(p1);
4585 int len2 = utfc_ptr2len(p2);
4586
4587 textline[col] = ' ';
4588 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004589 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004590 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004591 else if (lnum == cursor_pos1.row + 1
4592 && col == cursor_pos1.col
4593 && (cursor_pos1.row != cursor_pos2.row
4594 || cursor_pos1.col != cursor_pos2.col))
4595 /* cursor in first but not in second */
4596 textline[col] = '>';
4597 else if (lnum == cursor_pos2.row + 1
4598 && col == cursor_pos2.col
4599 && (cursor_pos1.row != cursor_pos2.row
4600 || cursor_pos1.col != cursor_pos2.col))
4601 /* cursor in second but not in first */
4602 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004603 else if (cellattr1 != NULL && cellattr2 != NULL)
4604 {
4605 if ((cellattr1 + col)->width
4606 != (cellattr2 + col)->width)
4607 textline[col] = 'w';
4608 else if (!same_color(&(cellattr1 + col)->fg,
4609 &(cellattr2 + col)->fg))
4610 textline[col] = 'f';
4611 else if (!same_color(&(cellattr1 + col)->bg,
4612 &(cellattr2 + col)->bg))
4613 textline[col] = 'b';
4614 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4615 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4616 textline[col] = 'a';
4617 }
4618 p1 += len1;
4619 p2 += len2;
4620 /* TODO: handle different width */
4621 }
4622 vim_free(line1);
4623
4624 while (col < width)
4625 {
4626 if (*p1 == NUL && *p2 == NUL)
4627 textline[col] = '?';
4628 else if (*p1 == NUL)
4629 {
4630 textline[col] = '+';
4631 p2 += utfc_ptr2len(p2);
4632 }
4633 else
4634 {
4635 textline[col] = '-';
4636 p1 += utfc_ptr2len(p1);
4637 }
4638 ++col;
4639 }
4640 }
4641 if (add_empty_scrollback(term, &term->tl_default_color,
4642 term->tl_top_diff_rows) == OK)
4643 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4644 ++bot_lnum;
4645 }
4646
4647 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4648 {
4649 /* bottom part has more rows, fill with "+" */
4650 for (i = 0; i < width; ++i)
4651 textline[i] = '+';
4652 if (add_empty_scrollback(term, &term->tl_default_color,
4653 term->tl_top_diff_rows) == OK)
4654 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4655 ++lnum;
4656 ++bot_lnum;
4657 }
4658
4659 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004660
4661 /* looks better without wrapping */
4662 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004663 }
4664
4665theend:
4666 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004667 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004668 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004669 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004670 fclose(fd2);
4671}
4672
4673/*
4674 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4675 * bottom files.
4676 * Return FAIL when this is not possible.
4677 */
4678 int
4679term_swap_diff()
4680{
4681 term_T *term = curbuf->b_term;
4682 linenr_T line_count;
4683 linenr_T top_rows;
4684 linenr_T bot_rows;
4685 linenr_T bot_start;
4686 linenr_T lnum;
4687 char_u *p;
4688 sb_line_T *sb_line;
4689
4690 if (term == NULL
4691 || !term_is_finished(curbuf)
4692 || term->tl_top_diff_rows == 0
4693 || term->tl_scrollback.ga_len == 0)
4694 return FAIL;
4695
4696 line_count = curbuf->b_ml.ml_line_count;
4697 top_rows = term->tl_top_diff_rows;
4698 bot_rows = term->tl_bot_diff_rows;
4699 bot_start = line_count - bot_rows;
4700 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4701
4702 /* move lines from top to above the bottom part */
4703 for (lnum = 1; lnum <= top_rows; ++lnum)
4704 {
4705 p = vim_strsave(ml_get(1));
4706 if (p == NULL)
4707 return OK;
4708 ml_append(bot_start, p, 0, FALSE);
4709 ml_delete(1, FALSE);
4710 vim_free(p);
4711 }
4712
4713 /* move lines from bottom to the top */
4714 for (lnum = 1; lnum <= bot_rows; ++lnum)
4715 {
4716 p = vim_strsave(ml_get(bot_start + lnum));
4717 if (p == NULL)
4718 return OK;
4719 ml_delete(bot_start + lnum, FALSE);
4720 ml_append(lnum - 1, p, 0, FALSE);
4721 vim_free(p);
4722 }
4723
4724 if (top_rows == bot_rows)
4725 {
4726 /* rows counts are equal, can swap cell properties */
4727 for (lnum = 0; lnum < top_rows; ++lnum)
4728 {
4729 sb_line_T temp;
4730
4731 temp = *(sb_line + lnum);
4732 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4733 *(sb_line + bot_start + lnum) = temp;
4734 }
4735 }
4736 else
4737 {
4738 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4739 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4740
4741 /* need to copy cell properties into temp memory */
4742 if (temp != NULL)
4743 {
4744 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4745 mch_memmove(term->tl_scrollback.ga_data,
4746 temp + bot_start,
4747 sizeof(sb_line_T) * bot_rows);
4748 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4749 temp + top_rows,
4750 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4751 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4752 + line_count - top_rows,
4753 temp,
4754 sizeof(sb_line_T) * top_rows);
4755 vim_free(temp);
4756 }
4757 }
4758
4759 term->tl_top_diff_rows = bot_rows;
4760 term->tl_bot_diff_rows = top_rows;
4761
4762 update_screen(NOT_VALID);
4763 return OK;
4764}
4765
4766/*
4767 * "term_dumpdiff(filename, filename, options)" function
4768 */
4769 void
4770f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4771{
4772 term_load_dump(argvars, rettv, TRUE);
4773}
4774
4775/*
4776 * "term_dumpload(filename, options)" function
4777 */
4778 void
4779f_term_dumpload(typval_T *argvars, typval_T *rettv)
4780{
4781 term_load_dump(argvars, rettv, FALSE);
4782}
4783
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004784/*
4785 * "term_getaltscreen(buf)" function
4786 */
4787 void
4788f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4789{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004790 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004791
4792 if (buf == NULL)
4793 return;
4794 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4795}
4796
4797/*
4798 * "term_getattr(attr, name)" function
4799 */
4800 void
4801f_term_getattr(typval_T *argvars, typval_T *rettv)
4802{
4803 int attr;
4804 size_t i;
4805 char_u *name;
4806
4807 static struct {
4808 char *name;
4809 int attr;
4810 } attrs[] = {
4811 {"bold", HL_BOLD},
4812 {"italic", HL_ITALIC},
4813 {"underline", HL_UNDERLINE},
4814 {"strike", HL_STRIKETHROUGH},
4815 {"reverse", HL_INVERSE},
4816 };
4817
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004818 attr = tv_get_number(&argvars[0]);
4819 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004820 if (name == NULL)
4821 return;
4822
4823 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4824 if (STRCMP(name, attrs[i].name) == 0)
4825 {
4826 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4827 break;
4828 }
4829}
4830
4831/*
4832 * "term_getcursor(buf)" function
4833 */
4834 void
4835f_term_getcursor(typval_T *argvars, typval_T *rettv)
4836{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004837 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004838 term_T *term;
4839 list_T *l;
4840 dict_T *d;
4841
4842 if (rettv_list_alloc(rettv) == FAIL)
4843 return;
4844 if (buf == NULL)
4845 return;
4846 term = buf->b_term;
4847
4848 l = rettv->vval.v_list;
4849 list_append_number(l, term->tl_cursor_pos.row + 1);
4850 list_append_number(l, term->tl_cursor_pos.col + 1);
4851
4852 d = dict_alloc();
4853 if (d != NULL)
4854 {
Bram Moolenaare0be1672018-07-08 16:50:37 +02004855 dict_add_number(d, "visible", term->tl_cursor_visible);
4856 dict_add_number(d, "blink", blink_state_is_inverted()
4857 ? !term->tl_cursor_blink : term->tl_cursor_blink);
4858 dict_add_number(d, "shape", term->tl_cursor_shape);
4859 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004860 list_append_dict(l, d);
4861 }
4862}
4863
4864/*
4865 * "term_getjob(buf)" function
4866 */
4867 void
4868f_term_getjob(typval_T *argvars, typval_T *rettv)
4869{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004870 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004871
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004872 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01004873 {
4874 rettv->v_type = VAR_SPECIAL;
4875 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004876 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01004877 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004878
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01004879 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004880 rettv->vval.v_job = buf->b_term->tl_job;
4881 if (rettv->vval.v_job != NULL)
4882 ++rettv->vval.v_job->jv_refcount;
4883}
4884
4885 static int
4886get_row_number(typval_T *tv, term_T *term)
4887{
4888 if (tv->v_type == VAR_STRING
4889 && tv->vval.v_string != NULL
4890 && STRCMP(tv->vval.v_string, ".") == 0)
4891 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004892 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004893}
4894
4895/*
4896 * "term_getline(buf, row)" function
4897 */
4898 void
4899f_term_getline(typval_T *argvars, typval_T *rettv)
4900{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004901 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004902 term_T *term;
4903 int row;
4904
4905 rettv->v_type = VAR_STRING;
4906 if (buf == NULL)
4907 return;
4908 term = buf->b_term;
4909 row = get_row_number(&argvars[1], term);
4910
4911 if (term->tl_vterm == NULL)
4912 {
4913 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4914
4915 /* vterm is finished, get the text from the buffer */
4916 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4917 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4918 }
4919 else
4920 {
4921 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4922 VTermRect rect;
4923 int len;
4924 char_u *p;
4925
4926 if (row < 0 || row >= term->tl_rows)
4927 return;
4928 len = term->tl_cols * MB_MAXBYTES + 1;
4929 p = alloc(len);
4930 if (p == NULL)
4931 return;
4932 rettv->vval.v_string = p;
4933
4934 rect.start_col = 0;
4935 rect.end_col = term->tl_cols;
4936 rect.start_row = row;
4937 rect.end_row = row + 1;
4938 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4939 }
4940}
4941
4942/*
4943 * "term_getscrolled(buf)" function
4944 */
4945 void
4946f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4947{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004948 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004949
4950 if (buf == NULL)
4951 return;
4952 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4953}
4954
4955/*
4956 * "term_getsize(buf)" function
4957 */
4958 void
4959f_term_getsize(typval_T *argvars, typval_T *rettv)
4960{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004961 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004962 list_T *l;
4963
4964 if (rettv_list_alloc(rettv) == FAIL)
4965 return;
4966 if (buf == NULL)
4967 return;
4968
4969 l = rettv->vval.v_list;
4970 list_append_number(l, buf->b_term->tl_rows);
4971 list_append_number(l, buf->b_term->tl_cols);
4972}
4973
4974/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004975 * "term_setsize(buf, rows, cols)" function
4976 */
4977 void
4978f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4979{
4980 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4981 term_T *term;
4982 varnumber_T rows, cols;
4983
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004984 if (buf == NULL)
4985 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004986 emsg(_("E955: Not a terminal buffer"));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004987 return;
4988 }
4989 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004990 return;
4991 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004992 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02004993 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004994 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02004995 cols = cols <= 0 ? term->tl_cols : cols;
4996 vterm_set_size(term->tl_vterm, rows, cols);
4997 /* handle_resize() will resize the windows */
4998
4999 /* Get and remember the size we ended up with. Update the pty. */
5000 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
5001 term_report_winsize(term, term->tl_rows, term->tl_cols);
5002}
5003
5004/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005005 * "term_getstatus(buf)" function
5006 */
5007 void
5008f_term_getstatus(typval_T *argvars, typval_T *rettv)
5009{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005010 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005011 term_T *term;
5012 char_u val[100];
5013
5014 rettv->v_type = VAR_STRING;
5015 if (buf == NULL)
5016 return;
5017 term = buf->b_term;
5018
5019 if (term_job_running(term))
5020 STRCPY(val, "running");
5021 else
5022 STRCPY(val, "finished");
5023 if (term->tl_normal_mode)
5024 STRCAT(val, ",normal");
5025 rettv->vval.v_string = vim_strsave(val);
5026}
5027
5028/*
5029 * "term_gettitle(buf)" function
5030 */
5031 void
5032f_term_gettitle(typval_T *argvars, typval_T *rettv)
5033{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005034 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005035
5036 rettv->v_type = VAR_STRING;
5037 if (buf == NULL)
5038 return;
5039
5040 if (buf->b_term->tl_title != NULL)
5041 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
5042}
5043
5044/*
5045 * "term_gettty(buf)" function
5046 */
5047 void
5048f_term_gettty(typval_T *argvars, typval_T *rettv)
5049{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005050 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02005051 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005052 int num = 0;
5053
5054 rettv->v_type = VAR_STRING;
5055 if (buf == NULL)
5056 return;
5057 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005058 num = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005059
5060 switch (num)
5061 {
5062 case 0:
5063 if (buf->b_term->tl_job != NULL)
5064 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005065 break;
5066 case 1:
5067 if (buf->b_term->tl_job != NULL)
5068 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005069 break;
5070 default:
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005071 semsg(_(e_invarg2), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005072 return;
5073 }
5074 if (p != NULL)
5075 rettv->vval.v_string = vim_strsave(p);
5076}
5077
5078/*
5079 * "term_list()" function
5080 */
5081 void
5082f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
5083{
5084 term_T *tp;
5085 list_T *l;
5086
5087 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
5088 return;
5089
5090 l = rettv->vval.v_list;
5091 for (tp = first_term; tp != NULL; tp = tp->tl_next)
5092 if (tp != NULL && tp->tl_buffer != NULL)
5093 if (list_append_number(l,
5094 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
5095 return;
5096}
5097
5098/*
5099 * "term_scrape(buf, row)" function
5100 */
5101 void
5102f_term_scrape(typval_T *argvars, typval_T *rettv)
5103{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005104 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005105 VTermScreen *screen = NULL;
5106 VTermPos pos;
5107 list_T *l;
5108 term_T *term;
5109 char_u *p;
5110 sb_line_T *line;
5111
5112 if (rettv_list_alloc(rettv) == FAIL)
5113 return;
5114 if (buf == NULL)
5115 return;
5116 term = buf->b_term;
5117
5118 l = rettv->vval.v_list;
5119 pos.row = get_row_number(&argvars[1], term);
5120
5121 if (term->tl_vterm != NULL)
5122 {
5123 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01005124 if (screen == NULL) // can't really happen
5125 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005126 p = NULL;
5127 line = NULL;
5128 }
5129 else
5130 {
5131 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
5132
5133 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
5134 return;
5135 p = ml_get_buf(buf, lnum + 1, FALSE);
5136 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
5137 }
5138
5139 for (pos.col = 0; pos.col < term->tl_cols; )
5140 {
5141 dict_T *dcell;
5142 int width;
5143 VTermScreenCellAttrs attrs;
5144 VTermColor fg, bg;
5145 char_u rgb[8];
5146 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5147 int off = 0;
5148 int i;
5149
5150 if (screen == NULL)
5151 {
5152 cellattr_T *cellattr;
5153 int len;
5154
5155 /* vterm has finished, get the cell from scrollback */
5156 if (pos.col >= line->sb_cols)
5157 break;
5158 cellattr = line->sb_cells + pos.col;
5159 width = cellattr->width;
5160 attrs = cellattr->attrs;
5161 fg = cellattr->fg;
5162 bg = cellattr->bg;
5163 len = MB_PTR2LEN(p);
5164 mch_memmove(mbs, p, len);
5165 mbs[len] = NUL;
5166 p += len;
5167 }
5168 else
5169 {
5170 VTermScreenCell cell;
5171 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5172 break;
5173 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5174 {
5175 if (cell.chars[i] == 0)
5176 break;
5177 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5178 }
5179 mbs[off] = NUL;
5180 width = cell.width;
5181 attrs = cell.attrs;
5182 fg = cell.fg;
5183 bg = cell.bg;
5184 }
5185 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01005186 if (dcell == NULL)
5187 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005188 list_append_dict(l, dcell);
5189
Bram Moolenaare0be1672018-07-08 16:50:37 +02005190 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005191
5192 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5193 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005194 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005195 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5196 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005197 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005198
Bram Moolenaare0be1672018-07-08 16:50:37 +02005199 dict_add_number(dcell, "attr", cell2attr(attrs, fg, bg));
5200 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005201
5202 ++pos.col;
5203 if (width == 2)
5204 ++pos.col;
5205 }
5206}
5207
5208/*
5209 * "term_sendkeys(buf, keys)" function
5210 */
5211 void
5212f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5213{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005214 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005215 char_u *msg;
5216 term_T *term;
5217
5218 rettv->v_type = VAR_UNKNOWN;
5219 if (buf == NULL)
5220 return;
5221
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005222 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005223 if (msg == NULL)
5224 return;
5225 term = buf->b_term;
5226 if (term->tl_vterm == NULL)
5227 return;
5228
5229 while (*msg != NUL)
5230 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02005231 int c;
5232
5233 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
5234 {
5235 c = TO_SPECIAL(msg[1], msg[2]);
5236 msg += 3;
5237 }
5238 else
5239 {
5240 c = PTR2CHAR(msg);
5241 msg += MB_CPTR2LEN(msg);
5242 }
5243 send_keys_to_term(term, c, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005244 }
5245}
5246
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005247#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5248/*
5249 * "term_getansicolors(buf)" function
5250 */
5251 void
5252f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5253{
5254 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5255 term_T *term;
5256 VTermState *state;
5257 VTermColor color;
5258 char_u hexbuf[10];
5259 int index;
5260 list_T *list;
5261
5262 if (rettv_list_alloc(rettv) == FAIL)
5263 return;
5264
5265 if (buf == NULL)
5266 return;
5267 term = buf->b_term;
5268 if (term->tl_vterm == NULL)
5269 return;
5270
5271 list = rettv->vval.v_list;
5272 state = vterm_obtain_state(term->tl_vterm);
5273 for (index = 0; index < 16; index++)
5274 {
5275 vterm_state_get_palette_color(state, index, &color);
5276 sprintf((char *)hexbuf, "#%02x%02x%02x",
5277 color.red, color.green, color.blue);
5278 if (list_append_string(list, hexbuf, 7) == FAIL)
5279 return;
5280 }
5281}
5282
5283/*
5284 * "term_setansicolors(buf, list)" function
5285 */
5286 void
5287f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5288{
5289 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5290 term_T *term;
5291
5292 if (buf == NULL)
5293 return;
5294 term = buf->b_term;
5295 if (term->tl_vterm == NULL)
5296 return;
5297
5298 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5299 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005300 emsg(_(e_listreq));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005301 return;
5302 }
5303
5304 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005305 emsg(_(e_invarg));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005306}
5307#endif
5308
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005309/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005310 * "term_setrestore(buf, command)" function
5311 */
5312 void
5313f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5314{
5315#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005316 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005317 term_T *term;
5318 char_u *cmd;
5319
5320 if (buf == NULL)
5321 return;
5322 term = buf->b_term;
5323 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005324 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005325 if (cmd != NULL)
5326 term->tl_command = vim_strsave(cmd);
5327 else
5328 term->tl_command = NULL;
5329#endif
5330}
5331
5332/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005333 * "term_setkill(buf, how)" function
5334 */
5335 void
5336f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5337{
5338 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5339 term_T *term;
5340 char_u *how;
5341
5342 if (buf == NULL)
5343 return;
5344 term = buf->b_term;
5345 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005346 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005347 if (how != NULL)
5348 term->tl_kill = vim_strsave(how);
5349 else
5350 term->tl_kill = NULL;
5351}
5352
5353/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005354 * "term_start(command, options)" function
5355 */
5356 void
5357f_term_start(typval_T *argvars, typval_T *rettv)
5358{
5359 jobopt_T opt;
5360 buf_T *buf;
5361
5362 init_job_options(&opt);
5363 if (argvars[1].v_type != VAR_UNKNOWN
5364 && get_job_options(&argvars[1], &opt,
5365 JO_TIMEOUT_ALL + JO_STOPONEXIT
5366 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5367 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5368 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5369 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005370 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005371 + JO2_NORESTORE + JO2_TERM_KILL
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005372 + JO2_ANSI_COLORS + JO2_TERM_MODE) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005373 return;
5374
Bram Moolenaar13568252018-03-16 20:46:58 +01005375 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005376
5377 if (buf != NULL && buf->b_term != NULL)
5378 rettv->vval.v_number = buf->b_fnum;
5379}
5380
5381/*
5382 * "term_wait" function
5383 */
5384 void
5385f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5386{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005387 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005388
5389 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005390 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005391 if (buf->b_term->tl_job == NULL)
5392 {
5393 ch_log(NULL, "term_wait(): no job to wait for");
5394 return;
5395 }
5396 if (buf->b_term->tl_job->jv_channel == NULL)
5397 /* channel is closed, nothing to do */
5398 return;
5399
5400 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005401 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005402 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5403 {
5404 /* The job is dead, keep reading channel I/O until the channel is
5405 * closed. buf->b_term may become NULL if the terminal was closed while
5406 * waiting. */
5407 ch_log(NULL, "term_wait(): waiting for channel to close");
5408 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5409 {
5410 mch_check_messages();
5411 parse_queued_messages();
Bram Moolenaard45aa552018-05-21 22:50:29 +02005412 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01005413 if (!buf_valid(buf))
5414 /* If the terminal is closed when the channel is closed the
5415 * buffer disappears. */
5416 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005417 }
5418 mch_check_messages();
5419 parse_queued_messages();
5420 }
5421 else
5422 {
5423 long wait = 10L;
5424
5425 mch_check_messages();
5426 parse_queued_messages();
5427
5428 /* Wait for some time for any channel I/O. */
5429 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005430 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005431 ui_delay(wait, TRUE);
5432 mch_check_messages();
5433
5434 /* Flushing messages on channels is hopefully sufficient.
5435 * TODO: is there a better way? */
5436 parse_queued_messages();
5437 }
5438}
5439
5440/*
5441 * Called when a channel has sent all the lines to a terminal.
5442 * Send a CTRL-D to mark the end of the text.
5443 */
5444 void
5445term_send_eof(channel_T *ch)
5446{
5447 term_T *term;
5448
5449 for (term = first_term; term != NULL; term = term->tl_next)
5450 if (term->tl_job == ch->ch_job)
5451 {
5452 if (term->tl_eof_chars != NULL)
5453 {
5454 channel_send(ch, PART_IN, term->tl_eof_chars,
5455 (int)STRLEN(term->tl_eof_chars), NULL);
5456 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5457 }
5458# ifdef WIN3264
5459 else
5460 /* Default: CTRL-D */
5461 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5462# endif
5463 }
5464}
5465
Bram Moolenaar113e1072019-01-20 15:30:40 +01005466#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005467 job_T *
5468term_getjob(term_T *term)
5469{
5470 return term != NULL ? term->tl_job : NULL;
5471}
Bram Moolenaar113e1072019-01-20 15:30:40 +01005472#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005473
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005474# if defined(WIN3264) || defined(PROTO)
5475
5476/**************************************
5477 * 2. MS-Windows implementation.
5478 */
5479
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005480HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
5481HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
5482HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
5483BOOL (*pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
5484BOOL (*pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
5485void (*pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
5486
5487 static int
5488dyn_conpty_init(int verbose)
5489{
5490 static BOOL handled = FALSE;
5491 static int result;
5492 HMODULE hKerneldll;
5493 int i;
5494 static struct
5495 {
5496 char *name;
5497 FARPROC *ptr;
5498 } conpty_entry[] =
5499 {
5500 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
5501 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
5502 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
5503 {"InitializeProcThreadAttributeList",
5504 (FARPROC*)&pInitializeProcThreadAttributeList},
5505 {"UpdateProcThreadAttribute",
5506 (FARPROC*)&pUpdateProcThreadAttribute},
5507 {"DeleteProcThreadAttributeList",
5508 (FARPROC*)&pDeleteProcThreadAttributeList},
5509 {NULL, NULL}
5510 };
5511
5512 if (handled)
5513 return result;
5514
5515 if (!has_vtp_working())
5516 {
5517 handled = TRUE;
5518 result = FAIL;
5519 return FAIL;
5520 }
5521
5522 hKerneldll = vimLoadLib("kernel32.dll");
5523 for (i = 0; conpty_entry[i].name != NULL
5524 && conpty_entry[i].ptr != NULL; ++i)
5525 {
5526 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
5527 conpty_entry[i].name)) == NULL)
5528 {
5529 if (verbose)
5530 semsg(_(e_loadfunc), conpty_entry[i].name);
5531 return FAIL;
5532 }
5533 }
5534
5535 handled = TRUE;
5536 result = OK;
5537 return OK;
5538}
5539
5540 static int
5541conpty_term_and_job_init(
5542 term_T *term,
5543 typval_T *argvar,
5544 char **argv,
5545 jobopt_T *opt,
5546 jobopt_T *orig_opt)
5547{
5548 WCHAR *cmd_wchar = NULL;
5549 WCHAR *cmd_wchar_copy = NULL;
5550 WCHAR *cwd_wchar = NULL;
5551 WCHAR *env_wchar = NULL;
5552 channel_T *channel = NULL;
5553 job_T *job = NULL;
5554 HANDLE jo = NULL;
5555 garray_T ga_cmd, ga_env;
5556 char_u *cmd = NULL;
5557 HRESULT hr;
5558 COORD consize;
5559 SIZE_T breq;
5560 PROCESS_INFORMATION proc_info;
5561 HANDLE i_theirs = NULL;
5562 HANDLE o_theirs = NULL;
5563 HANDLE i_ours = NULL;
5564 HANDLE o_ours = NULL;
5565
5566 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5567 ga_init2(&ga_env, (int)sizeof(char*), 20);
5568
5569 if (argvar->v_type == VAR_STRING)
5570 {
5571 cmd = argvar->vval.v_string;
5572 }
5573 else if (argvar->v_type == VAR_LIST)
5574 {
5575 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
5576 goto failed;
5577 cmd = ga_cmd.ga_data;
5578 }
5579 if (cmd == NULL || *cmd == NUL)
5580 {
5581 emsg(_(e_invarg));
5582 goto failed;
5583 }
5584
5585 term->tl_arg0_cmd = vim_strsave(cmd);
5586
5587 cmd_wchar = enc_to_utf16(cmd, NULL);
5588
5589 if (cmd_wchar != NULL)
5590 {
5591 /* Request by CreateProcessW */
5592 breq = wcslen(cmd_wchar) + 1 + 1; /* Addition of NUL by API */
5593 cmd_wchar_copy = (PWSTR)alloc((int)(breq * sizeof(WCHAR)));
5594 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
5595 }
5596
5597 ga_clear(&ga_cmd);
5598 if (cmd_wchar == NULL)
5599 goto failed;
5600 if (opt->jo_cwd != NULL)
5601 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
5602
5603 win32_build_env(opt->jo_env, &ga_env, TRUE);
5604 env_wchar = ga_env.ga_data;
5605
5606 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
5607 goto failed;
5608 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
5609 goto failed;
5610
5611 consize.X = term->tl_cols;
5612 consize.Y = term->tl_rows;
5613 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
5614 &term->tl_conpty);
5615 if (FAILED(hr))
5616 goto failed;
5617
5618 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
5619
5620 /* Set up pipe inheritance safely: Vista or later. */
5621 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
5622 term->tl_siex.lpAttributeList =
5623 (PPROC_THREAD_ATTRIBUTE_LIST)alloc((int)breq);
5624 if (!term->tl_siex.lpAttributeList)
5625 goto failed;
5626 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
5627 0, &breq))
5628 goto failed;
5629 if (!pUpdateProcThreadAttribute(
5630 term->tl_siex.lpAttributeList, 0,
5631 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
5632 sizeof(HPCON), NULL, NULL))
5633 goto failed;
5634
5635 channel = add_channel();
5636 if (channel == NULL)
5637 goto failed;
5638
5639 job = job_alloc();
5640 if (job == NULL)
5641 goto failed;
5642 if (argvar->v_type == VAR_STRING)
5643 {
5644 int argc;
5645
5646 build_argv_from_string(cmd, &job->jv_argv, &argc);
5647 }
5648 else
5649 {
5650 int argc;
5651
5652 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5653 }
5654
5655 if (opt->jo_set & JO_IN_BUF)
5656 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5657
5658 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
5659 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
5660 | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP
5661 | CREATE_DEFAULT_ERROR_MODE,
5662 env_wchar, cwd_wchar,
5663 &term->tl_siex.StartupInfo, &proc_info))
5664 goto failed;
5665
5666 CloseHandle(i_theirs);
5667 CloseHandle(o_theirs);
5668
5669 channel_set_pipes(channel,
5670 (sock_T)i_ours,
5671 (sock_T)o_ours,
5672 (sock_T)o_ours);
5673
5674 /* Write lines with CR instead of NL. */
5675 channel->ch_write_text_mode = TRUE;
5676
5677 /* Use to explicitly delete anonymous pipe handle. */
5678 channel->ch_anonymous_pipe = TRUE;
5679
5680 jo = CreateJobObject(NULL, NULL);
5681 if (jo == NULL)
5682 goto failed;
5683
5684 if (!AssignProcessToJobObject(jo, proc_info.hProcess))
5685 {
5686 /* Failed, switch the way to terminate process with TerminateProcess. */
5687 CloseHandle(jo);
5688 jo = NULL;
5689 }
5690
5691 ResumeThread(proc_info.hThread);
5692 CloseHandle(proc_info.hThread);
5693
5694 vim_free(cmd_wchar);
5695 vim_free(cmd_wchar_copy);
5696 vim_free(cwd_wchar);
5697 vim_free(env_wchar);
5698
5699 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
5700 goto failed;
5701
5702#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5703 if (opt->jo_set2 & JO2_ANSI_COLORS)
5704 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5705 else
5706 init_vterm_ansi_colors(term->tl_vterm);
5707#endif
5708
5709 channel_set_job(channel, job, opt);
5710 job_set_options(job, opt);
5711
5712 job->jv_channel = channel;
5713 job->jv_proc_info = proc_info;
5714 job->jv_job_object = jo;
5715 job->jv_status = JOB_STARTED;
5716 ++job->jv_refcount;
5717 term->tl_job = job;
5718
5719 /* Redirecting stdout and stderr doesn't work at the job level. Instead
5720 * open the file here and handle it in. opt->jo_io was changed in
5721 * setup_job_options(), use the original flags here. */
5722 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
5723 {
5724 char_u *fname = opt->jo_io_name[PART_OUT];
5725
5726 ch_log(channel, "Opening output file %s", fname);
5727 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
5728 if (term->tl_out_fd == NULL)
5729 semsg(_(e_notopen), fname);
5730 }
5731
5732 return OK;
5733
5734failed:
5735 ga_clear(&ga_cmd);
5736 ga_clear(&ga_env);
5737 vim_free(cmd_wchar);
5738 vim_free(cmd_wchar_copy);
5739 vim_free(cwd_wchar);
5740 if (channel != NULL)
5741 channel_clear(channel);
5742 if (job != NULL)
5743 {
5744 job->jv_channel = NULL;
5745 job_cleanup(job);
5746 }
5747 term->tl_job = NULL;
5748 if (jo != NULL)
5749 CloseHandle(jo);
5750
5751 if (term->tl_siex.lpAttributeList != NULL)
5752 {
5753 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
5754 vim_free(term->tl_siex.lpAttributeList);
5755 }
5756 term->tl_siex.lpAttributeList = NULL;
5757 if (o_theirs != NULL)
5758 CloseHandle(o_theirs);
5759 if (o_ours != NULL)
5760 CloseHandle(o_ours);
5761 if (i_ours != NULL)
5762 CloseHandle(i_ours);
5763 if (i_theirs != NULL)
5764 CloseHandle(i_theirs);
5765 if (term->tl_conpty != NULL)
5766 pClosePseudoConsole(term->tl_conpty);
5767 term->tl_conpty = NULL;
5768 return FAIL;
5769}
5770
5771 static void
5772conpty_term_report_winsize(term_T *term, int rows, int cols)
5773{
5774 COORD consize;
5775
5776 consize.X = cols;
5777 consize.Y = rows;
5778 pResizePseudoConsole(term->tl_conpty, consize);
5779}
5780
5781 void
5782term_free_conpty(term_T *term)
5783{
5784 if (term->tl_siex.lpAttributeList != NULL)
5785 {
5786 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
5787 vim_free(term->tl_siex.lpAttributeList);
5788 }
5789 term->tl_siex.lpAttributeList = NULL;
5790 if (term->tl_conpty != NULL)
5791 pClosePseudoConsole(term->tl_conpty);
5792 term->tl_conpty = NULL;
5793}
5794
5795 int
5796use_conpty(void)
5797{
5798 return has_conpty;
5799}
5800
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005801# ifndef PROTO
5802
5803#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5804#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005805#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005806
5807void* (*winpty_config_new)(UINT64, void*);
5808void* (*winpty_open)(void*, void*);
5809void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5810BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5811void (*winpty_config_set_mouse_mode)(void*, int);
5812void (*winpty_config_set_initial_size)(void*, int, int);
5813LPCWSTR (*winpty_conin_name)(void*);
5814LPCWSTR (*winpty_conout_name)(void*);
5815LPCWSTR (*winpty_conerr_name)(void*);
5816void (*winpty_free)(void*);
5817void (*winpty_config_free)(void*);
5818void (*winpty_spawn_config_free)(void*);
5819void (*winpty_error_free)(void*);
5820LPCWSTR (*winpty_error_msg)(void*);
5821BOOL (*winpty_set_size)(void*, int, int, void*);
5822HANDLE (*winpty_agent_process)(void*);
5823
5824#define WINPTY_DLL "winpty.dll"
5825
5826static HINSTANCE hWinPtyDLL = NULL;
5827# endif
5828
5829 static int
5830dyn_winpty_init(int verbose)
5831{
5832 int i;
5833 static struct
5834 {
5835 char *name;
5836 FARPROC *ptr;
5837 } winpty_entry[] =
5838 {
5839 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5840 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5841 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5842 {"winpty_config_set_mouse_mode",
5843 (FARPROC*)&winpty_config_set_mouse_mode},
5844 {"winpty_config_set_initial_size",
5845 (FARPROC*)&winpty_config_set_initial_size},
5846 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5847 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5848 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5849 {"winpty_free", (FARPROC*)&winpty_free},
5850 {"winpty_open", (FARPROC*)&winpty_open},
5851 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5852 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5853 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5854 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5855 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5856 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5857 {NULL, NULL}
5858 };
5859
5860 /* No need to initialize twice. */
5861 if (hWinPtyDLL)
5862 return OK;
5863 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5864 * winpty.dll. */
5865 if (*p_winptydll != NUL)
5866 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5867 if (!hWinPtyDLL)
5868 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5869 if (!hWinPtyDLL)
5870 {
5871 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005872 semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005873 : (char_u *)WINPTY_DLL);
5874 return FAIL;
5875 }
5876 for (i = 0; winpty_entry[i].name != NULL
5877 && winpty_entry[i].ptr != NULL; ++i)
5878 {
5879 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5880 winpty_entry[i].name)) == NULL)
5881 {
5882 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005883 semsg(_(e_loadfunc), winpty_entry[i].name);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005884 return FAIL;
5885 }
5886 }
5887
5888 return OK;
5889}
5890
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005891 static int
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005892winpty_term_and_job_init(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005893 term_T *term,
5894 typval_T *argvar,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005895 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02005896 jobopt_T *opt,
5897 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005898{
5899 WCHAR *cmd_wchar = NULL;
5900 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005901 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005902 channel_T *channel = NULL;
5903 job_T *job = NULL;
5904 DWORD error;
5905 HANDLE jo = NULL;
5906 HANDLE child_process_handle;
5907 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005908 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005909 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005910 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005911 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005912
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005913 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5914 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005915
5916 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005917 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005918 cmd = argvar->vval.v_string;
5919 }
5920 else if (argvar->v_type == VAR_LIST)
5921 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005922 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005923 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005924 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005925 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005926 if (cmd == NULL || *cmd == NUL)
5927 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005928 emsg(_(e_invarg));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005929 goto failed;
5930 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005931
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005932 term->tl_arg0_cmd = vim_strsave(cmd);
5933
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005934 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005935 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005936 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005937 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005938 if (opt->jo_cwd != NULL)
5939 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005940
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005941 win32_build_env(opt->jo_env, &ga_env, TRUE);
5942 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005943
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005944 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5945 if (term->tl_winpty_config == NULL)
5946 goto failed;
5947
5948 winpty_config_set_mouse_mode(term->tl_winpty_config,
5949 WINPTY_MOUSE_MODE_FORCE);
5950 winpty_config_set_initial_size(term->tl_winpty_config,
5951 term->tl_cols, term->tl_rows);
5952 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5953 if (term->tl_winpty == NULL)
5954 goto failed;
5955
5956 spawn_config = winpty_spawn_config_new(
5957 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5958 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5959 NULL,
5960 cmd_wchar,
5961 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005962 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005963 &winpty_err);
5964 if (spawn_config == NULL)
5965 goto failed;
5966
5967 channel = add_channel();
5968 if (channel == NULL)
5969 goto failed;
5970
5971 job = job_alloc();
5972 if (job == NULL)
5973 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005974 if (argvar->v_type == VAR_STRING)
5975 {
5976 int argc;
5977
5978 build_argv_from_string(cmd, &job->jv_argv, &argc);
5979 }
5980 else
5981 {
5982 int argc;
5983
5984 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5985 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005986
5987 if (opt->jo_set & JO_IN_BUF)
5988 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5989
5990 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5991 &child_thread_handle, &error, &winpty_err))
5992 goto failed;
5993
5994 channel_set_pipes(channel,
5995 (sock_T)CreateFileW(
5996 winpty_conin_name(term->tl_winpty),
5997 GENERIC_WRITE, 0, NULL,
5998 OPEN_EXISTING, 0, NULL),
5999 (sock_T)CreateFileW(
6000 winpty_conout_name(term->tl_winpty),
6001 GENERIC_READ, 0, NULL,
6002 OPEN_EXISTING, 0, NULL),
6003 (sock_T)CreateFileW(
6004 winpty_conerr_name(term->tl_winpty),
6005 GENERIC_READ, 0, NULL,
6006 OPEN_EXISTING, 0, NULL));
6007
6008 /* Write lines with CR instead of NL. */
6009 channel->ch_write_text_mode = TRUE;
6010
6011 jo = CreateJobObject(NULL, NULL);
6012 if (jo == NULL)
6013 goto failed;
6014
6015 if (!AssignProcessToJobObject(jo, child_process_handle))
6016 {
6017 /* Failed, switch the way to terminate process with TerminateProcess. */
6018 CloseHandle(jo);
6019 jo = NULL;
6020 }
6021
6022 winpty_spawn_config_free(spawn_config);
6023 vim_free(cmd_wchar);
6024 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006025 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006026
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006027 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6028 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006029
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006030#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6031 if (opt->jo_set2 & JO2_ANSI_COLORS)
6032 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6033 else
6034 init_vterm_ansi_colors(term->tl_vterm);
6035#endif
6036
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006037 channel_set_job(channel, job, opt);
6038 job_set_options(job, opt);
6039
6040 job->jv_channel = channel;
6041 job->jv_proc_info.hProcess = child_process_handle;
6042 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
6043 job->jv_job_object = jo;
6044 job->jv_status = JOB_STARTED;
6045 job->jv_tty_in = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006046 (short_u *)winpty_conin_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006047 job->jv_tty_out = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006048 (short_u *)winpty_conout_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006049 ++job->jv_refcount;
6050 term->tl_job = job;
6051
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006052 /* Redirecting stdout and stderr doesn't work at the job level. Instead
6053 * open the file here and handle it in. opt->jo_io was changed in
6054 * setup_job_options(), use the original flags here. */
6055 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6056 {
6057 char_u *fname = opt->jo_io_name[PART_OUT];
6058
6059 ch_log(channel, "Opening output file %s", fname);
6060 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6061 if (term->tl_out_fd == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006062 semsg(_(e_notopen), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006063 }
6064
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006065 return OK;
6066
6067failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006068 ga_clear(&ga_cmd);
6069 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006070 vim_free(cmd_wchar);
6071 vim_free(cwd_wchar);
6072 if (spawn_config != NULL)
6073 winpty_spawn_config_free(spawn_config);
6074 if (channel != NULL)
6075 channel_clear(channel);
6076 if (job != NULL)
6077 {
6078 job->jv_channel = NULL;
6079 job_cleanup(job);
6080 }
6081 term->tl_job = NULL;
6082 if (jo != NULL)
6083 CloseHandle(jo);
6084 if (term->tl_winpty != NULL)
6085 winpty_free(term->tl_winpty);
6086 term->tl_winpty = NULL;
6087 if (term->tl_winpty_config != NULL)
6088 winpty_config_free(term->tl_winpty_config);
6089 term->tl_winpty_config = NULL;
6090 if (winpty_err != NULL)
6091 {
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006092 char *msg = (char *)utf16_to_enc(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006093 (short_u *)winpty_error_msg(winpty_err), NULL);
6094
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006095 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006096 winpty_error_free(winpty_err);
6097 }
6098 return FAIL;
6099}
6100
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006101/*
6102 * Create a new terminal of "rows" by "cols" cells.
6103 * Store a reference in "term".
6104 * Return OK or FAIL.
6105 */
6106 static int
6107term_and_job_init(
6108 term_T *term,
6109 typval_T *argvar,
6110 char **argv UNUSED,
6111 jobopt_T *opt,
6112 jobopt_T *orig_opt)
6113{
6114 int use_winpty = FALSE;
6115 int use_conpty = FALSE;
6116
6117 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
6118 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
6119
6120 if (!has_winpty && !has_conpty)
6121 // If neither is available give the errors for winpty, since when
6122 // conpty is not available it can't be installed either.
6123 return dyn_winpty_init(TRUE);
6124
6125 if (opt->jo_term_mode == 'w')
6126 set_string_option_direct((char_u *)"tmod", -1, (char_u *)"winpty",
6127 OPT_FREE|OPT_LOCAL, 0);
6128 if (opt->jo_term_mode == 'c')
6129 set_string_option_direct((char_u *)"tmod", -1, (char_u *)"conpty",
6130 OPT_FREE|OPT_LOCAL, 0);
6131
6132 if (curwin->w_p_tmod == NULL || *curwin->w_p_tmod == NUL)
6133 {
6134 if (has_conpty)
6135 use_conpty = TRUE;
6136 else if (has_winpty)
6137 use_winpty = TRUE;
6138 // else: error
6139 }
6140 else if (STRICMP(curwin->w_p_tmod, "winpty") == 0)
6141 {
6142 if (has_winpty)
6143 use_winpty = TRUE;
6144 }
6145 else if (STRICMP(curwin->w_p_tmod, "conpty") == 0)
6146 {
6147 if (has_conpty)
6148 use_conpty = TRUE;
6149 else
6150 return dyn_conpty_init(TRUE);
6151 }
6152
6153 if (use_conpty)
6154 {
6155 set_string_option_direct((char_u *)"tmod", -1, (char_u *)"conpty",
6156 OPT_FREE|OPT_LOCAL, 0);
6157 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
6158 }
6159
6160 if (use_winpty)
6161 {
6162 set_string_option_direct((char_u *)"tmod", -1, (char_u *)"winpty",
6163 OPT_FREE|OPT_LOCAL, 0);
6164 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
6165 }
6166
6167 // error
6168 return dyn_winpty_init(TRUE);
6169}
6170
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006171 static int
6172create_pty_only(term_T *term, jobopt_T *options)
6173{
6174 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
6175 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
6176 char in_name[80], out_name[80];
6177 channel_T *channel = NULL;
6178
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006179 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6180 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006181
6182 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
6183 GetCurrentProcessId(),
6184 curbuf->b_fnum);
6185 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
6186 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6187 PIPE_UNLIMITED_INSTANCES,
6188 0, 0, NMPWAIT_NOWAIT, NULL);
6189 if (hPipeIn == INVALID_HANDLE_VALUE)
6190 goto failed;
6191
6192 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
6193 GetCurrentProcessId(),
6194 curbuf->b_fnum);
6195 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
6196 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6197 PIPE_UNLIMITED_INSTANCES,
6198 0, 0, 0, NULL);
6199 if (hPipeOut == INVALID_HANDLE_VALUE)
6200 goto failed;
6201
6202 ConnectNamedPipe(hPipeIn, NULL);
6203 ConnectNamedPipe(hPipeOut, NULL);
6204
6205 term->tl_job = job_alloc();
6206 if (term->tl_job == NULL)
6207 goto failed;
6208 ++term->tl_job->jv_refcount;
6209
6210 /* behave like the job is already finished */
6211 term->tl_job->jv_status = JOB_FINISHED;
6212
6213 channel = add_channel();
6214 if (channel == NULL)
6215 goto failed;
6216 term->tl_job->jv_channel = channel;
6217 channel->ch_keep_open = TRUE;
6218 channel->ch_named_pipe = TRUE;
6219
6220 channel_set_pipes(channel,
6221 (sock_T)hPipeIn,
6222 (sock_T)hPipeOut,
6223 (sock_T)hPipeOut);
6224 channel_set_job(channel, term->tl_job, options);
6225 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
6226 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
6227
6228 return OK;
6229
6230failed:
6231 if (hPipeIn != NULL)
6232 CloseHandle(hPipeIn);
6233 if (hPipeOut != NULL)
6234 CloseHandle(hPipeOut);
6235 return FAIL;
6236}
6237
6238/*
6239 * Free the terminal emulator part of "term".
6240 */
6241 static void
6242term_free_vterm(term_T *term)
6243{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006244 term_free_conpty(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006245 if (term->tl_winpty != NULL)
6246 winpty_free(term->tl_winpty);
6247 term->tl_winpty = NULL;
6248 if (term->tl_winpty_config != NULL)
6249 winpty_config_free(term->tl_winpty_config);
6250 term->tl_winpty_config = NULL;
6251 if (term->tl_vterm != NULL)
6252 vterm_free(term->tl_vterm);
6253 term->tl_vterm = NULL;
6254}
6255
6256/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006257 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006258 */
6259 static void
6260term_report_winsize(term_T *term, int rows, int cols)
6261{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006262 if (term->tl_conpty)
6263 conpty_term_report_winsize(term, rows, cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006264 if (term->tl_winpty)
6265 winpty_set_size(term->tl_winpty, cols, rows, NULL);
6266}
6267
6268 int
6269terminal_enabled(void)
6270{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006271 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006272}
6273
6274# else
6275
6276/**************************************
6277 * 3. Unix-like implementation.
6278 */
6279
6280/*
6281 * Create a new terminal of "rows" by "cols" cells.
6282 * Start job for "cmd".
6283 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01006284 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006285 * Return OK or FAIL.
6286 */
6287 static int
6288term_and_job_init(
6289 term_T *term,
6290 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01006291 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006292 jobopt_T *opt,
6293 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006294{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006295 term->tl_arg0_cmd = NULL;
6296
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006297 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6298 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006299
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006300#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6301 if (opt->jo_set2 & JO2_ANSI_COLORS)
6302 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6303 else
6304 init_vterm_ansi_colors(term->tl_vterm);
6305#endif
6306
Bram Moolenaar13568252018-03-16 20:46:58 +01006307 /* This may change a string in "argvar". */
Bram Moolenaar493359e2018-06-12 20:25:52 +02006308 term->tl_job = job_start(argvar, argv, opt, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006309 if (term->tl_job != NULL)
6310 ++term->tl_job->jv_refcount;
6311
6312 return term->tl_job != NULL
6313 && term->tl_job->jv_channel != NULL
6314 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
6315}
6316
6317 static int
6318create_pty_only(term_T *term, jobopt_T *opt)
6319{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006320 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6321 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006322
6323 term->tl_job = job_alloc();
6324 if (term->tl_job == NULL)
6325 return FAIL;
6326 ++term->tl_job->jv_refcount;
6327
6328 /* behave like the job is already finished */
6329 term->tl_job->jv_status = JOB_FINISHED;
6330
6331 return mch_create_pty_channel(term->tl_job, opt);
6332}
6333
6334/*
6335 * Free the terminal emulator part of "term".
6336 */
6337 static void
6338term_free_vterm(term_T *term)
6339{
6340 if (term->tl_vterm != NULL)
6341 vterm_free(term->tl_vterm);
6342 term->tl_vterm = NULL;
6343}
6344
6345/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006346 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006347 */
6348 static void
6349term_report_winsize(term_T *term, int rows, int cols)
6350{
6351 /* Use an ioctl() to report the new window size to the job. */
6352 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
6353 {
6354 int fd = -1;
6355 int part;
6356
6357 for (part = PART_OUT; part < PART_COUNT; ++part)
6358 {
6359 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01006360 if (mch_isatty(fd))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006361 break;
6362 }
6363 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
6364 mch_signal_job(term->tl_job, (char_u *)"winch");
6365 }
6366}
6367
6368# endif
6369
6370#endif /* FEAT_TERMINAL */