blob: 58ac786c2fc17c3a98ad4dbbcf148269b79a4d61 [file] [log] [blame]
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * Terminal window support, see ":help :terminal".
12 *
13 * There are three parts:
14 * 1. Generic code for all systems.
15 * Uses libvterm for the terminal emulator.
16 * 2. The MS-Windows implementation.
17 * Uses winpty.
18 * 3. The Unix-like implementation.
19 * Uses pseudo-tty's (pty's).
20 *
21 * For each terminal one VTerm is constructed. This uses libvterm. A copy of
22 * this library is in the libvterm directory.
23 *
24 * When a terminal window is opened, a job is started that will be connected to
25 * the terminal emulator.
26 *
27 * If the terminal window has keyboard focus, typed keys are converted to the
28 * terminal encoding and writing to the job over a channel.
29 *
30 * If the job produces output, it is written to the terminal emulator. The
31 * terminal emulator invokes callbacks when its screen content changes. The
32 * line range is stored in tl_dirty_row_start and tl_dirty_row_end. Once in a
33 * while, if the terminal window is visible, the screen contents is drawn.
34 *
35 * When the job ends the text is put in a buffer. Redrawing then happens from
36 * that buffer, attributes come from the scrollback buffer tl_scrollback.
37 * When the buffer is changed it is turned into a normal buffer, the attributes
38 * in tl_scrollback are no longer used.
39 *
40 * TODO:
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +020041 * - Win32: Make terminal used for :!cmd in the GUI work better. Allow for
Bram Moolenaar4a696342018-04-05 18:45:26 +020042 * redirection. Probably in call to channel_set_pipes().
Bram Moolenaar802bfb12018-04-15 17:28:13 +020043 * - Win32: Redirecting output does not work, Test_terminal_redir_file()
44 * is disabled.
Bram Moolenaara997b452018-04-17 23:24:06 +020045 * - handle_moverect() scrolls one line at a time. Postpone scrolling, count
46 * the number of lines, until a redraw happens. Then if scrolling many lines
47 * a redraw is faster.
Bram Moolenaar498c2562018-04-15 23:45:15 +020048 * - Copy text in the vterm to the Vim buffer once in a while, so that
49 * completion works.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +020050 * - When the job only outputs lines, we could handle resizing the terminal
51 * better: store lines separated by line breaks, instead of screen lines,
52 * then when the window is resized redraw those lines.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020053 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020054 * - For the GUI fill termios with default values, perhaps like pangoterm:
55 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar802bfb12018-04-15 17:28:13 +020056 * - When 'encoding' is not utf-8, or the job is using another encoding, setup
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020057 * conversions.
Bram Moolenaar498c2562018-04-15 23:45:15 +020058 * - Termdebug does not work when Vim build with mzscheme: gdb hangs just after
59 * "run". Everything else works, including communication channel. Not
60 * initializing mzscheme avoid the problem, thus it's not some #ifdef.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020061 */
62
63#include "vim.h"
64
65#if defined(FEAT_TERMINAL) || defined(PROTO)
66
67#ifndef MIN
68# define MIN(x,y) ((x) < (y) ? (x) : (y))
69#endif
70#ifndef MAX
71# define MAX(x,y) ((x) > (y) ? (x) : (y))
72#endif
73
74#include "libvterm/include/vterm.h"
75
76/* This is VTermScreenCell without the characters, thus much smaller. */
77typedef struct {
78 VTermScreenCellAttrs attrs;
79 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010080 VTermColor fg;
81 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020082} cellattr_T;
83
84typedef struct sb_line_S {
85 int sb_cols; /* can differ per line */
86 cellattr_T *sb_cells; /* allocated */
87 cellattr_T sb_fill_attr; /* for short line */
88} sb_line_T;
89
90/* typedef term_T in structs.h */
91struct terminal_S {
92 term_T *tl_next;
93
94 VTerm *tl_vterm;
95 job_T *tl_job;
96 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010097#if defined(FEAT_GUI)
98 int tl_system; /* when non-zero used for :!cmd output */
99 int tl_toprow; /* row with first line of system terminal */
100#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200101
102 /* Set when setting the size of a vterm, reset after redrawing. */
103 int tl_vterm_size_changed;
104
105 /* used when tl_job is NULL and only a pty was created */
106 int tl_tty_fd;
107 char_u *tl_tty_in;
108 char_u *tl_tty_out;
109
110 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
111 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100112 int tl_finish;
113#define TL_FINISH_UNSET NUL
114#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
115#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
116#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200117 char_u *tl_opencmd;
118 char_u *tl_eof_chars;
119
120#ifdef WIN3264
121 void *tl_winpty_config;
122 void *tl_winpty;
123#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100124#if defined(FEAT_SESSION)
125 char_u *tl_command;
126#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100127 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200128
129 /* last known vterm size */
130 int tl_rows;
131 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200132
133 char_u *tl_title; /* NULL or allocated */
134 char_u *tl_status_text; /* NULL or allocated */
135
136 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200137 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200138 int tl_dirty_row_end; /* row below last one to update */
139
140 garray_T tl_scrollback;
141 int tl_scrollback_scrolled;
142 cellattr_T tl_default_color;
143
Bram Moolenaard96ff162018-02-18 22:13:29 +0100144 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
145 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
146
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200147 VTermPos tl_cursor_pos;
148 int tl_cursor_visible;
149 int tl_cursor_blink;
150 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
151 char_u *tl_cursor_color; /* NULL or allocated */
152
153 int tl_using_altscreen;
154};
155
156#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
157#define TMODE_LOOP 2 /* CTRL-W N used */
158
159/*
160 * List of all active terminals.
161 */
162static term_T *first_term = NULL;
163
164/* Terminal active in terminal_loop(). */
165static term_T *in_terminal_loop = NULL;
166
167#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
168#define KEY_BUF_LEN 200
169
170/*
171 * Functions with separate implementation for MS-Windows and Unix-like systems.
172 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100173static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200174static int create_pty_only(term_T *term, jobopt_T *opt);
175static void term_report_winsize(term_T *term, int rows, int cols);
176static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100177#ifdef FEAT_GUI
178static void update_system_term(term_T *term);
179#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200180
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100181/* The character that we know (or assume) that the terminal expects for the
182 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200183static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200184
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100185/* "Terminal" highlight group colors. */
186static int term_default_cterm_fg = -1;
187static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200188
Bram Moolenaard317b382018-02-08 22:33:31 +0100189/* Store the last set and the desired cursor properties, so that we only update
190 * them when needed. Doing it unnecessary may result in flicker. */
191static char_u *last_set_cursor_color = (char_u *)"";
192static char_u *desired_cursor_color = (char_u *)"";
193static int last_set_cursor_shape = -1;
194static int desired_cursor_shape = -1;
195static int last_set_cursor_blink = -1;
196static int desired_cursor_blink = -1;
197
198
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200199/**************************************
200 * 1. Generic code for all systems.
201 */
202
203/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200204 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200205 * current window.
206 * Sets "rows" and/or "cols" to zero when it should follow the window size.
207 * Return TRUE if the size is the minimum size: "24*80".
208 */
209 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200210parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200211{
212 int minsize = FALSE;
213
214 *rows = 0;
215 *cols = 0;
216
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200217 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200218 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200219 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200220
221 /* Syntax of value was already checked when it's set. */
222 if (p == NULL)
223 {
224 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200225 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200226 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200227 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200228 *cols = atoi((char *)p + 1);
229 }
230 return minsize;
231}
232
233/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200234 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200235 */
236 static void
237set_term_and_win_size(term_T *term)
238{
Bram Moolenaar13568252018-03-16 20:46:58 +0100239#ifdef FEAT_GUI
240 if (term->tl_system)
241 {
242 /* Use the whole screen for the system command. However, it will start
243 * at the command line and scroll up as needed, using tl_toprow. */
244 term->tl_rows = Rows;
245 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200246 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100247 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100248#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200249 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200250 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200251 if (term->tl_rows != 0)
252 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
253 if (term->tl_cols != 0)
254 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200255 }
256 if (term->tl_rows == 0)
257 term->tl_rows = curwin->w_height;
258 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200259 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200260 if (term->tl_cols == 0)
261 term->tl_cols = curwin->w_width;
262 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200263 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200264}
265
266/*
267 * Initialize job options for a terminal job.
268 * Caller may overrule some of them.
269 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100270 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200271init_job_options(jobopt_T *opt)
272{
273 clear_job_options(opt);
274
275 opt->jo_mode = MODE_RAW;
276 opt->jo_out_mode = MODE_RAW;
277 opt->jo_err_mode = MODE_RAW;
278 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
279}
280
281/*
282 * Set job options mandatory for a terminal job.
283 */
284 static void
285setup_job_options(jobopt_T *opt, int rows, int cols)
286{
287 if (!(opt->jo_set & JO_OUT_IO))
288 {
289 /* Connect stdout to the terminal. */
290 opt->jo_io[PART_OUT] = JIO_BUFFER;
291 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
292 opt->jo_modifiable[PART_OUT] = 0;
293 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
294 }
295
296 if (!(opt->jo_set & JO_ERR_IO))
297 {
298 /* Connect stderr to the terminal. */
299 opt->jo_io[PART_ERR] = JIO_BUFFER;
300 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
301 opt->jo_modifiable[PART_ERR] = 0;
302 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
303 }
304
305 opt->jo_pty = TRUE;
306 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
307 opt->jo_term_rows = rows;
308 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
309 opt->jo_term_cols = cols;
310}
311
312/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100313 * Close a terminal buffer (and its window). Used when creating the terminal
314 * fails.
315 */
316 static void
317term_close_buffer(buf_T *buf, buf_T *old_curbuf)
318{
319 free_terminal(buf);
320 if (old_curbuf != NULL)
321 {
322 --curbuf->b_nwindows;
323 curbuf = old_curbuf;
324 curwin->w_buffer = curbuf;
325 ++curbuf->b_nwindows;
326 }
327
328 /* Wiping out the buffer will also close the window and call
329 * free_terminal(). */
330 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
331}
332
333/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200334 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100335 * Use either "argvar" or "argv", the other must be NULL.
336 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
337 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200338 * Returns NULL when failed.
339 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100340 buf_T *
341term_start(
342 typval_T *argvar,
343 char **argv,
344 jobopt_T *opt,
345 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200346{
347 exarg_T split_ea;
348 win_T *old_curwin = curwin;
349 term_T *term;
350 buf_T *old_curbuf = NULL;
351 int res;
352 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100353 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200354
355 if (check_restricted() || check_secure())
356 return NULL;
357
358 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
359 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
360 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
361 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
362 {
363 EMSG(_(e_invarg));
364 return NULL;
365 }
366
367 term = (term_T *)alloc_clear(sizeof(term_T));
368 if (term == NULL)
369 return NULL;
370 term->tl_dirty_row_end = MAX_ROW;
371 term->tl_cursor_visible = TRUE;
372 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
373 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100374#ifdef FEAT_GUI
375 term->tl_system = (flags & TERM_START_SYSTEM);
376#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200377 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
378
379 vim_memset(&split_ea, 0, sizeof(split_ea));
380 if (opt->jo_curwin)
381 {
382 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100383 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200384 {
385 no_write_message();
386 vim_free(term);
387 return NULL;
388 }
389 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100390 ECMD_HIDE
391 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
392 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200393 {
394 vim_free(term);
395 return NULL;
396 }
397 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100398 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200399 {
400 buf_T *buf;
401
402 /* Create a new buffer without a window. Make it the current buffer for
403 * a moment to be able to do the initialisations. */
404 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
405 BLN_NEW | BLN_LISTED);
406 if (buf == NULL || ml_open(buf) == FAIL)
407 {
408 vim_free(term);
409 return NULL;
410 }
411 old_curbuf = curbuf;
412 --curbuf->b_nwindows;
413 curbuf = buf;
414 curwin->w_buffer = buf;
415 ++curbuf->b_nwindows;
416 }
417 else
418 {
419 /* Open a new window or tab. */
420 split_ea.cmdidx = CMD_new;
421 split_ea.cmd = (char_u *)"new";
422 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100423 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200424 {
425 split_ea.line2 = opt->jo_term_rows;
426 split_ea.addr_count = 1;
427 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100428 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200429 {
430 split_ea.line2 = opt->jo_term_cols;
431 split_ea.addr_count = 1;
432 }
433
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100434 if (vertical)
435 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200436 ex_splitview(&split_ea);
437 if (curwin == old_curwin)
438 {
439 /* split failed */
440 vim_free(term);
441 return NULL;
442 }
443 }
444 term->tl_buffer = curbuf;
445 curbuf->b_term = term;
446
447 if (!opt->jo_hidden)
448 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100449 /* Only one size was taken care of with :new, do the other one. With
450 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100451 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200452 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100453 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200454 win_setwidth(opt->jo_term_cols);
455 }
456
457 /* Link the new terminal in the list of active terminals. */
458 term->tl_next = first_term;
459 first_term = term;
460
461 if (opt->jo_term_name != NULL)
462 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100463 else if (argv != NULL)
464 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200465 else
466 {
467 int i;
468 size_t len;
469 char_u *cmd, *p;
470
471 if (argvar->v_type == VAR_STRING)
472 {
473 cmd = argvar->vval.v_string;
474 if (cmd == NULL)
475 cmd = (char_u *)"";
476 else if (STRCMP(cmd, "NONE") == 0)
477 cmd = (char_u *)"pty";
478 }
479 else if (argvar->v_type != VAR_LIST
480 || argvar->vval.v_list == NULL
481 || argvar->vval.v_list->lv_len < 1
482 || (cmd = get_tv_string_chk(
483 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
484 cmd = (char_u*)"";
485
486 len = STRLEN(cmd) + 10;
487 p = alloc((int)len);
488
489 for (i = 0; p != NULL; ++i)
490 {
491 /* Prepend a ! to the command name to avoid the buffer name equals
492 * the executable, otherwise ":w!" would overwrite it. */
493 if (i == 0)
494 vim_snprintf((char *)p, len, "!%s", cmd);
495 else
496 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
497 if (buflist_findname(p) == NULL)
498 {
499 vim_free(curbuf->b_ffname);
500 curbuf->b_ffname = p;
501 break;
502 }
503 }
504 }
505 curbuf->b_fname = curbuf->b_ffname;
506
507 if (opt->jo_term_opencmd != NULL)
508 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
509
510 if (opt->jo_eof_chars != NULL)
511 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
512
513 set_string_option_direct((char_u *)"buftype", -1,
514 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
515
516 /* Mark the buffer as not modifiable. It can only be made modifiable after
517 * the job finished. */
518 curbuf->b_p_ma = FALSE;
519
520 set_term_and_win_size(term);
521 setup_job_options(opt, term->tl_rows, term->tl_cols);
522
Bram Moolenaar13568252018-03-16 20:46:58 +0100523 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100524 return curbuf;
525
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100526#if defined(FEAT_SESSION)
527 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100528 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100529 {
530 term->tl_command = vim_strsave((char_u *)"NONE");
531 }
532 else if (argvar->v_type == VAR_STRING)
533 {
534 char_u *cmd = argvar->vval.v_string;
535
536 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
537 term->tl_command = vim_strsave(cmd);
538 }
539 else if (argvar->v_type == VAR_LIST
540 && argvar->vval.v_list != NULL
541 && argvar->vval.v_list->lv_len > 0)
542 {
543 garray_T ga;
544 listitem_T *item;
545
546 ga_init2(&ga, 1, 100);
547 for (item = argvar->vval.v_list->lv_first;
548 item != NULL; item = item->li_next)
549 {
550 char_u *s = get_tv_string_chk(&item->li_tv);
551 char_u *p;
552
553 if (s == NULL)
554 break;
555 p = vim_strsave_fnameescape(s, FALSE);
556 if (p == NULL)
557 break;
558 ga_concat(&ga, p);
559 vim_free(p);
560 ga_append(&ga, ' ');
561 }
562 if (item == NULL)
563 {
564 ga_append(&ga, NUL);
565 term->tl_command = ga.ga_data;
566 }
567 else
568 ga_clear(&ga);
569 }
570#endif
571
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100572 if (opt->jo_term_kill != NULL)
573 {
574 char_u *p = skiptowhite(opt->jo_term_kill);
575
576 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
577 }
578
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200579 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100580 if (argv == NULL
581 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200582 && argvar->vval.v_string != NULL
583 && STRCMP(argvar->vval.v_string, "NONE") == 0)
584 res = create_pty_only(term, opt);
585 else
Bram Moolenaar13568252018-03-16 20:46:58 +0100586 res = term_and_job_init(term, argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200587
588 newbuf = curbuf;
589 if (res == OK)
590 {
591 /* Get and remember the size we ended up with. Update the pty. */
592 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
593 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100594#ifdef FEAT_GUI
595 if (term->tl_system)
596 {
597 /* display first line below typed command */
598 term->tl_toprow = msg_row + 1;
599 term->tl_dirty_row_end = 0;
600 }
601#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200602
603 /* Make sure we don't get stuck on sending keys to the job, it leads to
604 * a deadlock if the job is waiting for Vim to read. */
605 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
606
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200607 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200608 {
609 --curbuf->b_nwindows;
610 curbuf = old_curbuf;
611 curwin->w_buffer = curbuf;
612 ++curbuf->b_nwindows;
613 }
614 }
615 else
616 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100617 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200618 return NULL;
619 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100620
Bram Moolenaar13568252018-03-16 20:46:58 +0100621 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200622 return newbuf;
623}
624
625/*
626 * ":terminal": open a terminal window and execute a job in it.
627 */
628 void
629ex_terminal(exarg_T *eap)
630{
631 typval_T argvar[2];
632 jobopt_T opt;
633 char_u *cmd;
634 char_u *tofree = NULL;
635
636 init_job_options(&opt);
637
638 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100639 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200640 {
641 char_u *p, *ep;
642
643 cmd += 2;
644 p = skiptowhite(cmd);
645 ep = vim_strchr(cmd, '=');
646 if (ep != NULL && ep < p)
647 p = ep;
648
649 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
650 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100651 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
652 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200653 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
654 opt.jo_term_finish = 'o';
655 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
656 opt.jo_curwin = 1;
657 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
658 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100659 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
660 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100661 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
662 && ep != NULL)
663 {
664 opt.jo_set2 |= JO2_TERM_KILL;
665 opt.jo_term_kill = ep + 1;
666 p = skiptowhite(cmd);
667 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200668 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
669 && ep != NULL && isdigit(ep[1]))
670 {
671 opt.jo_set2 |= JO2_TERM_ROWS;
672 opt.jo_term_rows = atoi((char *)ep + 1);
673 p = skiptowhite(cmd);
674 }
675 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
676 && ep != NULL && isdigit(ep[1]))
677 {
678 opt.jo_set2 |= JO2_TERM_COLS;
679 opt.jo_term_cols = atoi((char *)ep + 1);
680 p = skiptowhite(cmd);
681 }
682 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
683 && ep != NULL)
684 {
685 char_u *buf = NULL;
686 char_u *keys;
687
688 p = skiptowhite(cmd);
689 *p = NUL;
690 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
691 opt.jo_set2 |= JO2_EOF_CHARS;
692 opt.jo_eof_chars = vim_strsave(keys);
693 vim_free(buf);
694 *p = ' ';
695 }
696 else
697 {
698 if (*p)
699 *p = NUL;
700 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100701 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200702 }
703 cmd = skipwhite(p);
704 }
705 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100706 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200707 /* Make a copy of 'shell', an autocommand may change the option. */
708 tofree = cmd = vim_strsave(p_sh);
709
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100710 /* default to close when the shell exits */
711 if (opt.jo_term_finish == NUL)
712 opt.jo_term_finish = 'c';
713 }
714
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200715 if (eap->addr_count > 0)
716 {
717 /* Write lines from current buffer to the job. */
718 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
719 opt.jo_io[PART_IN] = JIO_BUFFER;
720 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
721 opt.jo_in_top = eap->line1;
722 opt.jo_in_bot = eap->line2;
723 }
724
725 argvar[0].v_type = VAR_STRING;
726 argvar[0].vval.v_string = cmd;
727 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100728 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200729 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100730
731theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200732 vim_free(opt.jo_eof_chars);
733}
734
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100735#if defined(FEAT_SESSION) || defined(PROTO)
736/*
737 * Write a :terminal command to the session file to restore the terminal in
738 * window "wp".
739 * Return FAIL if writing fails.
740 */
741 int
742term_write_session(FILE *fd, win_T *wp)
743{
744 term_T *term = wp->w_buffer->b_term;
745
746 /* Create the terminal and run the command. This is not without
747 * risk, but let's assume the user only creates a session when this
748 * will be OK. */
749 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
750 term->tl_cols, term->tl_rows) < 0)
751 return FAIL;
752 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
753 return FAIL;
754
755 return put_eol(fd);
756}
757
758/*
759 * Return TRUE if "buf" has a terminal that should be restored.
760 */
761 int
762term_should_restore(buf_T *buf)
763{
764 term_T *term = buf->b_term;
765
766 return term != NULL && (term->tl_command == NULL
767 || STRCMP(term->tl_command, "NONE") != 0);
768}
769#endif
770
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200771/*
772 * Free the scrollback buffer for "term".
773 */
774 static void
775free_scrollback(term_T *term)
776{
777 int i;
778
779 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
780 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
781 ga_clear(&term->tl_scrollback);
782}
783
784/*
785 * Free a terminal and everything it refers to.
786 * Kills the job if there is one.
787 * Called when wiping out a buffer.
788 */
789 void
790free_terminal(buf_T *buf)
791{
792 term_T *term = buf->b_term;
793 term_T *tp;
794
795 if (term == NULL)
796 return;
797 if (first_term == term)
798 first_term = term->tl_next;
799 else
800 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
801 if (tp->tl_next == term)
802 {
803 tp->tl_next = term->tl_next;
804 break;
805 }
806
807 if (term->tl_job != NULL)
808 {
809 if (term->tl_job->jv_status != JOB_ENDED
810 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100811 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200812 job_stop(term->tl_job, NULL, "kill");
813 job_unref(term->tl_job);
814 }
815
816 free_scrollback(term);
817
818 term_free_vterm(term);
819 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100820#ifdef FEAT_SESSION
821 vim_free(term->tl_command);
822#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100823 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200824 vim_free(term->tl_status_text);
825 vim_free(term->tl_opencmd);
826 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100827 if (desired_cursor_color == term->tl_cursor_color)
828 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200829 vim_free(term->tl_cursor_color);
830 vim_free(term);
831 buf->b_term = NULL;
832 if (in_terminal_loop == term)
833 in_terminal_loop = NULL;
834}
835
836/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100837 * Get the part that is connected to the tty. Normally this is PART_IN, but
838 * when writing buffer lines to the job it can be another. This makes it
839 * possible to do "1,5term vim -".
840 */
841 static ch_part_T
842get_tty_part(term_T *term)
843{
844#ifdef UNIX
845 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
846 int i;
847
848 for (i = 0; i < 3; ++i)
849 {
850 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
851
852 if (isatty(fd))
853 return parts[i];
854 }
855#endif
856 return PART_IN;
857}
858
859/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200860 * Write job output "msg[len]" to the vterm.
861 */
862 static void
863term_write_job_output(term_T *term, char_u *msg, size_t len)
864{
865 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100866 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200867
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100868 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200869
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100870 /* flush vterm buffer when vterm responded to control sequence */
871 if (prevlen != vterm_output_get_buffer_current(vterm))
872 {
873 char buf[KEY_BUF_LEN];
874 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
875
876 if (curlen > 0)
877 channel_send(term->tl_job->jv_channel, get_tty_part(term),
878 (char_u *)buf, (int)curlen, NULL);
879 }
880
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200881 /* this invokes the damage callbacks */
882 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
883}
884
885 static void
886update_cursor(term_T *term, int redraw)
887{
888 if (term->tl_normal_mode)
889 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100890#ifdef FEAT_GUI
891 if (term->tl_system)
892 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
893 term->tl_cursor_pos.col);
894 else
895#endif
896 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200897 if (redraw)
898 {
899 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
900 cursor_on();
901 out_flush();
902#ifdef FEAT_GUI
903 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100904 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200905 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100906 gui_mch_flush();
907 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200908#endif
909 }
910}
911
912/*
913 * Invoked when "msg" output from a job was received. Write it to the terminal
914 * of "buffer".
915 */
916 void
917write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
918{
919 size_t len = STRLEN(msg);
920 term_T *term = buffer->b_term;
921
922 if (term->tl_vterm == NULL)
923 {
924 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
925 return;
926 }
927 ch_log(channel, "writing %d bytes to terminal", (int)len);
928 term_write_job_output(term, msg, len);
929
Bram Moolenaar13568252018-03-16 20:46:58 +0100930#ifdef FEAT_GUI
931 if (term->tl_system)
932 {
933 /* show system output, scrolling up the screen as needed */
934 update_system_term(term);
935 update_cursor(term, TRUE);
936 }
937 else
938#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200939 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
940 * contents, thus no screen update is needed. */
941 if (!term->tl_normal_mode)
942 {
943 /* TODO: only update once in a while. */
944 ch_log(term->tl_job->jv_channel, "updating screen");
945 if (buffer == curbuf)
946 {
947 update_screen(0);
948 update_cursor(term, TRUE);
949 }
950 else
951 redraw_after_callback(TRUE);
952 }
953}
954
955/*
956 * Send a mouse position and click to the vterm
957 */
958 static int
959term_send_mouse(VTerm *vterm, int button, int pressed)
960{
961 VTermModifier mod = VTERM_MOD_NONE;
962
963 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200964 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100965 if (button != 0)
966 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200967 return TRUE;
968}
969
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100970static int enter_mouse_col = -1;
971static int enter_mouse_row = -1;
972
973/*
974 * Handle a mouse click, drag or release.
975 * Return TRUE when a mouse event is sent to the terminal.
976 */
977 static int
978term_mouse_click(VTerm *vterm, int key)
979{
980#if defined(FEAT_CLIPBOARD)
981 /* For modeless selection mouse drag and release events are ignored, unless
982 * they are preceded with a mouse down event */
983 static int ignore_drag_release = TRUE;
984 VTermMouseState mouse_state;
985
986 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
987 if (mouse_state.flags == 0)
988 {
989 /* Terminal is not using the mouse, use modeless selection. */
990 switch (key)
991 {
992 case K_LEFTDRAG:
993 case K_LEFTRELEASE:
994 case K_RIGHTDRAG:
995 case K_RIGHTRELEASE:
996 /* Ignore drag and release events when the button-down wasn't
997 * seen before. */
998 if (ignore_drag_release)
999 {
1000 int save_mouse_col, save_mouse_row;
1001
1002 if (enter_mouse_col < 0)
1003 break;
1004
1005 /* mouse click in the window gave us focus, handle that
1006 * click now */
1007 save_mouse_col = mouse_col;
1008 save_mouse_row = mouse_row;
1009 mouse_col = enter_mouse_col;
1010 mouse_row = enter_mouse_row;
1011 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1012 mouse_col = save_mouse_col;
1013 mouse_row = save_mouse_row;
1014 }
1015 /* FALLTHROUGH */
1016 case K_LEFTMOUSE:
1017 case K_RIGHTMOUSE:
1018 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1019 ignore_drag_release = TRUE;
1020 else
1021 ignore_drag_release = FALSE;
1022 /* Should we call mouse_has() here? */
1023 if (clip_star.available)
1024 {
1025 int button, is_click, is_drag;
1026
1027 button = get_mouse_button(KEY2TERMCAP1(key),
1028 &is_click, &is_drag);
1029 if (mouse_model_popup() && button == MOUSE_LEFT
1030 && (mod_mask & MOD_MASK_SHIFT))
1031 {
1032 /* Translate shift-left to right button. */
1033 button = MOUSE_RIGHT;
1034 mod_mask &= ~MOD_MASK_SHIFT;
1035 }
1036 clip_modeless(button, is_click, is_drag);
1037 }
1038 break;
1039
1040 case K_MIDDLEMOUSE:
1041 if (clip_star.available)
1042 insert_reg('*', TRUE);
1043 break;
1044 }
1045 enter_mouse_col = -1;
1046 return FALSE;
1047 }
1048#endif
1049 enter_mouse_col = -1;
1050
1051 switch (key)
1052 {
1053 case K_LEFTMOUSE:
1054 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1055 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1056 case K_LEFTRELEASE:
1057 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1058 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1059 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1060 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1061 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1062 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1063 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1064 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1065 }
1066 return TRUE;
1067}
1068
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001069/*
1070 * Convert typed key "c" into bytes to send to the job.
1071 * Return the number of bytes in "buf".
1072 */
1073 static int
1074term_convert_key(term_T *term, int c, char *buf)
1075{
1076 VTerm *vterm = term->tl_vterm;
1077 VTermKey key = VTERM_KEY_NONE;
1078 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001079 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001080
1081 switch (c)
1082 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001083 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1084
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001085 /* don't use VTERM_KEY_BACKSPACE, it always
1086 * becomes 0x7f DEL */
1087 case K_BS: c = term_backspace_char; break;
1088
1089 case ESC: key = VTERM_KEY_ESCAPE; break;
1090 case K_DEL: key = VTERM_KEY_DEL; break;
1091 case K_DOWN: key = VTERM_KEY_DOWN; break;
1092 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1093 key = VTERM_KEY_DOWN; break;
1094 case K_END: key = VTERM_KEY_END; break;
1095 case K_S_END: mod = VTERM_MOD_SHIFT;
1096 key = VTERM_KEY_END; break;
1097 case K_C_END: mod = VTERM_MOD_CTRL;
1098 key = VTERM_KEY_END; break;
1099 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1100 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1101 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1102 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1103 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1104 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1105 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1106 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1107 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1108 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1109 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1110 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1111 case K_HOME: key = VTERM_KEY_HOME; break;
1112 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1113 key = VTERM_KEY_HOME; break;
1114 case K_C_HOME: mod = VTERM_MOD_CTRL;
1115 key = VTERM_KEY_HOME; break;
1116 case K_INS: key = VTERM_KEY_INS; break;
1117 case K_K0: key = VTERM_KEY_KP_0; break;
1118 case K_K1: key = VTERM_KEY_KP_1; break;
1119 case K_K2: key = VTERM_KEY_KP_2; break;
1120 case K_K3: key = VTERM_KEY_KP_3; break;
1121 case K_K4: key = VTERM_KEY_KP_4; break;
1122 case K_K5: key = VTERM_KEY_KP_5; break;
1123 case K_K6: key = VTERM_KEY_KP_6; break;
1124 case K_K7: key = VTERM_KEY_KP_7; break;
1125 case K_K8: key = VTERM_KEY_KP_8; break;
1126 case K_K9: key = VTERM_KEY_KP_9; break;
1127 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1128 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1129 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1130 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1131 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1132 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1133 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1134 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1135 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1136 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1137 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1138 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1139 case K_LEFT: key = VTERM_KEY_LEFT; break;
1140 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1141 key = VTERM_KEY_LEFT; break;
1142 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1143 key = VTERM_KEY_LEFT; break;
1144 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1145 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1146 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1147 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1148 key = VTERM_KEY_RIGHT; break;
1149 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1150 key = VTERM_KEY_RIGHT; break;
1151 case K_UP: key = VTERM_KEY_UP; break;
1152 case K_S_UP: mod = VTERM_MOD_SHIFT;
1153 key = VTERM_KEY_UP; break;
1154 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001155 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1156 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001157
Bram Moolenaara42ad572017-11-16 13:08:04 +01001158 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1159 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001160 case K_MOUSELEFT: /* TODO */ return 0;
1161 case K_MOUSERIGHT: /* TODO */ return 0;
1162
1163 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001164 case K_LEFTMOUSE_NM:
1165 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001166 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001167 case K_LEFTRELEASE_NM:
1168 case K_MOUSEMOVE:
1169 case K_MIDDLEMOUSE:
1170 case K_MIDDLEDRAG:
1171 case K_MIDDLERELEASE:
1172 case K_RIGHTMOUSE:
1173 case K_RIGHTDRAG:
1174 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1175 return 0;
1176 other = TRUE;
1177 break;
1178
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001179 case K_X1MOUSE: /* TODO */ return 0;
1180 case K_X1DRAG: /* TODO */ return 0;
1181 case K_X1RELEASE: /* TODO */ return 0;
1182 case K_X2MOUSE: /* TODO */ return 0;
1183 case K_X2DRAG: /* TODO */ return 0;
1184 case K_X2RELEASE: /* TODO */ return 0;
1185
1186 case K_IGNORE: return 0;
1187 case K_NOP: return 0;
1188 case K_UNDO: return 0;
1189 case K_HELP: return 0;
1190 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1191 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1192 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1193 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1194 case K_SELECT: return 0;
1195#ifdef FEAT_GUI
1196 case K_VER_SCROLLBAR: return 0;
1197 case K_HOR_SCROLLBAR: return 0;
1198#endif
1199#ifdef FEAT_GUI_TABLINE
1200 case K_TABLINE: return 0;
1201 case K_TABMENU: return 0;
1202#endif
1203#ifdef FEAT_NETBEANS_INTG
1204 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1205#endif
1206#ifdef FEAT_DND
1207 case K_DROP: return 0;
1208#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001209 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001210 case K_PS: vterm_keyboard_start_paste(vterm);
1211 other = TRUE;
1212 break;
1213 case K_PE: vterm_keyboard_end_paste(vterm);
1214 other = TRUE;
1215 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001216 }
1217
1218 /*
1219 * Convert special keys to vterm keys:
1220 * - Write keys to vterm: vterm_keyboard_key()
1221 * - Write output to channel.
1222 * TODO: use mod_mask
1223 */
1224 if (key != VTERM_KEY_NONE)
1225 /* Special key, let vterm convert it. */
1226 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001227 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001228 /* Normal character, let vterm convert it. */
1229 vterm_keyboard_unichar(vterm, c, mod);
1230
1231 /* Read back the converted escape sequence. */
1232 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1233}
1234
1235/*
1236 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001237 * If "check_job_status" is TRUE update the job status.
1238 */
1239 static int
1240term_job_running_check(term_T *term, int check_job_status)
1241{
1242 /* Also consider the job finished when the channel is closed, to avoid a
1243 * race condition when updating the title. */
1244 if (term != NULL
1245 && term->tl_job != NULL
1246 && channel_is_open(term->tl_job->jv_channel))
1247 {
1248 if (check_job_status)
1249 job_status(term->tl_job);
1250 return (term->tl_job->jv_status == JOB_STARTED
1251 || term->tl_job->jv_channel->ch_keep_open);
1252 }
1253 return FALSE;
1254}
1255
1256/*
1257 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001258 */
1259 int
1260term_job_running(term_T *term)
1261{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001262 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001263}
1264
1265/*
1266 * Return TRUE if "term" has an active channel and used ":term NONE".
1267 */
1268 int
1269term_none_open(term_T *term)
1270{
1271 /* Also consider the job finished when the channel is closed, to avoid a
1272 * race condition when updating the title. */
1273 return term != NULL
1274 && term->tl_job != NULL
1275 && channel_is_open(term->tl_job->jv_channel)
1276 && term->tl_job->jv_channel->ch_keep_open;
1277}
1278
1279/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001280 * Used when exiting: kill the job in "buf" if so desired.
1281 * Return OK when the job finished.
1282 * Return FAIL when the job is still running.
1283 */
1284 int
1285term_try_stop_job(buf_T *buf)
1286{
1287 int count;
1288 char *how = (char *)buf->b_term->tl_kill;
1289
1290#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1291 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1292 {
1293 char_u buff[DIALOG_MSG_SIZE];
1294 int ret;
1295
1296 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1297 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1298 if (ret == VIM_YES)
1299 how = "kill";
1300 else if (ret == VIM_CANCEL)
1301 return FAIL;
1302 }
1303#endif
1304 if (how == NULL || *how == NUL)
1305 return FAIL;
1306
1307 job_stop(buf->b_term->tl_job, NULL, how);
1308
1309 /* wait for up to a second for the job to die */
1310 for (count = 0; count < 100; ++count)
1311 {
1312 /* buffer, terminal and job may be cleaned up while waiting */
1313 if (!buf_valid(buf)
1314 || buf->b_term == NULL
1315 || buf->b_term->tl_job == NULL)
1316 return OK;
1317
1318 /* call job_status() to update jv_status */
1319 job_status(buf->b_term->tl_job);
1320 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1321 return OK;
1322 ui_delay(10L, FALSE);
1323 mch_check_messages();
1324 parse_queued_messages();
1325 }
1326 return FAIL;
1327}
1328
1329/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001330 * Add the last line of the scrollback buffer to the buffer in the window.
1331 */
1332 static void
1333add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1334{
1335 buf_T *buf = term->tl_buffer;
1336 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1337 linenr_T lnum = buf->b_ml.ml_line_count;
1338
1339#ifdef WIN3264
1340 if (!enc_utf8 && enc_codepage > 0)
1341 {
1342 WCHAR *ret = NULL;
1343 int length = 0;
1344
1345 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1346 &ret, &length);
1347 if (ret != NULL)
1348 {
1349 WideCharToMultiByte_alloc(enc_codepage, 0,
1350 ret, length, (char **)&text, &len, 0, 0);
1351 vim_free(ret);
1352 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1353 vim_free(text);
1354 }
1355 }
1356 else
1357#endif
1358 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1359 if (empty)
1360 {
1361 /* Delete the empty line that was in the empty buffer. */
1362 curbuf = buf;
1363 ml_delete(1, FALSE);
1364 curbuf = curwin->w_buffer;
1365 }
1366}
1367
1368 static void
1369cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1370{
1371 attr->width = cell->width;
1372 attr->attrs = cell->attrs;
1373 attr->fg = cell->fg;
1374 attr->bg = cell->bg;
1375}
1376
1377 static int
1378equal_celattr(cellattr_T *a, cellattr_T *b)
1379{
1380 /* Comparing the colors should be sufficient. */
1381 return a->fg.red == b->fg.red
1382 && a->fg.green == b->fg.green
1383 && a->fg.blue == b->fg.blue
1384 && a->bg.red == b->bg.red
1385 && a->bg.green == b->bg.green
1386 && a->bg.blue == b->bg.blue;
1387}
1388
Bram Moolenaard96ff162018-02-18 22:13:29 +01001389/*
1390 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1391 * line at this position. Otherwise at the end.
1392 */
1393 static int
1394add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1395{
1396 if (ga_grow(&term->tl_scrollback, 1) == OK)
1397 {
1398 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1399 + term->tl_scrollback.ga_len;
1400
1401 if (lnum > 0)
1402 {
1403 int i;
1404
1405 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1406 {
1407 *line = *(line - 1);
1408 --line;
1409 }
1410 }
1411 line->sb_cols = 0;
1412 line->sb_cells = NULL;
1413 line->sb_fill_attr = *fill_attr;
1414 ++term->tl_scrollback.ga_len;
1415 return OK;
1416 }
1417 return FALSE;
1418}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001419
1420/*
1421 * Add the current lines of the terminal to scrollback and to the buffer.
1422 * Called after the job has ended and when switching to Terminal-Normal mode.
1423 */
1424 static void
1425move_terminal_to_buffer(term_T *term)
1426{
1427 win_T *wp;
1428 int len;
1429 int lines_skipped = 0;
1430 VTermPos pos;
1431 VTermScreenCell cell;
1432 cellattr_T fill_attr, new_fill_attr;
1433 cellattr_T *p;
1434 VTermScreen *screen;
1435
1436 if (term->tl_vterm == NULL)
1437 return;
1438 screen = vterm_obtain_screen(term->tl_vterm);
1439 fill_attr = new_fill_attr = term->tl_default_color;
1440
1441 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1442 {
1443 len = 0;
1444 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1445 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1446 && cell.chars[0] != NUL)
1447 {
1448 len = pos.col + 1;
1449 new_fill_attr = term->tl_default_color;
1450 }
1451 else
1452 /* Assume the last attr is the filler attr. */
1453 cell2cellattr(&cell, &new_fill_attr);
1454
1455 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1456 ++lines_skipped;
1457 else
1458 {
1459 while (lines_skipped > 0)
1460 {
1461 /* Line was skipped, add an empty line. */
1462 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001463 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001464 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001465 }
1466
1467 if (len == 0)
1468 p = NULL;
1469 else
1470 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1471 if ((p != NULL || len == 0)
1472 && ga_grow(&term->tl_scrollback, 1) == OK)
1473 {
1474 garray_T ga;
1475 int width;
1476 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1477 + term->tl_scrollback.ga_len;
1478
1479 ga_init2(&ga, 1, 100);
1480 for (pos.col = 0; pos.col < len; pos.col += width)
1481 {
1482 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1483 {
1484 width = 1;
1485 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1486 if (ga_grow(&ga, 1) == OK)
1487 ga.ga_len += utf_char2bytes(' ',
1488 (char_u *)ga.ga_data + ga.ga_len);
1489 }
1490 else
1491 {
1492 width = cell.width;
1493
1494 cell2cellattr(&cell, &p[pos.col]);
1495
1496 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1497 {
1498 int i;
1499 int c;
1500
1501 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1502 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1503 (char_u *)ga.ga_data + ga.ga_len);
1504 }
1505 }
1506 }
1507 line->sb_cols = len;
1508 line->sb_cells = p;
1509 line->sb_fill_attr = new_fill_attr;
1510 fill_attr = new_fill_attr;
1511 ++term->tl_scrollback.ga_len;
1512
1513 if (ga_grow(&ga, 1) == FAIL)
1514 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1515 else
1516 {
1517 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1518 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1519 }
1520 ga_clear(&ga);
1521 }
1522 else
1523 vim_free(p);
1524 }
1525 }
1526
1527 /* Obtain the current background color. */
1528 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1529 &term->tl_default_color.fg, &term->tl_default_color.bg);
1530
1531 FOR_ALL_WINDOWS(wp)
1532 {
1533 if (wp->w_buffer == term->tl_buffer)
1534 {
1535 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1536 wp->w_cursor.col = 0;
1537 wp->w_valid = 0;
1538 if (wp->w_cursor.lnum >= wp->w_height)
1539 {
1540 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1541
1542 if (wp->w_topline < min_topline)
1543 wp->w_topline = min_topline;
1544 }
1545 redraw_win_later(wp, NOT_VALID);
1546 }
1547 }
1548}
1549
1550 static void
1551set_terminal_mode(term_T *term, int normal_mode)
1552{
1553 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001554 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001555 if (term->tl_buffer == curbuf)
1556 maketitle();
1557}
1558
1559/*
1560 * Called after the job if finished and Terminal mode is not active:
1561 * Move the vterm contents into the scrollback buffer and free the vterm.
1562 */
1563 static void
1564cleanup_vterm(term_T *term)
1565{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001566 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001567 move_terminal_to_buffer(term);
1568 term_free_vterm(term);
1569 set_terminal_mode(term, FALSE);
1570}
1571
1572/*
1573 * Switch from Terminal-Job mode to Terminal-Normal mode.
1574 * Suspends updating the terminal window.
1575 */
1576 static void
1577term_enter_normal_mode(void)
1578{
1579 term_T *term = curbuf->b_term;
1580
1581 /* Append the current terminal contents to the buffer. */
1582 move_terminal_to_buffer(term);
1583
1584 set_terminal_mode(term, TRUE);
1585
1586 /* Move the window cursor to the position of the cursor in the
1587 * terminal. */
1588 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1589 + term->tl_cursor_pos.row + 1;
1590 check_cursor();
1591 coladvance(term->tl_cursor_pos.col);
1592
1593 /* Display the same lines as in the terminal. */
1594 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1595}
1596
1597/*
1598 * Returns TRUE if the current window contains a terminal and we are in
1599 * Terminal-Normal mode.
1600 */
1601 int
1602term_in_normal_mode(void)
1603{
1604 term_T *term = curbuf->b_term;
1605
1606 return term != NULL && term->tl_normal_mode;
1607}
1608
1609/*
1610 * Switch from Terminal-Normal mode to Terminal-Job mode.
1611 * Restores updating the terminal window.
1612 */
1613 void
1614term_enter_job_mode()
1615{
1616 term_T *term = curbuf->b_term;
1617 sb_line_T *line;
1618 garray_T *gap;
1619
1620 /* Remove the terminal contents from the scrollback and the buffer. */
1621 gap = &term->tl_scrollback;
1622 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1623 && gap->ga_len > 0)
1624 {
1625 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1626 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1627 vim_free(line->sb_cells);
1628 --gap->ga_len;
1629 }
1630 check_cursor();
1631
1632 set_terminal_mode(term, FALSE);
1633
1634 if (term->tl_channel_closed)
1635 cleanup_vterm(term);
1636 redraw_buf_and_status_later(curbuf, NOT_VALID);
1637}
1638
1639/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001640 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001641 * Note: while waiting a terminal may be closed and freed if the channel is
1642 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001643 */
1644 static int
1645term_vgetc()
1646{
1647 int c;
1648 int save_State = State;
1649
1650 State = TERMINAL;
1651 got_int = FALSE;
1652#ifdef WIN3264
1653 ctrl_break_was_pressed = FALSE;
1654#endif
1655 c = vgetc();
1656 got_int = FALSE;
1657 State = save_State;
1658 return c;
1659}
1660
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001661static int mouse_was_outside = FALSE;
1662
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001663/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001664 * Send keys to terminal.
1665 * Return FAIL when the key needs to be handled in Normal mode.
1666 * Return OK when the key was dropped or sent to the terminal.
1667 */
1668 int
1669send_keys_to_term(term_T *term, int c, int typed)
1670{
1671 char msg[KEY_BUF_LEN];
1672 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001673 int dragging_outside = FALSE;
1674
1675 /* Catch keys that need to be handled as in Normal mode. */
1676 switch (c)
1677 {
1678 case NUL:
1679 case K_ZERO:
1680 if (typed)
1681 stuffcharReadbuff(c);
1682 return FAIL;
1683
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001684 case K_TABLINE:
1685 stuffcharReadbuff(c);
1686 return FAIL;
1687
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001688 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001689 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001690 return FAIL;
1691
1692 case K_LEFTDRAG:
1693 case K_MIDDLEDRAG:
1694 case K_RIGHTDRAG:
1695 case K_X1DRAG:
1696 case K_X2DRAG:
1697 dragging_outside = mouse_was_outside;
1698 /* FALLTHROUGH */
1699 case K_LEFTMOUSE:
1700 case K_LEFTMOUSE_NM:
1701 case K_LEFTRELEASE:
1702 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001703 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001704 case K_MIDDLEMOUSE:
1705 case K_MIDDLERELEASE:
1706 case K_RIGHTMOUSE:
1707 case K_RIGHTRELEASE:
1708 case K_X1MOUSE:
1709 case K_X1RELEASE:
1710 case K_X2MOUSE:
1711 case K_X2RELEASE:
1712
1713 case K_MOUSEUP:
1714 case K_MOUSEDOWN:
1715 case K_MOUSELEFT:
1716 case K_MOUSERIGHT:
1717 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001718 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001719 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001720 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001721 || dragging_outside)
1722 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001723 /* click or scroll outside the current window or on status line
1724 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001725 if (typed)
1726 {
1727 stuffcharReadbuff(c);
1728 mouse_was_outside = TRUE;
1729 }
1730 return FAIL;
1731 }
1732 }
1733 if (typed)
1734 mouse_was_outside = FALSE;
1735
1736 /* Convert the typed key to a sequence of bytes for the job. */
1737 len = term_convert_key(term, c, msg);
1738 if (len > 0)
1739 /* TODO: if FAIL is returned, stop? */
1740 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1741 (char_u *)msg, (int)len, NULL);
1742
1743 return OK;
1744}
1745
1746 static void
1747position_cursor(win_T *wp, VTermPos *pos)
1748{
1749 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1750 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1751 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1752}
1753
1754/*
1755 * Handle CTRL-W "": send register contents to the job.
1756 */
1757 static void
1758term_paste_register(int prev_c UNUSED)
1759{
1760 int c;
1761 list_T *l;
1762 listitem_T *item;
1763 long reglen = 0;
1764 int type;
1765
1766#ifdef FEAT_CMDL_INFO
1767 if (add_to_showcmd(prev_c))
1768 if (add_to_showcmd('"'))
1769 out_flush();
1770#endif
1771 c = term_vgetc();
1772#ifdef FEAT_CMDL_INFO
1773 clear_showcmd();
1774#endif
1775 if (!term_use_loop())
1776 /* job finished while waiting for a character */
1777 return;
1778
1779 /* CTRL-W "= prompt for expression to evaluate. */
1780 if (c == '=' && get_expr_register() != '=')
1781 return;
1782 if (!term_use_loop())
1783 /* job finished while waiting for a character */
1784 return;
1785
1786 l = (list_T *)get_reg_contents(c, GREG_LIST);
1787 if (l != NULL)
1788 {
1789 type = get_reg_type(c, &reglen);
1790 for (item = l->lv_first; item != NULL; item = item->li_next)
1791 {
1792 char_u *s = get_tv_string(&item->li_tv);
1793#ifdef WIN3264
1794 char_u *tmp = s;
1795
1796 if (!enc_utf8 && enc_codepage > 0)
1797 {
1798 WCHAR *ret = NULL;
1799 int length = 0;
1800
1801 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1802 (int)STRLEN(s), &ret, &length);
1803 if (ret != NULL)
1804 {
1805 WideCharToMultiByte_alloc(CP_UTF8, 0,
1806 ret, length, (char **)&s, &length, 0, 0);
1807 vim_free(ret);
1808 }
1809 }
1810#endif
1811 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1812 s, (int)STRLEN(s), NULL);
1813#ifdef WIN3264
1814 if (tmp != s)
1815 vim_free(s);
1816#endif
1817
1818 if (item->li_next != NULL || type == MLINE)
1819 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1820 (char_u *)"\r", 1, NULL);
1821 }
1822 list_free(l);
1823 }
1824}
1825
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001826/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001827 * Return TRUE when waiting for a character in the terminal, the cursor of the
1828 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001829 */
1830 int
1831terminal_is_active()
1832{
1833 return in_terminal_loop != NULL;
1834}
1835
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001836#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001837 cursorentry_T *
1838term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1839{
1840 term_T *term = in_terminal_loop;
1841 static cursorentry_T entry;
1842
1843 vim_memset(&entry, 0, sizeof(entry));
1844 entry.shape = entry.mshape =
1845 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1846 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1847 SHAPE_BLOCK;
1848 entry.percentage = 20;
1849 if (term->tl_cursor_blink)
1850 {
1851 entry.blinkwait = 700;
1852 entry.blinkon = 400;
1853 entry.blinkoff = 250;
1854 }
1855 *fg = gui.back_pixel;
1856 if (term->tl_cursor_color == NULL)
1857 *bg = gui.norm_pixel;
1858 else
1859 *bg = color_name2handle(term->tl_cursor_color);
1860 entry.name = "n";
1861 entry.used_for = SHAPE_CURSOR;
1862
1863 return &entry;
1864}
1865#endif
1866
Bram Moolenaard317b382018-02-08 22:33:31 +01001867 static void
1868may_output_cursor_props(void)
1869{
1870 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1871 || last_set_cursor_shape != desired_cursor_shape
1872 || last_set_cursor_blink != desired_cursor_blink)
1873 {
1874 last_set_cursor_color = desired_cursor_color;
1875 last_set_cursor_shape = desired_cursor_shape;
1876 last_set_cursor_blink = desired_cursor_blink;
1877 term_cursor_color(desired_cursor_color);
1878 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1879 /* this will restore the initial cursor style, if possible */
1880 ui_cursor_shape_forced(TRUE);
1881 else
1882 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1883 }
1884}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001885
Bram Moolenaard317b382018-02-08 22:33:31 +01001886/*
1887 * Set the cursor color and shape, if not last set to these.
1888 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001889 static void
1890may_set_cursor_props(term_T *term)
1891{
1892#ifdef FEAT_GUI
1893 /* For the GUI the cursor properties are obtained with
1894 * term_get_cursor_shape(). */
1895 if (gui.in_use)
1896 return;
1897#endif
1898 if (in_terminal_loop == term)
1899 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001900 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001901 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001902 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001903 desired_cursor_color = (char_u *)"";
1904 desired_cursor_shape = term->tl_cursor_shape;
1905 desired_cursor_blink = term->tl_cursor_blink;
1906 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001907 }
1908}
1909
Bram Moolenaard317b382018-02-08 22:33:31 +01001910/*
1911 * Reset the desired cursor properties and restore them when needed.
1912 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001913 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001914prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001915{
1916#ifdef FEAT_GUI
1917 if (gui.in_use)
1918 return;
1919#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001920 desired_cursor_color = (char_u *)"";
1921 desired_cursor_shape = -1;
1922 desired_cursor_blink = -1;
1923 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001924}
1925
1926/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001927 * Returns TRUE if the current window contains a terminal and we are sending
1928 * keys to the job.
1929 * If "check_job_status" is TRUE update the job status.
1930 */
1931 static int
1932term_use_loop_check(int check_job_status)
1933{
1934 term_T *term = curbuf->b_term;
1935
1936 return term != NULL
1937 && !term->tl_normal_mode
1938 && term->tl_vterm != NULL
1939 && term_job_running_check(term, check_job_status);
1940}
1941
1942/*
1943 * Returns TRUE if the current window contains a terminal and we are sending
1944 * keys to the job.
1945 */
1946 int
1947term_use_loop(void)
1948{
1949 return term_use_loop_check(FALSE);
1950}
1951
1952/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001953 * Called when entering a window with the mouse. If this is a terminal window
1954 * we may want to change state.
1955 */
1956 void
1957term_win_entered()
1958{
1959 term_T *term = curbuf->b_term;
1960
1961 if (term != NULL)
1962 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001963 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001964 {
1965 reset_VIsual_and_resel();
1966 if (State & INSERT)
1967 stop_insert_mode = TRUE;
1968 }
1969 mouse_was_outside = FALSE;
1970 enter_mouse_col = mouse_col;
1971 enter_mouse_row = mouse_row;
1972 }
1973}
1974
1975/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001976 * Wait for input and send it to the job.
1977 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1978 * when there is no more typahead.
1979 * Return when the start of a CTRL-W command is typed or anything else that
1980 * should be handled as a Normal mode command.
1981 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1982 * the terminal was closed.
1983 */
1984 int
1985terminal_loop(int blocking)
1986{
1987 int c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02001988 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001989 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001990#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001991 int tty_fd = curbuf->b_term->tl_job->jv_channel
1992 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001993#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001994 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001995
1996 /* Remember the terminal we are sending keys to. However, the terminal
1997 * might be closed while waiting for a character, e.g. typing "exit" in a
1998 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1999 * stored reference. */
2000 in_terminal_loop = curbuf->b_term;
2001
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002002 if (*curwin->w_p_twk != NUL)
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002003 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002004 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2005 may_set_cursor_props(curbuf->b_term);
2006
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002007 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002008 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002009#ifdef FEAT_GUI
2010 if (!curbuf->b_term->tl_system)
2011#endif
2012 /* TODO: skip screen update when handling a sequence of keys. */
2013 /* Repeat redrawing in case a message is received while redrawing.
2014 */
2015 while (must_redraw != 0)
2016 if (update_screen(0) == FAIL)
2017 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002018 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002019 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002020
2021 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002022 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002023 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002024 /* Job finished while waiting for a character. Push back the
2025 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002026 if (c != K_IGNORE)
2027 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002028 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002029 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002030 if (c == K_IGNORE)
2031 continue;
2032
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002033#ifdef UNIX
2034 /*
2035 * The shell or another program may change the tty settings. Getting
2036 * them for every typed character is a bit of overhead, but it's needed
2037 * for the first character typed, e.g. when Vim starts in a shell.
2038 */
2039 if (isatty(tty_fd))
2040 {
2041 ttyinfo_T info;
2042
2043 /* Get the current backspace character of the pty. */
2044 if (get_tty_info(tty_fd, &info) == OK)
2045 term_backspace_char = info.backspace;
2046 }
2047#endif
2048
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002049#ifdef WIN3264
2050 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2051 * Use CTRL-BREAK to kill the job. */
2052 if (ctrl_break_was_pressed)
2053 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2054#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002055 /* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002056 * Not in a system terminal. */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002057 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002058#ifdef FEAT_GUI
2059 && !curbuf->b_term->tl_system
2060#endif
2061 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002062 {
2063 int prev_c = c;
2064
2065#ifdef FEAT_CMDL_INFO
2066 if (add_to_showcmd(c))
2067 out_flush();
2068#endif
2069 c = term_vgetc();
2070#ifdef FEAT_CMDL_INFO
2071 clear_showcmd();
2072#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002073 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002074 /* job finished while waiting for a character */
2075 break;
2076
2077 if (prev_c == Ctrl_BSL)
2078 {
2079 if (c == Ctrl_N)
2080 {
2081 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2082 term_enter_normal_mode();
2083 ret = FAIL;
2084 goto theend;
2085 }
2086 /* Send both keys to the terminal. */
2087 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2088 }
2089 else if (c == Ctrl_C)
2090 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002091 /* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002092 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2093 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002094 else if (termwinkey == 0 && c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002095 {
2096 /* "CTRL-W .": send CTRL-W to the job */
2097 c = Ctrl_W;
2098 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002099 else if (termwinkey == 0 && c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002100 {
2101 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2102 c = Ctrl_BSL;
2103 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002104 else if (c == 'N')
2105 {
2106 /* CTRL-W N : go to Terminal-Normal mode. */
2107 term_enter_normal_mode();
2108 ret = FAIL;
2109 goto theend;
2110 }
2111 else if (c == '"')
2112 {
2113 term_paste_register(prev_c);
2114 continue;
2115 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002116 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002117 {
2118 stuffcharReadbuff(Ctrl_W);
2119 stuffcharReadbuff(c);
2120 ret = OK;
2121 goto theend;
2122 }
2123 }
2124# ifdef WIN3264
2125 if (!enc_utf8 && has_mbyte && c >= 0x80)
2126 {
2127 WCHAR wc;
2128 char_u mb[3];
2129
2130 mb[0] = (unsigned)c >> 8;
2131 mb[1] = c;
2132 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2133 c = wc;
2134 }
2135# endif
2136 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2137 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002138 if (c == K_MOUSEMOVE)
2139 /* We are sure to come back here, don't reset the cursor color
2140 * and shape to avoid flickering. */
2141 restore_cursor = FALSE;
2142
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002143 ret = OK;
2144 goto theend;
2145 }
2146 }
2147 ret = FAIL;
2148
2149theend:
2150 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002151 if (restore_cursor)
2152 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002153 return ret;
2154}
2155
2156/*
2157 * Called when a job has finished.
2158 * This updates the title and status, but does not close the vterm, because
2159 * there might still be pending output in the channel.
2160 */
2161 void
2162term_job_ended(job_T *job)
2163{
2164 term_T *term;
2165 int did_one = FALSE;
2166
2167 for (term = first_term; term != NULL; term = term->tl_next)
2168 if (term->tl_job == job)
2169 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002170 VIM_CLEAR(term->tl_title);
2171 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002172 redraw_buf_and_status_later(term->tl_buffer, VALID);
2173 did_one = TRUE;
2174 }
2175 if (did_one)
2176 redraw_statuslines();
2177 if (curbuf->b_term != NULL)
2178 {
2179 if (curbuf->b_term->tl_job == job)
2180 maketitle();
2181 update_cursor(curbuf->b_term, TRUE);
2182 }
2183}
2184
2185 static void
2186may_toggle_cursor(term_T *term)
2187{
2188 if (in_terminal_loop == term)
2189 {
2190 if (term->tl_cursor_visible)
2191 cursor_on();
2192 else
2193 cursor_off();
2194 }
2195}
2196
2197/*
2198 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002199 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002200 */
2201 static int
2202color2index(VTermColor *color, int fg, int *boldp)
2203{
2204 int red = color->red;
2205 int blue = color->blue;
2206 int green = color->green;
2207
Bram Moolenaar46359e12017-11-29 22:33:38 +01002208 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002209 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002210 /* First 16 colors and default: use the ANSI index, because these
2211 * colors can be redefined. */
2212 if (t_colors >= 16)
2213 return color->ansi_index;
2214 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002215 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002216 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002217 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002218 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2219 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2220 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002221 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002222 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2223 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2224 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2225 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2226 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2227 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2228 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2229 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2230 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2231 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2232 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002233 }
2234 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002235
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002236 if (t_colors >= 256)
2237 {
2238 if (red == blue && red == green)
2239 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002240 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002241 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002242 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2243 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2244 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002245 int i;
2246
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002247 if (red < 5)
2248 return 17; /* 00/00/00 */
2249 if (red > 245) /* ff/ff/ff */
2250 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002251 for (i = 0; i < 23; ++i)
2252 if (red < cutoff[i])
2253 return i + 233;
2254 return 256;
2255 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002256 {
2257 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2258 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002259
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002260 /* 216-color cube */
2261 for (ri = 0; ri < 5; ++ri)
2262 if (red < cutoff[ri])
2263 break;
2264 for (gi = 0; gi < 5; ++gi)
2265 if (green < cutoff[gi])
2266 break;
2267 for (bi = 0; bi < 5; ++bi)
2268 if (blue < cutoff[bi])
2269 break;
2270 return 17 + ri * 36 + gi * 6 + bi;
2271 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002272 }
2273 return 0;
2274}
2275
2276/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002277 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002278 */
2279 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002280vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002281{
2282 int attr = 0;
2283
2284 if (cellattrs.bold)
2285 attr |= HL_BOLD;
2286 if (cellattrs.underline)
2287 attr |= HL_UNDERLINE;
2288 if (cellattrs.italic)
2289 attr |= HL_ITALIC;
2290 if (cellattrs.strike)
2291 attr |= HL_STRIKETHROUGH;
2292 if (cellattrs.reverse)
2293 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002294 return attr;
2295}
2296
2297/*
2298 * Store Vterm attributes in "cell" from highlight flags.
2299 */
2300 static void
2301hl2vtermAttr(int attr, cellattr_T *cell)
2302{
2303 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2304 if (attr & HL_BOLD)
2305 cell->attrs.bold = 1;
2306 if (attr & HL_UNDERLINE)
2307 cell->attrs.underline = 1;
2308 if (attr & HL_ITALIC)
2309 cell->attrs.italic = 1;
2310 if (attr & HL_STRIKETHROUGH)
2311 cell->attrs.strike = 1;
2312 if (attr & HL_INVERSE)
2313 cell->attrs.reverse = 1;
2314}
2315
2316/*
2317 * Convert the attributes of a vterm cell into an attribute index.
2318 */
2319 static int
2320cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2321{
2322 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002323
2324#ifdef FEAT_GUI
2325 if (gui.in_use)
2326 {
2327 guicolor_T fg, bg;
2328
2329 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2330 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2331 return get_gui_attr_idx(attr, fg, bg);
2332 }
2333 else
2334#endif
2335#ifdef FEAT_TERMGUICOLORS
2336 if (p_tgc)
2337 {
2338 guicolor_T fg, bg;
2339
2340 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2341 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2342
2343 return get_tgc_attr_idx(attr, fg, bg);
2344 }
2345 else
2346#endif
2347 {
2348 int bold = MAYBE;
2349 int fg = color2index(&cellfg, TRUE, &bold);
2350 int bg = color2index(&cellbg, FALSE, &bold);
2351
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002352 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002353 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002354 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002355 if (fg == 0 && term_default_cterm_fg >= 0)
2356 fg = term_default_cterm_fg + 1;
2357 if (bg == 0 && term_default_cterm_bg >= 0)
2358 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002359 }
2360
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002361 /* with 8 colors set the bold attribute to get a bright foreground */
2362 if (bold == TRUE)
2363 attr |= HL_BOLD;
2364 return get_cterm_attr_idx(attr, fg, bg);
2365 }
2366 return 0;
2367}
2368
2369 static int
2370handle_damage(VTermRect rect, void *user)
2371{
2372 term_T *term = (term_T *)user;
2373
2374 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2375 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2376 redraw_buf_later(term->tl_buffer, NOT_VALID);
2377 return 1;
2378}
2379
2380 static int
2381handle_moverect(VTermRect dest, VTermRect src, void *user)
2382{
2383 term_T *term = (term_T *)user;
2384
2385 /* Scrolling up is done much more efficiently by deleting lines instead of
2386 * redrawing the text. */
2387 if (dest.start_col == src.start_col
2388 && dest.end_col == src.end_col
2389 && dest.start_row < src.start_row)
2390 {
2391 win_T *wp;
2392 VTermColor fg, bg;
2393 VTermScreenCellAttrs attr;
2394 int clear_attr;
2395
2396 /* Set the color to clear lines with. */
2397 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2398 &fg, &bg);
2399 vim_memset(&attr, 0, sizeof(attr));
2400 clear_attr = cell2attr(attr, fg, bg);
2401
2402 FOR_ALL_WINDOWS(wp)
2403 {
2404 if (wp->w_buffer == term->tl_buffer)
2405 win_del_lines(wp, dest.start_row,
2406 src.start_row - dest.start_row, FALSE, FALSE,
2407 clear_attr);
2408 }
2409 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002410
2411 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2412 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2413
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002414 redraw_buf_later(term->tl_buffer, NOT_VALID);
2415 return 1;
2416}
2417
2418 static int
2419handle_movecursor(
2420 VTermPos pos,
2421 VTermPos oldpos UNUSED,
2422 int visible,
2423 void *user)
2424{
2425 term_T *term = (term_T *)user;
2426 win_T *wp;
2427
2428 term->tl_cursor_pos = pos;
2429 term->tl_cursor_visible = visible;
2430
2431 FOR_ALL_WINDOWS(wp)
2432 {
2433 if (wp->w_buffer == term->tl_buffer)
2434 position_cursor(wp, &pos);
2435 }
2436 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2437 {
2438 may_toggle_cursor(term);
2439 update_cursor(term, term->tl_cursor_visible);
2440 }
2441
2442 return 1;
2443}
2444
2445 static int
2446handle_settermprop(
2447 VTermProp prop,
2448 VTermValue *value,
2449 void *user)
2450{
2451 term_T *term = (term_T *)user;
2452
2453 switch (prop)
2454 {
2455 case VTERM_PROP_TITLE:
2456 vim_free(term->tl_title);
2457 /* a blank title isn't useful, make it empty, so that "running" is
2458 * displayed */
2459 if (*skipwhite((char_u *)value->string) == NUL)
2460 term->tl_title = NULL;
2461#ifdef WIN3264
2462 else if (!enc_utf8 && enc_codepage > 0)
2463 {
2464 WCHAR *ret = NULL;
2465 int length = 0;
2466
2467 MultiByteToWideChar_alloc(CP_UTF8, 0,
2468 (char*)value->string, (int)STRLEN(value->string),
2469 &ret, &length);
2470 if (ret != NULL)
2471 {
2472 WideCharToMultiByte_alloc(enc_codepage, 0,
2473 ret, length, (char**)&term->tl_title,
2474 &length, 0, 0);
2475 vim_free(ret);
2476 }
2477 }
2478#endif
2479 else
2480 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002481 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002482 if (term == curbuf->b_term)
2483 maketitle();
2484 break;
2485
2486 case VTERM_PROP_CURSORVISIBLE:
2487 term->tl_cursor_visible = value->boolean;
2488 may_toggle_cursor(term);
2489 out_flush();
2490 break;
2491
2492 case VTERM_PROP_CURSORBLINK:
2493 term->tl_cursor_blink = value->boolean;
2494 may_set_cursor_props(term);
2495 break;
2496
2497 case VTERM_PROP_CURSORSHAPE:
2498 term->tl_cursor_shape = value->number;
2499 may_set_cursor_props(term);
2500 break;
2501
2502 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002503 if (desired_cursor_color == term->tl_cursor_color)
2504 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002505 vim_free(term->tl_cursor_color);
2506 if (*value->string == NUL)
2507 term->tl_cursor_color = NULL;
2508 else
2509 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2510 may_set_cursor_props(term);
2511 break;
2512
2513 case VTERM_PROP_ALTSCREEN:
2514 /* TODO: do anything else? */
2515 term->tl_using_altscreen = value->boolean;
2516 break;
2517
2518 default:
2519 break;
2520 }
2521 /* Always return 1, otherwise vterm doesn't store the value internally. */
2522 return 1;
2523}
2524
2525/*
2526 * The job running in the terminal resized the terminal.
2527 */
2528 static int
2529handle_resize(int rows, int cols, void *user)
2530{
2531 term_T *term = (term_T *)user;
2532 win_T *wp;
2533
2534 term->tl_rows = rows;
2535 term->tl_cols = cols;
2536 if (term->tl_vterm_size_changed)
2537 /* Size was set by vterm_set_size(), don't set the window size. */
2538 term->tl_vterm_size_changed = FALSE;
2539 else
2540 {
2541 FOR_ALL_WINDOWS(wp)
2542 {
2543 if (wp->w_buffer == term->tl_buffer)
2544 {
2545 win_setheight_win(rows, wp);
2546 win_setwidth_win(cols, wp);
2547 }
2548 }
2549 redraw_buf_later(term->tl_buffer, NOT_VALID);
2550 }
2551 return 1;
2552}
2553
2554/*
2555 * Handle a line that is pushed off the top of the screen.
2556 */
2557 static int
2558handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2559{
2560 term_T *term = (term_T *)user;
2561
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002562 /* If the number of lines that are stored goes over 'termscrollback' then
2563 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002564 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002565 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002566 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002567 int i;
2568
2569 curbuf = term->tl_buffer;
2570 for (i = 0; i < todo; ++i)
2571 {
2572 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2573 ml_delete(1, FALSE);
2574 }
2575 curbuf = curwin->w_buffer;
2576
2577 term->tl_scrollback.ga_len -= todo;
2578 mch_memmove(term->tl_scrollback.ga_data,
2579 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2580 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2581 }
2582
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002583 if (ga_grow(&term->tl_scrollback, 1) == OK)
2584 {
2585 cellattr_T *p = NULL;
2586 int len = 0;
2587 int i;
2588 int c;
2589 int col;
2590 sb_line_T *line;
2591 garray_T ga;
2592 cellattr_T fill_attr = term->tl_default_color;
2593
2594 /* do not store empty cells at the end */
2595 for (i = 0; i < cols; ++i)
2596 if (cells[i].chars[0] != 0)
2597 len = i + 1;
2598 else
2599 cell2cellattr(&cells[i], &fill_attr);
2600
2601 ga_init2(&ga, 1, 100);
2602 if (len > 0)
2603 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2604 if (p != NULL)
2605 {
2606 for (col = 0; col < len; col += cells[col].width)
2607 {
2608 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2609 {
2610 ga.ga_len = 0;
2611 break;
2612 }
2613 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2614 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2615 (char_u *)ga.ga_data + ga.ga_len);
2616 cell2cellattr(&cells[col], &p[col]);
2617 }
2618 }
2619 if (ga_grow(&ga, 1) == FAIL)
2620 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2621 else
2622 {
2623 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2624 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2625 }
2626 ga_clear(&ga);
2627
2628 line = (sb_line_T *)term->tl_scrollback.ga_data
2629 + term->tl_scrollback.ga_len;
2630 line->sb_cols = len;
2631 line->sb_cells = p;
2632 line->sb_fill_attr = fill_attr;
2633 ++term->tl_scrollback.ga_len;
2634 ++term->tl_scrollback_scrolled;
2635 }
2636 return 0; /* ignored */
2637}
2638
2639static VTermScreenCallbacks screen_callbacks = {
2640 handle_damage, /* damage */
2641 handle_moverect, /* moverect */
2642 handle_movecursor, /* movecursor */
2643 handle_settermprop, /* settermprop */
2644 NULL, /* bell */
2645 handle_resize, /* resize */
2646 handle_pushline, /* sb_pushline */
2647 NULL /* sb_popline */
2648};
2649
2650/*
2651 * Called when a channel has been closed.
2652 * If this was a channel for a terminal window then finish it up.
2653 */
2654 void
2655term_channel_closed(channel_T *ch)
2656{
2657 term_T *term;
2658 int did_one = FALSE;
2659
2660 for (term = first_term; term != NULL; term = term->tl_next)
2661 if (term->tl_job == ch->ch_job)
2662 {
2663 term->tl_channel_closed = TRUE;
2664 did_one = TRUE;
2665
Bram Moolenaard23a8232018-02-10 18:45:26 +01002666 VIM_CLEAR(term->tl_title);
2667 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002668
2669 /* Unless in Terminal-Normal mode: clear the vterm. */
2670 if (!term->tl_normal_mode)
2671 {
2672 int fnum = term->tl_buffer->b_fnum;
2673
2674 cleanup_vterm(term);
2675
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002676 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002677 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002678 aco_save_T aco;
2679
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002680 /* ++close or term_finish == "close" */
2681 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002682 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002683 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002684 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002685 break;
2686 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002687 if (term->tl_finish == TL_FINISH_OPEN
2688 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002689 {
2690 char buf[50];
2691
2692 /* TODO: use term_opencmd */
2693 ch_log(NULL, "terminal job finished, opening window");
2694 vim_snprintf(buf, sizeof(buf),
2695 term->tl_opencmd == NULL
2696 ? "botright sbuf %d"
2697 : (char *)term->tl_opencmd, fnum);
2698 do_cmdline_cmd((char_u *)buf);
2699 }
2700 else
2701 ch_log(NULL, "terminal job finished");
2702 }
2703
2704 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2705 }
2706 if (did_one)
2707 {
2708 redraw_statuslines();
2709
2710 /* Need to break out of vgetc(). */
2711 ins_char_typebuf(K_IGNORE);
2712 typebuf_was_filled = TRUE;
2713
2714 term = curbuf->b_term;
2715 if (term != NULL)
2716 {
2717 if (term->tl_job == ch->ch_job)
2718 maketitle();
2719 update_cursor(term, term->tl_cursor_visible);
2720 }
2721 }
2722}
2723
2724/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002725 * Fill one screen line from a line of the terminal.
2726 * Advances "pos" to past the last column.
2727 */
2728 static void
2729term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2730{
2731 int off = screen_get_current_line_off();
2732
2733 for (pos->col = 0; pos->col < max_col; )
2734 {
2735 VTermScreenCell cell;
2736 int c;
2737
2738 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2739 vim_memset(&cell, 0, sizeof(cell));
2740
2741 c = cell.chars[0];
2742 if (c == NUL)
2743 {
2744 ScreenLines[off] = ' ';
2745 if (enc_utf8)
2746 ScreenLinesUC[off] = NUL;
2747 }
2748 else
2749 {
2750 if (enc_utf8)
2751 {
2752 int i;
2753
2754 /* composing chars */
2755 for (i = 0; i < Screen_mco
2756 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2757 {
2758 ScreenLinesC[i][off] = cell.chars[i + 1];
2759 if (cell.chars[i + 1] == 0)
2760 break;
2761 }
2762 if (c >= 0x80 || (Screen_mco > 0
2763 && ScreenLinesC[0][off] != 0))
2764 {
2765 ScreenLines[off] = ' ';
2766 ScreenLinesUC[off] = c;
2767 }
2768 else
2769 {
2770 ScreenLines[off] = c;
2771 ScreenLinesUC[off] = NUL;
2772 }
2773 }
2774#ifdef WIN3264
2775 else if (has_mbyte && c >= 0x80)
2776 {
2777 char_u mb[MB_MAXBYTES+1];
2778 WCHAR wc = c;
2779
2780 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2781 (char*)mb, 2, 0, 0) > 1)
2782 {
2783 ScreenLines[off] = mb[0];
2784 ScreenLines[off + 1] = mb[1];
2785 cell.width = mb_ptr2cells(mb);
2786 }
2787 else
2788 ScreenLines[off] = c;
2789 }
2790#endif
2791 else
2792 ScreenLines[off] = c;
2793 }
2794 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2795
2796 ++pos->col;
2797 ++off;
2798 if (cell.width == 2)
2799 {
2800 if (enc_utf8)
2801 ScreenLinesUC[off] = NUL;
2802
2803 /* don't set the second byte to NUL for a DBCS encoding, it
2804 * has been set above */
2805 if (enc_utf8 || !has_mbyte)
2806 ScreenLines[off] = NUL;
2807
2808 ++pos->col;
2809 ++off;
2810 }
2811 }
2812}
2813
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002814#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002815 static void
2816update_system_term(term_T *term)
2817{
2818 VTermPos pos;
2819 VTermScreen *screen;
2820
2821 if (term->tl_vterm == NULL)
2822 return;
2823 screen = vterm_obtain_screen(term->tl_vterm);
2824
2825 /* Scroll up to make more room for terminal lines if needed. */
2826 while (term->tl_toprow > 0
2827 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2828 {
2829 int save_p_more = p_more;
2830
2831 p_more = FALSE;
2832 msg_row = Rows - 1;
2833 msg_puts((char_u *)"\n");
2834 p_more = save_p_more;
2835 --term->tl_toprow;
2836 }
2837
2838 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2839 && pos.row < Rows; ++pos.row)
2840 {
2841 if (pos.row < term->tl_rows)
2842 {
2843 int max_col = MIN(Columns, term->tl_cols);
2844
2845 term_line2screenline(screen, &pos, max_col);
2846 }
2847 else
2848 pos.col = 0;
2849
2850 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2851 }
2852
2853 term->tl_dirty_row_start = MAX_ROW;
2854 term->tl_dirty_row_end = 0;
2855 update_cursor(term, TRUE);
2856}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002857#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002858
2859/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002860 * Called to update a window that contains an active terminal.
2861 * Returns FAIL when there is no terminal running in this window or in
2862 * Terminal-Normal mode.
2863 */
2864 int
2865term_update_window(win_T *wp)
2866{
2867 term_T *term = wp->w_buffer->b_term;
2868 VTerm *vterm;
2869 VTermScreen *screen;
2870 VTermState *state;
2871 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002872 int rows, cols;
2873 int newrows, newcols;
2874 int minsize;
2875 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002876
2877 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2878 return FAIL;
2879
2880 vterm = term->tl_vterm;
2881 screen = vterm_obtain_screen(vterm);
2882 state = vterm_obtain_state(vterm);
2883
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002884 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002885 {
2886 term->tl_dirty_row_start = 0;
2887 term->tl_dirty_row_end = MAX_ROW;
2888 }
2889
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002890 /*
2891 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002892 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002893 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002894 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002895
Bram Moolenaar498c2562018-04-15 23:45:15 +02002896 newrows = 99999;
2897 newcols = 99999;
2898 FOR_ALL_WINDOWS(twp)
2899 {
2900 /* When more than one window shows the same terminal, use the
2901 * smallest size. */
2902 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002903 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02002904 newrows = MIN(newrows, twp->w_height);
2905 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002906 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02002907 }
2908 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
2909 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
2910
2911 if (term->tl_rows != newrows || term->tl_cols != newcols)
2912 {
2913
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002914
2915 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002916 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002917 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02002918 newrows);
2919 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002920 }
2921
2922 /* The cursor may have been moved when resizing. */
2923 vterm_state_get_cursorpos(state, &pos);
2924 position_cursor(wp, &pos);
2925
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002926 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2927 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002928 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002929 if (pos.row < term->tl_rows)
2930 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002931 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002932
Bram Moolenaar13568252018-03-16 20:46:58 +01002933 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002934 }
2935 else
2936 pos.col = 0;
2937
Bram Moolenaarf118d482018-03-13 13:14:00 +01002938 screen_line(wp->w_winrow + pos.row
2939#ifdef FEAT_MENU
2940 + winbar_height(wp)
2941#endif
2942 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002943 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002944 term->tl_dirty_row_start = MAX_ROW;
2945 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002946
2947 return OK;
2948}
2949
2950/*
2951 * Return TRUE if "wp" is a terminal window where the job has finished.
2952 */
2953 int
2954term_is_finished(buf_T *buf)
2955{
2956 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2957}
2958
2959/*
2960 * Return TRUE if "wp" is a terminal window where the job has finished or we
2961 * are in Terminal-Normal mode, thus we show the buffer contents.
2962 */
2963 int
2964term_show_buffer(buf_T *buf)
2965{
2966 term_T *term = buf->b_term;
2967
2968 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2969}
2970
2971/*
2972 * The current buffer is going to be changed. If there is terminal
2973 * highlighting remove it now.
2974 */
2975 void
2976term_change_in_curbuf(void)
2977{
2978 term_T *term = curbuf->b_term;
2979
2980 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2981 {
2982 free_scrollback(term);
2983 redraw_buf_later(term->tl_buffer, NOT_VALID);
2984
2985 /* The buffer is now like a normal buffer, it cannot be easily
2986 * abandoned when changed. */
2987 set_string_option_direct((char_u *)"buftype", -1,
2988 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2989 }
2990}
2991
2992/*
2993 * Get the screen attribute for a position in the buffer.
2994 * Use a negative "col" to get the filler background color.
2995 */
2996 int
2997term_get_attr(buf_T *buf, linenr_T lnum, int col)
2998{
2999 term_T *term = buf->b_term;
3000 sb_line_T *line;
3001 cellattr_T *cellattr;
3002
3003 if (lnum > term->tl_scrollback.ga_len)
3004 cellattr = &term->tl_default_color;
3005 else
3006 {
3007 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3008 if (col < 0 || col >= line->sb_cols)
3009 cellattr = &line->sb_fill_attr;
3010 else
3011 cellattr = line->sb_cells + col;
3012 }
3013 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3014}
3015
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003016/*
3017 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003018 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003019 */
3020 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003021cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003022{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003023 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003024}
3025
3026/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003027 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003028 */
3029 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003030init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003031{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003032 VTermColor *fg, *bg;
3033 int fgval, bgval;
3034 int id;
3035
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003036 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3037 term->tl_default_color.width = 1;
3038 fg = &term->tl_default_color.fg;
3039 bg = &term->tl_default_color.bg;
3040
3041 /* Vterm uses a default black background. Set it to white when
3042 * 'background' is "light". */
3043 if (*p_bg == 'l')
3044 {
3045 fgval = 0;
3046 bgval = 255;
3047 }
3048 else
3049 {
3050 fgval = 255;
3051 bgval = 0;
3052 }
3053 fg->red = fg->green = fg->blue = fgval;
3054 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003055 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003056
3057 /* The "Terminal" highlight group overrules the defaults. */
3058 id = syn_name2id((char_u *)"Terminal");
3059
Bram Moolenaar46359e12017-11-29 22:33:38 +01003060 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003061#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3062 if (0
3063# ifdef FEAT_GUI
3064 || gui.in_use
3065# endif
3066# ifdef FEAT_TERMGUICOLORS
3067 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003068# ifdef FEAT_VTP
3069 /* Finally get INVALCOLOR on this execution path */
3070 || (!p_tgc && t_colors >= 256)
3071# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003072# endif
3073 )
3074 {
3075 guicolor_T fg_rgb = INVALCOLOR;
3076 guicolor_T bg_rgb = INVALCOLOR;
3077
3078 if (id != 0)
3079 syn_id2colors(id, &fg_rgb, &bg_rgb);
3080
3081# ifdef FEAT_GUI
3082 if (gui.in_use)
3083 {
3084 if (fg_rgb == INVALCOLOR)
3085 fg_rgb = gui.norm_pixel;
3086 if (bg_rgb == INVALCOLOR)
3087 bg_rgb = gui.back_pixel;
3088 }
3089# ifdef FEAT_TERMGUICOLORS
3090 else
3091# endif
3092# endif
3093# ifdef FEAT_TERMGUICOLORS
3094 {
3095 if (fg_rgb == INVALCOLOR)
3096 fg_rgb = cterm_normal_fg_gui_color;
3097 if (bg_rgb == INVALCOLOR)
3098 bg_rgb = cterm_normal_bg_gui_color;
3099 }
3100# endif
3101 if (fg_rgb != INVALCOLOR)
3102 {
3103 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3104
3105 fg->red = (unsigned)(rgb >> 16);
3106 fg->green = (unsigned)(rgb >> 8) & 255;
3107 fg->blue = (unsigned)rgb & 255;
3108 }
3109 if (bg_rgb != INVALCOLOR)
3110 {
3111 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3112
3113 bg->red = (unsigned)(rgb >> 16);
3114 bg->green = (unsigned)(rgb >> 8) & 255;
3115 bg->blue = (unsigned)rgb & 255;
3116 }
3117 }
3118 else
3119#endif
3120 if (id != 0 && t_colors >= 16)
3121 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003122 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003123 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003124 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003125 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003126 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003127 else
3128 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003129#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003130 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003131#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003132
3133 /* In an MS-Windows console we know the normal colors. */
3134 if (cterm_normal_fg_color > 0)
3135 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003136 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003137# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003138 tmp = fg->red;
3139 fg->red = fg->blue;
3140 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003141# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003142 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003143# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003144 else
3145 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003146# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003147
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003148 if (cterm_normal_bg_color > 0)
3149 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003150 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003151# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003152 tmp = bg->red;
3153 bg->red = bg->blue;
3154 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003155# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003156 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003157# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003158 else
3159 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003160# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003161 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003162}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003163
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003164#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3165/*
3166 * Set the 16 ANSI colors from array of RGB values
3167 */
3168 static void
3169set_vterm_palette(VTerm *vterm, long_u *rgb)
3170{
3171 int index = 0;
3172 VTermState *state = vterm_obtain_state(vterm);
3173 for (; index < 16; index++)
3174 {
3175 VTermColor color;
3176 color.red = (unsigned)(rgb[index] >> 16);
3177 color.green = (unsigned)(rgb[index] >> 8) & 255;
3178 color.blue = (unsigned)rgb[index] & 255;
3179 vterm_state_set_palette_color(state, index, &color);
3180 }
3181}
3182
3183/*
3184 * Set the ANSI color palette from a list of colors
3185 */
3186 static int
3187set_ansi_colors_list(VTerm *vterm, list_T *list)
3188{
3189 int n = 0;
3190 long_u rgb[16];
3191 listitem_T *li = list->lv_first;
3192
3193 for (; li != NULL && n < 16; li = li->li_next, n++)
3194 {
3195 char_u *color_name;
3196 guicolor_T guicolor;
3197
3198 color_name = get_tv_string_chk(&li->li_tv);
3199 if (color_name == NULL)
3200 return FAIL;
3201
3202 guicolor = GUI_GET_COLOR(color_name);
3203 if (guicolor == INVALCOLOR)
3204 return FAIL;
3205
3206 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3207 }
3208
3209 if (n != 16 || li != NULL)
3210 return FAIL;
3211
3212 set_vterm_palette(vterm, rgb);
3213
3214 return OK;
3215}
3216
3217/*
3218 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3219 */
3220 static void
3221init_vterm_ansi_colors(VTerm *vterm)
3222{
3223 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3224
3225 if (var != NULL
3226 && (var->di_tv.v_type != VAR_LIST
3227 || var->di_tv.vval.v_list == NULL
3228 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3229 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3230}
3231#endif
3232
Bram Moolenaar52acb112018-03-18 19:20:22 +01003233/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003234 * Handles a "drop" command from the job in the terminal.
3235 * "item" is the file name, "item->li_next" may have options.
3236 */
3237 static void
3238handle_drop_command(listitem_T *item)
3239{
3240 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003241 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003242 int bufnr;
3243 win_T *wp;
3244 tabpage_T *tp;
3245 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003246 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003247
3248 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3249 FOR_ALL_TAB_WINDOWS(tp, wp)
3250 {
3251 if (wp->w_buffer->b_fnum == bufnr)
3252 {
3253 /* buffer is in a window already, go there */
3254 goto_tabpage_win(tp, wp);
3255 return;
3256 }
3257 }
3258
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003259 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003260
3261 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3262 && opt_item->li_tv.vval.v_dict != NULL)
3263 {
3264 dict_T *dict = opt_item->li_tv.vval.v_dict;
3265 char_u *p;
3266
3267 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3268 if (p == NULL)
3269 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3270 if (p != NULL)
3271 {
3272 if (check_ff_value(p) == FAIL)
3273 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3274 else
3275 ea.force_ff = *p;
3276 }
3277 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3278 if (p == NULL)
3279 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3280 if (p != NULL)
3281 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003282 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003283 if (ea.cmd != NULL)
3284 {
3285 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3286 ea.force_enc = 11;
3287 tofree = ea.cmd;
3288 }
3289 }
3290
3291 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3292 if (p != NULL)
3293 get_bad_opt(p, &ea);
3294
3295 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3296 ea.force_bin = FORCE_BIN;
3297 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3298 ea.force_bin = FORCE_BIN;
3299 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3300 ea.force_bin = FORCE_NOBIN;
3301 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3302 ea.force_bin = FORCE_NOBIN;
3303 }
3304
3305 /* open in new window, like ":split fname" */
3306 if (ea.cmd == NULL)
3307 ea.cmd = (char_u *)"split";
3308 ea.arg = fname;
3309 ea.cmdidx = CMD_split;
3310 ex_splitview(&ea);
3311
3312 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003313}
3314
3315/*
3316 * Handles a function call from the job running in a terminal.
3317 * "item" is the function name, "item->li_next" has the arguments.
3318 */
3319 static void
3320handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3321{
3322 char_u *func;
3323 typval_T argvars[2];
3324 typval_T rettv;
3325 int doesrange;
3326
3327 if (item->li_next == NULL)
3328 {
3329 ch_log(channel, "Missing function arguments for call");
3330 return;
3331 }
3332 func = get_tv_string(&item->li_tv);
3333
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003334 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003335 {
3336 ch_log(channel, "Invalid function name: %s", func);
3337 return;
3338 }
3339
3340 argvars[0].v_type = VAR_NUMBER;
3341 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3342 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003343 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003344 2, argvars, /* argv_func */ NULL,
3345 /* firstline */ 1, /* lastline */ 1,
3346 &doesrange, /* evaluate */ TRUE,
3347 /* partial */ NULL, /* selfdict */ NULL) == OK)
3348 {
3349 clear_tv(&rettv);
3350 ch_log(channel, "Function %s called", func);
3351 }
3352 else
3353 ch_log(channel, "Calling function %s failed", func);
3354}
3355
3356/*
3357 * Called by libvterm when it cannot recognize an OSC sequence.
3358 * We recognize a terminal API command.
3359 */
3360 static int
3361parse_osc(const char *command, size_t cmdlen, void *user)
3362{
3363 term_T *term = (term_T *)user;
3364 js_read_T reader;
3365 typval_T tv;
3366 channel_T *channel = term->tl_job == NULL ? NULL
3367 : term->tl_job->jv_channel;
3368
3369 /* We recognize only OSC 5 1 ; {command} */
3370 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3371 return 0; /* not handled */
3372
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003373 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003374 if (reader.js_buf == NULL)
3375 return 1;
3376 reader.js_fill = NULL;
3377 reader.js_used = 0;
3378 if (json_decode(&reader, &tv, 0) == OK
3379 && tv.v_type == VAR_LIST
3380 && tv.vval.v_list != NULL)
3381 {
3382 listitem_T *item = tv.vval.v_list->lv_first;
3383
3384 if (item == NULL)
3385 ch_log(channel, "Missing command");
3386 else
3387 {
3388 char_u *cmd = get_tv_string(&item->li_tv);
3389
Bram Moolenaara997b452018-04-17 23:24:06 +02003390 /* Make sure an invoked command doesn't delete the buffer (and the
3391 * terminal) under our fingers. */
3392 ++term->tl_buffer->b_locked;
3393
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003394 item = item->li_next;
3395 if (item == NULL)
3396 ch_log(channel, "Missing argument for %s", cmd);
3397 else if (STRCMP(cmd, "drop") == 0)
3398 handle_drop_command(item);
3399 else if (STRCMP(cmd, "call") == 0)
3400 handle_call_command(term, channel, item);
3401 else
3402 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003403 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003404 }
3405 }
3406 else
3407 ch_log(channel, "Invalid JSON received");
3408
3409 vim_free(reader.js_buf);
3410 clear_tv(&tv);
3411 return 1;
3412}
3413
3414static VTermParserCallbacks parser_fallbacks = {
3415 NULL, /* text */
3416 NULL, /* control */
3417 NULL, /* escape */
3418 NULL, /* csi */
3419 parse_osc, /* osc */
3420 NULL, /* dcs */
3421 NULL /* resize */
3422};
3423
3424/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003425 * Use Vim's allocation functions for vterm so profiling works.
3426 */
3427 static void *
3428vterm_malloc(size_t size, void *data UNUSED)
3429{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003430 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003431}
3432
3433 static void
3434vterm_memfree(void *ptr, void *data UNUSED)
3435{
3436 vim_free(ptr);
3437}
3438
3439static VTermAllocatorFunctions vterm_allocator = {
3440 &vterm_malloc,
3441 &vterm_memfree
3442};
3443
3444/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003445 * Create a new vterm and initialize it.
3446 */
3447 static void
3448create_vterm(term_T *term, int rows, int cols)
3449{
3450 VTerm *vterm;
3451 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003452 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003453 VTermValue value;
3454
Bram Moolenaar756ef112018-04-10 12:04:27 +02003455 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003456 term->tl_vterm = vterm;
3457 screen = vterm_obtain_screen(vterm);
3458 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3459 /* TODO: depends on 'encoding'. */
3460 vterm_set_utf8(vterm, 1);
3461
3462 init_default_colors(term);
3463
3464 vterm_state_set_default_colors(
3465 vterm_obtain_state(vterm),
3466 &term->tl_default_color.fg,
3467 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003468
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003469 if (t_colors >= 16)
3470 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3471
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003472 /* Required to initialize most things. */
3473 vterm_screen_reset(screen, 1 /* hard */);
3474
3475 /* Allow using alternate screen. */
3476 vterm_screen_enable_altscreen(screen, 1);
3477
3478 /* For unix do not use a blinking cursor. In an xterm this causes the
3479 * cursor to blink if it's blinking in the xterm.
3480 * For Windows we respect the system wide setting. */
3481#ifdef WIN3264
3482 if (GetCaretBlinkTime() == INFINITE)
3483 value.boolean = 0;
3484 else
3485 value.boolean = 1;
3486#else
3487 value.boolean = 0;
3488#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003489 state = vterm_obtain_state(vterm);
3490 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3491 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003492}
3493
3494/*
3495 * Return the text to show for the buffer name and status.
3496 */
3497 char_u *
3498term_get_status_text(term_T *term)
3499{
3500 if (term->tl_status_text == NULL)
3501 {
3502 char_u *txt;
3503 size_t len;
3504
3505 if (term->tl_normal_mode)
3506 {
3507 if (term_job_running(term))
3508 txt = (char_u *)_("Terminal");
3509 else
3510 txt = (char_u *)_("Terminal-finished");
3511 }
3512 else if (term->tl_title != NULL)
3513 txt = term->tl_title;
3514 else if (term_none_open(term))
3515 txt = (char_u *)_("active");
3516 else if (term_job_running(term))
3517 txt = (char_u *)_("running");
3518 else
3519 txt = (char_u *)_("finished");
3520 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3521 term->tl_status_text = alloc((int)len);
3522 if (term->tl_status_text != NULL)
3523 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3524 term->tl_buffer->b_fname, txt);
3525 }
3526 return term->tl_status_text;
3527}
3528
3529/*
3530 * Mark references in jobs of terminals.
3531 */
3532 int
3533set_ref_in_term(int copyID)
3534{
3535 int abort = FALSE;
3536 term_T *term;
3537 typval_T tv;
3538
3539 for (term = first_term; term != NULL; term = term->tl_next)
3540 if (term->tl_job != NULL)
3541 {
3542 tv.v_type = VAR_JOB;
3543 tv.vval.v_job = term->tl_job;
3544 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3545 }
3546 return abort;
3547}
3548
3549/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003550 * Cache "Terminal" highlight group colors.
3551 */
3552 void
3553set_terminal_default_colors(int cterm_fg, int cterm_bg)
3554{
3555 term_default_cterm_fg = cterm_fg - 1;
3556 term_default_cterm_bg = cterm_bg - 1;
3557}
3558
3559/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003560 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003561 * Returns NULL when the buffer is not for a terminal window and logs a message
3562 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003563 */
3564 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003565term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003566{
3567 buf_T *buf;
3568
3569 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3570 ++emsg_off;
3571 buf = get_buf_tv(&argvars[0], FALSE);
3572 --emsg_off;
3573 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003574 {
3575 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003576 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003577 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003578 return buf;
3579}
3580
Bram Moolenaard96ff162018-02-18 22:13:29 +01003581 static int
3582same_color(VTermColor *a, VTermColor *b)
3583{
3584 return a->red == b->red
3585 && a->green == b->green
3586 && a->blue == b->blue
3587 && a->ansi_index == b->ansi_index;
3588}
3589
3590 static void
3591dump_term_color(FILE *fd, VTermColor *color)
3592{
3593 fprintf(fd, "%02x%02x%02x%d",
3594 (int)color->red, (int)color->green, (int)color->blue,
3595 (int)color->ansi_index);
3596}
3597
3598/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003599 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003600 *
3601 * Each screen cell in full is:
3602 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3603 * {characters} is a space for an empty cell
3604 * For a double-width character "+" is changed to "*" and the next cell is
3605 * skipped.
3606 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3607 * when "&" use the same as the previous cell.
3608 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3609 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3610 * {color-idx} is a number from 0 to 255
3611 *
3612 * Screen cell with same width, attributes and color as the previous one:
3613 * |{characters}
3614 *
3615 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3616 *
3617 * Repeating the previous screen cell:
3618 * @{count}
3619 */
3620 void
3621f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3622{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003623 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003624 term_T *term;
3625 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003626 int max_height = 0;
3627 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003628 stat_T st;
3629 FILE *fd;
3630 VTermPos pos;
3631 VTermScreen *screen;
3632 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003633 VTermState *state;
3634 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003635
3636 if (check_restricted() || check_secure())
3637 return;
3638 if (buf == NULL)
3639 return;
3640 term = buf->b_term;
3641
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003642 if (argvars[2].v_type != VAR_UNKNOWN)
3643 {
3644 dict_T *d;
3645
3646 if (argvars[2].v_type != VAR_DICT)
3647 {
3648 EMSG(_(e_dictreq));
3649 return;
3650 }
3651 d = argvars[2].vval.v_dict;
3652 if (d != NULL)
3653 {
3654 max_height = get_dict_number(d, (char_u *)"rows");
3655 max_width = get_dict_number(d, (char_u *)"columns");
3656 }
3657 }
3658
Bram Moolenaard96ff162018-02-18 22:13:29 +01003659 fname = get_tv_string_chk(&argvars[1]);
3660 if (fname == NULL)
3661 return;
3662 if (mch_stat((char *)fname, &st) >= 0)
3663 {
3664 EMSG2(_("E953: File exists: %s"), fname);
3665 return;
3666 }
3667
Bram Moolenaard96ff162018-02-18 22:13:29 +01003668 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3669 {
3670 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3671 return;
3672 }
3673
3674 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3675
3676 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003677 state = vterm_obtain_state(term->tl_vterm);
3678 vterm_state_get_cursorpos(state, &cursor_pos);
3679
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003680 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3681 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003682 {
3683 int repeat = 0;
3684
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003685 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3686 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003687 {
3688 VTermScreenCell cell;
3689 int same_attr;
3690 int same_chars = TRUE;
3691 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003692 int is_cursor_pos = (pos.col == cursor_pos.col
3693 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003694
3695 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3696 vim_memset(&cell, 0, sizeof(cell));
3697
3698 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3699 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003700 int c = cell.chars[i];
3701 int pc = prev_cell.chars[i];
3702
3703 /* For the first character NUL is the same as space. */
3704 if (i == 0)
3705 {
3706 c = (c == NUL) ? ' ' : c;
3707 pc = (pc == NUL) ? ' ' : pc;
3708 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003709 if (cell.chars[i] != prev_cell.chars[i])
3710 same_chars = FALSE;
3711 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3712 break;
3713 }
3714 same_attr = vtermAttr2hl(cell.attrs)
3715 == vtermAttr2hl(prev_cell.attrs)
3716 && same_color(&cell.fg, &prev_cell.fg)
3717 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003718 if (same_chars && cell.width == prev_cell.width && same_attr
3719 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003720 {
3721 ++repeat;
3722 }
3723 else
3724 {
3725 if (repeat > 0)
3726 {
3727 fprintf(fd, "@%d", repeat);
3728 repeat = 0;
3729 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003730 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003731
3732 if (cell.chars[0] == NUL)
3733 fputs(" ", fd);
3734 else
3735 {
3736 char_u charbuf[10];
3737 int len;
3738
3739 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3740 && cell.chars[i] != NUL; ++i)
3741 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003742 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003743 fwrite(charbuf, len, 1, fd);
3744 }
3745 }
3746
3747 /* When only the characters differ we don't write anything, the
3748 * following "|", "@" or NL will indicate using the same
3749 * attributes. */
3750 if (cell.width != prev_cell.width || !same_attr)
3751 {
3752 if (cell.width == 2)
3753 {
3754 fputs("*", fd);
3755 ++pos.col;
3756 }
3757 else
3758 fputs("+", fd);
3759
3760 if (same_attr)
3761 {
3762 fputs("&", fd);
3763 }
3764 else
3765 {
3766 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3767 if (same_color(&cell.fg, &prev_cell.fg))
3768 fputs("&", fd);
3769 else
3770 {
3771 fputs("#", fd);
3772 dump_term_color(fd, &cell.fg);
3773 }
3774 if (same_color(&cell.bg, &prev_cell.bg))
3775 fputs("&", fd);
3776 else
3777 {
3778 fputs("#", fd);
3779 dump_term_color(fd, &cell.bg);
3780 }
3781 }
3782 }
3783
3784 prev_cell = cell;
3785 }
3786 }
3787 if (repeat > 0)
3788 fprintf(fd, "@%d", repeat);
3789 fputs("\n", fd);
3790 }
3791
3792 fclose(fd);
3793}
3794
3795/*
3796 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3797 */
3798 static void
3799dump_is_corrupt(garray_T *gap)
3800{
3801 ga_concat(gap, (char_u *)"CORRUPT");
3802}
3803
3804 static void
3805append_cell(garray_T *gap, cellattr_T *cell)
3806{
3807 if (ga_grow(gap, 1) == OK)
3808 {
3809 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3810 ++gap->ga_len;
3811 }
3812}
3813
3814/*
3815 * Read the dump file from "fd" and append lines to the current buffer.
3816 * Return the cell width of the longest line.
3817 */
3818 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003819read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003820{
3821 int c;
3822 garray_T ga_text;
3823 garray_T ga_cell;
3824 char_u *prev_char = NULL;
3825 int attr = 0;
3826 cellattr_T cell;
3827 term_T *term = curbuf->b_term;
3828 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003829 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003830
3831 ga_init2(&ga_text, 1, 90);
3832 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3833 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003834 cursor_pos->row = -1;
3835 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003836
3837 c = fgetc(fd);
3838 for (;;)
3839 {
3840 if (c == EOF)
3841 break;
3842 if (c == '\n')
3843 {
3844 /* End of a line: append it to the buffer. */
3845 if (ga_text.ga_data == NULL)
3846 dump_is_corrupt(&ga_text);
3847 if (ga_grow(&term->tl_scrollback, 1) == OK)
3848 {
3849 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3850 + term->tl_scrollback.ga_len;
3851
3852 if (max_cells < ga_cell.ga_len)
3853 max_cells = ga_cell.ga_len;
3854 line->sb_cols = ga_cell.ga_len;
3855 line->sb_cells = ga_cell.ga_data;
3856 line->sb_fill_attr = term->tl_default_color;
3857 ++term->tl_scrollback.ga_len;
3858 ga_init(&ga_cell);
3859
3860 ga_append(&ga_text, NUL);
3861 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3862 ga_text.ga_len, FALSE);
3863 }
3864 else
3865 ga_clear(&ga_cell);
3866 ga_text.ga_len = 0;
3867
3868 c = fgetc(fd);
3869 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003870 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003871 {
3872 int prev_len = ga_text.ga_len;
3873
Bram Moolenaar9271d052018-02-25 21:39:46 +01003874 if (c == '>')
3875 {
3876 if (cursor_pos->row != -1)
3877 dump_is_corrupt(&ga_text); /* duplicate cursor */
3878 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3879 cursor_pos->col = ga_cell.ga_len;
3880 }
3881
Bram Moolenaard96ff162018-02-18 22:13:29 +01003882 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3883 c = fgetc(fd);
3884 if (c != EOF)
3885 ga_append(&ga_text, c);
3886 for (;;)
3887 {
3888 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003889 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003890 || c == EOF || c == '\n')
3891 break;
3892 ga_append(&ga_text, c);
3893 }
3894
3895 /* save the character for repeating it */
3896 vim_free(prev_char);
3897 if (ga_text.ga_data != NULL)
3898 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3899 ga_text.ga_len - prev_len);
3900
Bram Moolenaar9271d052018-02-25 21:39:46 +01003901 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003902 {
3903 /* use all attributes from previous cell */
3904 }
3905 else if (c == '+' || c == '*')
3906 {
3907 int is_bg;
3908
3909 cell.width = c == '+' ? 1 : 2;
3910
3911 c = fgetc(fd);
3912 if (c == '&')
3913 {
3914 /* use same attr as previous cell */
3915 c = fgetc(fd);
3916 }
3917 else if (isdigit(c))
3918 {
3919 /* get the decimal attribute */
3920 attr = 0;
3921 while (isdigit(c))
3922 {
3923 attr = attr * 10 + (c - '0');
3924 c = fgetc(fd);
3925 }
3926 hl2vtermAttr(attr, &cell);
3927 }
3928 else
3929 dump_is_corrupt(&ga_text);
3930
3931 /* is_bg == 0: fg, is_bg == 1: bg */
3932 for (is_bg = 0; is_bg <= 1; ++is_bg)
3933 {
3934 if (c == '&')
3935 {
3936 /* use same color as previous cell */
3937 c = fgetc(fd);
3938 }
3939 else if (c == '#')
3940 {
3941 int red, green, blue, index = 0;
3942
3943 c = fgetc(fd);
3944 red = hex2nr(c);
3945 c = fgetc(fd);
3946 red = (red << 4) + hex2nr(c);
3947 c = fgetc(fd);
3948 green = hex2nr(c);
3949 c = fgetc(fd);
3950 green = (green << 4) + hex2nr(c);
3951 c = fgetc(fd);
3952 blue = hex2nr(c);
3953 c = fgetc(fd);
3954 blue = (blue << 4) + hex2nr(c);
3955 c = fgetc(fd);
3956 if (!isdigit(c))
3957 dump_is_corrupt(&ga_text);
3958 while (isdigit(c))
3959 {
3960 index = index * 10 + (c - '0');
3961 c = fgetc(fd);
3962 }
3963
3964 if (is_bg)
3965 {
3966 cell.bg.red = red;
3967 cell.bg.green = green;
3968 cell.bg.blue = blue;
3969 cell.bg.ansi_index = index;
3970 }
3971 else
3972 {
3973 cell.fg.red = red;
3974 cell.fg.green = green;
3975 cell.fg.blue = blue;
3976 cell.fg.ansi_index = index;
3977 }
3978 }
3979 else
3980 dump_is_corrupt(&ga_text);
3981 }
3982 }
3983 else
3984 dump_is_corrupt(&ga_text);
3985
3986 append_cell(&ga_cell, &cell);
3987 }
3988 else if (c == '@')
3989 {
3990 if (prev_char == NULL)
3991 dump_is_corrupt(&ga_text);
3992 else
3993 {
3994 int count = 0;
3995
3996 /* repeat previous character, get the count */
3997 for (;;)
3998 {
3999 c = fgetc(fd);
4000 if (!isdigit(c))
4001 break;
4002 count = count * 10 + (c - '0');
4003 }
4004
4005 while (count-- > 0)
4006 {
4007 ga_concat(&ga_text, prev_char);
4008 append_cell(&ga_cell, &cell);
4009 }
4010 }
4011 }
4012 else
4013 {
4014 dump_is_corrupt(&ga_text);
4015 c = fgetc(fd);
4016 }
4017 }
4018
4019 if (ga_text.ga_len > 0)
4020 {
4021 /* trailing characters after last NL */
4022 dump_is_corrupt(&ga_text);
4023 ga_append(&ga_text, NUL);
4024 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4025 ga_text.ga_len, FALSE);
4026 }
4027
4028 ga_clear(&ga_text);
4029 vim_free(prev_char);
4030
4031 return max_cells;
4032}
4033
4034/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004035 * Return an allocated string with at least "text_width" "=" characters and
4036 * "fname" inserted in the middle.
4037 */
4038 static char_u *
4039get_separator(int text_width, char_u *fname)
4040{
4041 int width = MAX(text_width, curwin->w_width);
4042 char_u *textline;
4043 int fname_size;
4044 char_u *p = fname;
4045 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004046 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004047
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004048 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004049 if (textline == NULL)
4050 return NULL;
4051
4052 fname_size = vim_strsize(fname);
4053 if (fname_size < width - 8)
4054 {
4055 /* enough room, don't use the full window width */
4056 width = MAX(text_width, fname_size + 8);
4057 }
4058 else if (fname_size > width - 8)
4059 {
4060 /* full name doesn't fit, use only the tail */
4061 p = gettail(fname);
4062 fname_size = vim_strsize(p);
4063 }
4064 /* skip characters until the name fits */
4065 while (fname_size > width - 8)
4066 {
4067 p += (*mb_ptr2len)(p);
4068 fname_size = vim_strsize(p);
4069 }
4070
4071 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4072 textline[i] = '=';
4073 textline[i++] = ' ';
4074
4075 STRCPY(textline + i, p);
4076 off = STRLEN(textline);
4077 textline[off] = ' ';
4078 for (i = 1; i < (width - fname_size) / 2; ++i)
4079 textline[off + i] = '=';
4080 textline[off + i] = NUL;
4081
4082 return textline;
4083}
4084
4085/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004086 * Common for "term_dumpdiff()" and "term_dumpload()".
4087 */
4088 static void
4089term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4090{
4091 jobopt_T opt;
4092 buf_T *buf;
4093 char_u buf1[NUMBUFLEN];
4094 char_u buf2[NUMBUFLEN];
4095 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004096 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004097 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004098 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004099 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004100 char_u *textline = NULL;
4101
4102 /* First open the files. If this fails bail out. */
4103 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4104 if (do_diff)
4105 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4106 if (fname1 == NULL || (do_diff && fname2 == NULL))
4107 {
4108 EMSG(_(e_invarg));
4109 return;
4110 }
4111 fd1 = mch_fopen((char *)fname1, READBIN);
4112 if (fd1 == NULL)
4113 {
4114 EMSG2(_(e_notread), fname1);
4115 return;
4116 }
4117 if (do_diff)
4118 {
4119 fd2 = mch_fopen((char *)fname2, READBIN);
4120 if (fd2 == NULL)
4121 {
4122 fclose(fd1);
4123 EMSG2(_(e_notread), fname2);
4124 return;
4125 }
4126 }
4127
4128 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004129 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4130 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4131 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4132 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4133 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004134
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004135 if (opt.jo_term_name == NULL)
4136 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004137 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004138
Bram Moolenaarb571c632018-03-21 22:27:59 +01004139 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004140 if (fname_tofree != NULL)
4141 {
4142 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4143 opt.jo_term_name = fname_tofree;
4144 }
4145 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004146
Bram Moolenaar13568252018-03-16 20:46:58 +01004147 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004148 if (buf != NULL && buf->b_term != NULL)
4149 {
4150 int i;
4151 linenr_T bot_lnum;
4152 linenr_T lnum;
4153 term_T *term = buf->b_term;
4154 int width;
4155 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004156 VTermPos cursor_pos1;
4157 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004158
Bram Moolenaar52acb112018-03-18 19:20:22 +01004159 init_default_colors(term);
4160
Bram Moolenaard96ff162018-02-18 22:13:29 +01004161 rettv->vval.v_number = buf->b_fnum;
4162
4163 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004164 width = read_dump_file(fd1, &cursor_pos1);
4165
4166 /* position the cursor */
4167 if (cursor_pos1.row >= 0)
4168 {
4169 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4170 coladvance(cursor_pos1.col);
4171 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004172
4173 /* Delete the empty line that was in the empty buffer. */
4174 ml_delete(1, FALSE);
4175
4176 /* For term_dumpload() we are done here. */
4177 if (!do_diff)
4178 goto theend;
4179
4180 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4181
Bram Moolenaar4a696342018-04-05 18:45:26 +02004182 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004183 if (textline == NULL)
4184 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004185 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4186 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4187 vim_free(textline);
4188
4189 textline = get_separator(width, fname2);
4190 if (textline == NULL)
4191 goto theend;
4192 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4193 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004194 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004195
4196 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004197 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004198 if (width2 > width)
4199 {
4200 vim_free(textline);
4201 textline = alloc(width2 + 1);
4202 if (textline == NULL)
4203 goto theend;
4204 width = width2;
4205 textline[width] = NUL;
4206 }
4207 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4208
4209 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4210 {
4211 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4212 {
4213 /* bottom part has fewer rows, fill with "-" */
4214 for (i = 0; i < width; ++i)
4215 textline[i] = '-';
4216 }
4217 else
4218 {
4219 char_u *line1;
4220 char_u *line2;
4221 char_u *p1;
4222 char_u *p2;
4223 int col;
4224 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4225 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4226 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4227 ->sb_cells;
4228
4229 /* Make a copy, getting the second line will invalidate it. */
4230 line1 = vim_strsave(ml_get(lnum));
4231 if (line1 == NULL)
4232 break;
4233 p1 = line1;
4234
4235 line2 = ml_get(lnum + bot_lnum);
4236 p2 = line2;
4237 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4238 {
4239 int len1 = utfc_ptr2len(p1);
4240 int len2 = utfc_ptr2len(p2);
4241
4242 textline[col] = ' ';
4243 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004244 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004245 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004246 else if (lnum == cursor_pos1.row + 1
4247 && col == cursor_pos1.col
4248 && (cursor_pos1.row != cursor_pos2.row
4249 || cursor_pos1.col != cursor_pos2.col))
4250 /* cursor in first but not in second */
4251 textline[col] = '>';
4252 else if (lnum == cursor_pos2.row + 1
4253 && col == cursor_pos2.col
4254 && (cursor_pos1.row != cursor_pos2.row
4255 || cursor_pos1.col != cursor_pos2.col))
4256 /* cursor in second but not in first */
4257 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004258 else if (cellattr1 != NULL && cellattr2 != NULL)
4259 {
4260 if ((cellattr1 + col)->width
4261 != (cellattr2 + col)->width)
4262 textline[col] = 'w';
4263 else if (!same_color(&(cellattr1 + col)->fg,
4264 &(cellattr2 + col)->fg))
4265 textline[col] = 'f';
4266 else if (!same_color(&(cellattr1 + col)->bg,
4267 &(cellattr2 + col)->bg))
4268 textline[col] = 'b';
4269 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4270 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4271 textline[col] = 'a';
4272 }
4273 p1 += len1;
4274 p2 += len2;
4275 /* TODO: handle different width */
4276 }
4277 vim_free(line1);
4278
4279 while (col < width)
4280 {
4281 if (*p1 == NUL && *p2 == NUL)
4282 textline[col] = '?';
4283 else if (*p1 == NUL)
4284 {
4285 textline[col] = '+';
4286 p2 += utfc_ptr2len(p2);
4287 }
4288 else
4289 {
4290 textline[col] = '-';
4291 p1 += utfc_ptr2len(p1);
4292 }
4293 ++col;
4294 }
4295 }
4296 if (add_empty_scrollback(term, &term->tl_default_color,
4297 term->tl_top_diff_rows) == OK)
4298 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4299 ++bot_lnum;
4300 }
4301
4302 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4303 {
4304 /* bottom part has more rows, fill with "+" */
4305 for (i = 0; i < width; ++i)
4306 textline[i] = '+';
4307 if (add_empty_scrollback(term, &term->tl_default_color,
4308 term->tl_top_diff_rows) == OK)
4309 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4310 ++lnum;
4311 ++bot_lnum;
4312 }
4313
4314 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004315
4316 /* looks better without wrapping */
4317 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004318 }
4319
4320theend:
4321 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004322 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004323 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004324 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004325 fclose(fd2);
4326}
4327
4328/*
4329 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4330 * bottom files.
4331 * Return FAIL when this is not possible.
4332 */
4333 int
4334term_swap_diff()
4335{
4336 term_T *term = curbuf->b_term;
4337 linenr_T line_count;
4338 linenr_T top_rows;
4339 linenr_T bot_rows;
4340 linenr_T bot_start;
4341 linenr_T lnum;
4342 char_u *p;
4343 sb_line_T *sb_line;
4344
4345 if (term == NULL
4346 || !term_is_finished(curbuf)
4347 || term->tl_top_diff_rows == 0
4348 || term->tl_scrollback.ga_len == 0)
4349 return FAIL;
4350
4351 line_count = curbuf->b_ml.ml_line_count;
4352 top_rows = term->tl_top_diff_rows;
4353 bot_rows = term->tl_bot_diff_rows;
4354 bot_start = line_count - bot_rows;
4355 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4356
4357 /* move lines from top to above the bottom part */
4358 for (lnum = 1; lnum <= top_rows; ++lnum)
4359 {
4360 p = vim_strsave(ml_get(1));
4361 if (p == NULL)
4362 return OK;
4363 ml_append(bot_start, p, 0, FALSE);
4364 ml_delete(1, FALSE);
4365 vim_free(p);
4366 }
4367
4368 /* move lines from bottom to the top */
4369 for (lnum = 1; lnum <= bot_rows; ++lnum)
4370 {
4371 p = vim_strsave(ml_get(bot_start + lnum));
4372 if (p == NULL)
4373 return OK;
4374 ml_delete(bot_start + lnum, FALSE);
4375 ml_append(lnum - 1, p, 0, FALSE);
4376 vim_free(p);
4377 }
4378
4379 if (top_rows == bot_rows)
4380 {
4381 /* rows counts are equal, can swap cell properties */
4382 for (lnum = 0; lnum < top_rows; ++lnum)
4383 {
4384 sb_line_T temp;
4385
4386 temp = *(sb_line + lnum);
4387 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4388 *(sb_line + bot_start + lnum) = temp;
4389 }
4390 }
4391 else
4392 {
4393 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4394 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4395
4396 /* need to copy cell properties into temp memory */
4397 if (temp != NULL)
4398 {
4399 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4400 mch_memmove(term->tl_scrollback.ga_data,
4401 temp + bot_start,
4402 sizeof(sb_line_T) * bot_rows);
4403 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4404 temp + top_rows,
4405 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4406 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4407 + line_count - top_rows,
4408 temp,
4409 sizeof(sb_line_T) * top_rows);
4410 vim_free(temp);
4411 }
4412 }
4413
4414 term->tl_top_diff_rows = bot_rows;
4415 term->tl_bot_diff_rows = top_rows;
4416
4417 update_screen(NOT_VALID);
4418 return OK;
4419}
4420
4421/*
4422 * "term_dumpdiff(filename, filename, options)" function
4423 */
4424 void
4425f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4426{
4427 term_load_dump(argvars, rettv, TRUE);
4428}
4429
4430/*
4431 * "term_dumpload(filename, options)" function
4432 */
4433 void
4434f_term_dumpload(typval_T *argvars, typval_T *rettv)
4435{
4436 term_load_dump(argvars, rettv, FALSE);
4437}
4438
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004439/*
4440 * "term_getaltscreen(buf)" function
4441 */
4442 void
4443f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4444{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004445 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004446
4447 if (buf == NULL)
4448 return;
4449 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4450}
4451
4452/*
4453 * "term_getattr(attr, name)" function
4454 */
4455 void
4456f_term_getattr(typval_T *argvars, typval_T *rettv)
4457{
4458 int attr;
4459 size_t i;
4460 char_u *name;
4461
4462 static struct {
4463 char *name;
4464 int attr;
4465 } attrs[] = {
4466 {"bold", HL_BOLD},
4467 {"italic", HL_ITALIC},
4468 {"underline", HL_UNDERLINE},
4469 {"strike", HL_STRIKETHROUGH},
4470 {"reverse", HL_INVERSE},
4471 };
4472
4473 attr = get_tv_number(&argvars[0]);
4474 name = get_tv_string_chk(&argvars[1]);
4475 if (name == NULL)
4476 return;
4477
4478 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4479 if (STRCMP(name, attrs[i].name) == 0)
4480 {
4481 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4482 break;
4483 }
4484}
4485
4486/*
4487 * "term_getcursor(buf)" function
4488 */
4489 void
4490f_term_getcursor(typval_T *argvars, typval_T *rettv)
4491{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004492 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004493 term_T *term;
4494 list_T *l;
4495 dict_T *d;
4496
4497 if (rettv_list_alloc(rettv) == FAIL)
4498 return;
4499 if (buf == NULL)
4500 return;
4501 term = buf->b_term;
4502
4503 l = rettv->vval.v_list;
4504 list_append_number(l, term->tl_cursor_pos.row + 1);
4505 list_append_number(l, term->tl_cursor_pos.col + 1);
4506
4507 d = dict_alloc();
4508 if (d != NULL)
4509 {
4510 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4511 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4512 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4513 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4514 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4515 ? (char_u *)"" : term->tl_cursor_color);
4516 list_append_dict(l, d);
4517 }
4518}
4519
4520/*
4521 * "term_getjob(buf)" function
4522 */
4523 void
4524f_term_getjob(typval_T *argvars, typval_T *rettv)
4525{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004526 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004527
4528 rettv->v_type = VAR_JOB;
4529 rettv->vval.v_job = NULL;
4530 if (buf == NULL)
4531 return;
4532
4533 rettv->vval.v_job = buf->b_term->tl_job;
4534 if (rettv->vval.v_job != NULL)
4535 ++rettv->vval.v_job->jv_refcount;
4536}
4537
4538 static int
4539get_row_number(typval_T *tv, term_T *term)
4540{
4541 if (tv->v_type == VAR_STRING
4542 && tv->vval.v_string != NULL
4543 && STRCMP(tv->vval.v_string, ".") == 0)
4544 return term->tl_cursor_pos.row;
4545 return (int)get_tv_number(tv) - 1;
4546}
4547
4548/*
4549 * "term_getline(buf, row)" function
4550 */
4551 void
4552f_term_getline(typval_T *argvars, typval_T *rettv)
4553{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004554 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004555 term_T *term;
4556 int row;
4557
4558 rettv->v_type = VAR_STRING;
4559 if (buf == NULL)
4560 return;
4561 term = buf->b_term;
4562 row = get_row_number(&argvars[1], term);
4563
4564 if (term->tl_vterm == NULL)
4565 {
4566 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4567
4568 /* vterm is finished, get the text from the buffer */
4569 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4570 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4571 }
4572 else
4573 {
4574 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4575 VTermRect rect;
4576 int len;
4577 char_u *p;
4578
4579 if (row < 0 || row >= term->tl_rows)
4580 return;
4581 len = term->tl_cols * MB_MAXBYTES + 1;
4582 p = alloc(len);
4583 if (p == NULL)
4584 return;
4585 rettv->vval.v_string = p;
4586
4587 rect.start_col = 0;
4588 rect.end_col = term->tl_cols;
4589 rect.start_row = row;
4590 rect.end_row = row + 1;
4591 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4592 }
4593}
4594
4595/*
4596 * "term_getscrolled(buf)" function
4597 */
4598 void
4599f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4600{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004601 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004602
4603 if (buf == NULL)
4604 return;
4605 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4606}
4607
4608/*
4609 * "term_getsize(buf)" function
4610 */
4611 void
4612f_term_getsize(typval_T *argvars, typval_T *rettv)
4613{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004614 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004615 list_T *l;
4616
4617 if (rettv_list_alloc(rettv) == FAIL)
4618 return;
4619 if (buf == NULL)
4620 return;
4621
4622 l = rettv->vval.v_list;
4623 list_append_number(l, buf->b_term->tl_rows);
4624 list_append_number(l, buf->b_term->tl_cols);
4625}
4626
4627/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004628 * "term_setsize(buf, rows, cols)" function
4629 */
4630 void
4631f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4632{
4633 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4634 term_T *term;
4635 varnumber_T rows, cols;
4636
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004637 if (buf == NULL)
4638 {
4639 EMSG(_("E955: Not a terminal buffer"));
4640 return;
4641 }
4642 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004643 return;
4644 term = buf->b_term;
4645 rows = get_tv_number(&argvars[1]);
4646 rows = rows <= 0 ? term->tl_rows : rows;
4647 cols = get_tv_number(&argvars[2]);
4648 cols = cols <= 0 ? term->tl_cols : cols;
4649 vterm_set_size(term->tl_vterm, rows, cols);
4650 /* handle_resize() will resize the windows */
4651
4652 /* Get and remember the size we ended up with. Update the pty. */
4653 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4654 term_report_winsize(term, term->tl_rows, term->tl_cols);
4655}
4656
4657/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004658 * "term_getstatus(buf)" function
4659 */
4660 void
4661f_term_getstatus(typval_T *argvars, typval_T *rettv)
4662{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004663 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004664 term_T *term;
4665 char_u val[100];
4666
4667 rettv->v_type = VAR_STRING;
4668 if (buf == NULL)
4669 return;
4670 term = buf->b_term;
4671
4672 if (term_job_running(term))
4673 STRCPY(val, "running");
4674 else
4675 STRCPY(val, "finished");
4676 if (term->tl_normal_mode)
4677 STRCAT(val, ",normal");
4678 rettv->vval.v_string = vim_strsave(val);
4679}
4680
4681/*
4682 * "term_gettitle(buf)" function
4683 */
4684 void
4685f_term_gettitle(typval_T *argvars, typval_T *rettv)
4686{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004687 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004688
4689 rettv->v_type = VAR_STRING;
4690 if (buf == NULL)
4691 return;
4692
4693 if (buf->b_term->tl_title != NULL)
4694 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4695}
4696
4697/*
4698 * "term_gettty(buf)" function
4699 */
4700 void
4701f_term_gettty(typval_T *argvars, typval_T *rettv)
4702{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004703 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004704 char_u *p;
4705 int num = 0;
4706
4707 rettv->v_type = VAR_STRING;
4708 if (buf == NULL)
4709 return;
4710 if (argvars[1].v_type != VAR_UNKNOWN)
4711 num = get_tv_number(&argvars[1]);
4712
4713 switch (num)
4714 {
4715 case 0:
4716 if (buf->b_term->tl_job != NULL)
4717 p = buf->b_term->tl_job->jv_tty_out;
4718 else
4719 p = buf->b_term->tl_tty_out;
4720 break;
4721 case 1:
4722 if (buf->b_term->tl_job != NULL)
4723 p = buf->b_term->tl_job->jv_tty_in;
4724 else
4725 p = buf->b_term->tl_tty_in;
4726 break;
4727 default:
4728 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4729 return;
4730 }
4731 if (p != NULL)
4732 rettv->vval.v_string = vim_strsave(p);
4733}
4734
4735/*
4736 * "term_list()" function
4737 */
4738 void
4739f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4740{
4741 term_T *tp;
4742 list_T *l;
4743
4744 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4745 return;
4746
4747 l = rettv->vval.v_list;
4748 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4749 if (tp != NULL && tp->tl_buffer != NULL)
4750 if (list_append_number(l,
4751 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4752 return;
4753}
4754
4755/*
4756 * "term_scrape(buf, row)" function
4757 */
4758 void
4759f_term_scrape(typval_T *argvars, typval_T *rettv)
4760{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004761 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004762 VTermScreen *screen = NULL;
4763 VTermPos pos;
4764 list_T *l;
4765 term_T *term;
4766 char_u *p;
4767 sb_line_T *line;
4768
4769 if (rettv_list_alloc(rettv) == FAIL)
4770 return;
4771 if (buf == NULL)
4772 return;
4773 term = buf->b_term;
4774
4775 l = rettv->vval.v_list;
4776 pos.row = get_row_number(&argvars[1], term);
4777
4778 if (term->tl_vterm != NULL)
4779 {
4780 screen = vterm_obtain_screen(term->tl_vterm);
4781 p = NULL;
4782 line = NULL;
4783 }
4784 else
4785 {
4786 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4787
4788 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4789 return;
4790 p = ml_get_buf(buf, lnum + 1, FALSE);
4791 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4792 }
4793
4794 for (pos.col = 0; pos.col < term->tl_cols; )
4795 {
4796 dict_T *dcell;
4797 int width;
4798 VTermScreenCellAttrs attrs;
4799 VTermColor fg, bg;
4800 char_u rgb[8];
4801 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4802 int off = 0;
4803 int i;
4804
4805 if (screen == NULL)
4806 {
4807 cellattr_T *cellattr;
4808 int len;
4809
4810 /* vterm has finished, get the cell from scrollback */
4811 if (pos.col >= line->sb_cols)
4812 break;
4813 cellattr = line->sb_cells + pos.col;
4814 width = cellattr->width;
4815 attrs = cellattr->attrs;
4816 fg = cellattr->fg;
4817 bg = cellattr->bg;
4818 len = MB_PTR2LEN(p);
4819 mch_memmove(mbs, p, len);
4820 mbs[len] = NUL;
4821 p += len;
4822 }
4823 else
4824 {
4825 VTermScreenCell cell;
4826 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4827 break;
4828 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4829 {
4830 if (cell.chars[i] == 0)
4831 break;
4832 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4833 }
4834 mbs[off] = NUL;
4835 width = cell.width;
4836 attrs = cell.attrs;
4837 fg = cell.fg;
4838 bg = cell.bg;
4839 }
4840 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004841 if (dcell == NULL)
4842 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004843 list_append_dict(l, dcell);
4844
4845 dict_add_nr_str(dcell, "chars", 0, mbs);
4846
4847 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4848 fg.red, fg.green, fg.blue);
4849 dict_add_nr_str(dcell, "fg", 0, rgb);
4850 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4851 bg.red, bg.green, bg.blue);
4852 dict_add_nr_str(dcell, "bg", 0, rgb);
4853
4854 dict_add_nr_str(dcell, "attr",
4855 cell2attr(attrs, fg, bg), NULL);
4856 dict_add_nr_str(dcell, "width", width, NULL);
4857
4858 ++pos.col;
4859 if (width == 2)
4860 ++pos.col;
4861 }
4862}
4863
4864/*
4865 * "term_sendkeys(buf, keys)" function
4866 */
4867 void
4868f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4869{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004870 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004871 char_u *msg;
4872 term_T *term;
4873
4874 rettv->v_type = VAR_UNKNOWN;
4875 if (buf == NULL)
4876 return;
4877
4878 msg = get_tv_string_chk(&argvars[1]);
4879 if (msg == NULL)
4880 return;
4881 term = buf->b_term;
4882 if (term->tl_vterm == NULL)
4883 return;
4884
4885 while (*msg != NUL)
4886 {
4887 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004888 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004889 }
4890}
4891
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004892#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
4893/*
4894 * "term_getansicolors(buf)" function
4895 */
4896 void
4897f_term_getansicolors(typval_T *argvars, typval_T *rettv)
4898{
4899 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
4900 term_T *term;
4901 VTermState *state;
4902 VTermColor color;
4903 char_u hexbuf[10];
4904 int index;
4905 list_T *list;
4906
4907 if (rettv_list_alloc(rettv) == FAIL)
4908 return;
4909
4910 if (buf == NULL)
4911 return;
4912 term = buf->b_term;
4913 if (term->tl_vterm == NULL)
4914 return;
4915
4916 list = rettv->vval.v_list;
4917 state = vterm_obtain_state(term->tl_vterm);
4918 for (index = 0; index < 16; index++)
4919 {
4920 vterm_state_get_palette_color(state, index, &color);
4921 sprintf((char *)hexbuf, "#%02x%02x%02x",
4922 color.red, color.green, color.blue);
4923 if (list_append_string(list, hexbuf, 7) == FAIL)
4924 return;
4925 }
4926}
4927
4928/*
4929 * "term_setansicolors(buf, list)" function
4930 */
4931 void
4932f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
4933{
4934 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
4935 term_T *term;
4936
4937 if (buf == NULL)
4938 return;
4939 term = buf->b_term;
4940 if (term->tl_vterm == NULL)
4941 return;
4942
4943 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
4944 {
4945 EMSG(_(e_listreq));
4946 return;
4947 }
4948
4949 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
4950 EMSG(_(e_invarg));
4951}
4952#endif
4953
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004954/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004955 * "term_setrestore(buf, command)" function
4956 */
4957 void
4958f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4959{
4960#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004961 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004962 term_T *term;
4963 char_u *cmd;
4964
4965 if (buf == NULL)
4966 return;
4967 term = buf->b_term;
4968 vim_free(term->tl_command);
4969 cmd = get_tv_string_chk(&argvars[1]);
4970 if (cmd != NULL)
4971 term->tl_command = vim_strsave(cmd);
4972 else
4973 term->tl_command = NULL;
4974#endif
4975}
4976
4977/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004978 * "term_setkill(buf, how)" function
4979 */
4980 void
4981f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4982{
4983 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4984 term_T *term;
4985 char_u *how;
4986
4987 if (buf == NULL)
4988 return;
4989 term = buf->b_term;
4990 vim_free(term->tl_kill);
4991 how = get_tv_string_chk(&argvars[1]);
4992 if (how != NULL)
4993 term->tl_kill = vim_strsave(how);
4994 else
4995 term->tl_kill = NULL;
4996}
4997
4998/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004999 * "term_start(command, options)" function
5000 */
5001 void
5002f_term_start(typval_T *argvars, typval_T *rettv)
5003{
5004 jobopt_T opt;
5005 buf_T *buf;
5006
5007 init_job_options(&opt);
5008 if (argvars[1].v_type != VAR_UNKNOWN
5009 && get_job_options(&argvars[1], &opt,
5010 JO_TIMEOUT_ALL + JO_STOPONEXIT
5011 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5012 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5013 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5014 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005015 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005016 + JO2_NORESTORE + JO2_TERM_KILL
5017 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005018 return;
5019
Bram Moolenaar13568252018-03-16 20:46:58 +01005020 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005021
5022 if (buf != NULL && buf->b_term != NULL)
5023 rettv->vval.v_number = buf->b_fnum;
5024}
5025
5026/*
5027 * "term_wait" function
5028 */
5029 void
5030f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5031{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005032 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005033
5034 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005035 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005036 if (buf->b_term->tl_job == NULL)
5037 {
5038 ch_log(NULL, "term_wait(): no job to wait for");
5039 return;
5040 }
5041 if (buf->b_term->tl_job->jv_channel == NULL)
5042 /* channel is closed, nothing to do */
5043 return;
5044
5045 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005046 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005047 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5048 {
5049 /* The job is dead, keep reading channel I/O until the channel is
5050 * closed. buf->b_term may become NULL if the terminal was closed while
5051 * waiting. */
5052 ch_log(NULL, "term_wait(): waiting for channel to close");
5053 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5054 {
5055 mch_check_messages();
5056 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005057 if (!buf_valid(buf))
5058 /* If the terminal is closed when the channel is closed the
5059 * buffer disappears. */
5060 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005061 ui_delay(10L, FALSE);
5062 }
5063 mch_check_messages();
5064 parse_queued_messages();
5065 }
5066 else
5067 {
5068 long wait = 10L;
5069
5070 mch_check_messages();
5071 parse_queued_messages();
5072
5073 /* Wait for some time for any channel I/O. */
5074 if (argvars[1].v_type != VAR_UNKNOWN)
5075 wait = get_tv_number(&argvars[1]);
5076 ui_delay(wait, TRUE);
5077 mch_check_messages();
5078
5079 /* Flushing messages on channels is hopefully sufficient.
5080 * TODO: is there a better way? */
5081 parse_queued_messages();
5082 }
5083}
5084
5085/*
5086 * Called when a channel has sent all the lines to a terminal.
5087 * Send a CTRL-D to mark the end of the text.
5088 */
5089 void
5090term_send_eof(channel_T *ch)
5091{
5092 term_T *term;
5093
5094 for (term = first_term; term != NULL; term = term->tl_next)
5095 if (term->tl_job == ch->ch_job)
5096 {
5097 if (term->tl_eof_chars != NULL)
5098 {
5099 channel_send(ch, PART_IN, term->tl_eof_chars,
5100 (int)STRLEN(term->tl_eof_chars), NULL);
5101 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5102 }
5103# ifdef WIN3264
5104 else
5105 /* Default: CTRL-D */
5106 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5107# endif
5108 }
5109}
5110
5111# if defined(WIN3264) || defined(PROTO)
5112
5113/**************************************
5114 * 2. MS-Windows implementation.
5115 */
5116
5117# ifndef PROTO
5118
5119#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5120#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005121#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005122
5123void* (*winpty_config_new)(UINT64, void*);
5124void* (*winpty_open)(void*, void*);
5125void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5126BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5127void (*winpty_config_set_mouse_mode)(void*, int);
5128void (*winpty_config_set_initial_size)(void*, int, int);
5129LPCWSTR (*winpty_conin_name)(void*);
5130LPCWSTR (*winpty_conout_name)(void*);
5131LPCWSTR (*winpty_conerr_name)(void*);
5132void (*winpty_free)(void*);
5133void (*winpty_config_free)(void*);
5134void (*winpty_spawn_config_free)(void*);
5135void (*winpty_error_free)(void*);
5136LPCWSTR (*winpty_error_msg)(void*);
5137BOOL (*winpty_set_size)(void*, int, int, void*);
5138HANDLE (*winpty_agent_process)(void*);
5139
5140#define WINPTY_DLL "winpty.dll"
5141
5142static HINSTANCE hWinPtyDLL = NULL;
5143# endif
5144
5145 static int
5146dyn_winpty_init(int verbose)
5147{
5148 int i;
5149 static struct
5150 {
5151 char *name;
5152 FARPROC *ptr;
5153 } winpty_entry[] =
5154 {
5155 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5156 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5157 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5158 {"winpty_config_set_mouse_mode",
5159 (FARPROC*)&winpty_config_set_mouse_mode},
5160 {"winpty_config_set_initial_size",
5161 (FARPROC*)&winpty_config_set_initial_size},
5162 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5163 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5164 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5165 {"winpty_free", (FARPROC*)&winpty_free},
5166 {"winpty_open", (FARPROC*)&winpty_open},
5167 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5168 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5169 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5170 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5171 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5172 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5173 {NULL, NULL}
5174 };
5175
5176 /* No need to initialize twice. */
5177 if (hWinPtyDLL)
5178 return OK;
5179 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5180 * winpty.dll. */
5181 if (*p_winptydll != NUL)
5182 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5183 if (!hWinPtyDLL)
5184 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5185 if (!hWinPtyDLL)
5186 {
5187 if (verbose)
5188 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5189 : (char_u *)WINPTY_DLL);
5190 return FAIL;
5191 }
5192 for (i = 0; winpty_entry[i].name != NULL
5193 && winpty_entry[i].ptr != NULL; ++i)
5194 {
5195 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5196 winpty_entry[i].name)) == NULL)
5197 {
5198 if (verbose)
5199 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5200 return FAIL;
5201 }
5202 }
5203
5204 return OK;
5205}
5206
5207/*
5208 * Create a new terminal of "rows" by "cols" cells.
5209 * Store a reference in "term".
5210 * Return OK or FAIL.
5211 */
5212 static int
5213term_and_job_init(
5214 term_T *term,
5215 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005216 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005217 jobopt_T *opt)
5218{
5219 WCHAR *cmd_wchar = NULL;
5220 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005221 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005222 channel_T *channel = NULL;
5223 job_T *job = NULL;
5224 DWORD error;
5225 HANDLE jo = NULL;
5226 HANDLE child_process_handle;
5227 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005228 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005229 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005230 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005231 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005232
5233 if (dyn_winpty_init(TRUE) == FAIL)
5234 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005235 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5236 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005237
5238 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005239 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005240 cmd = argvar->vval.v_string;
5241 }
5242 else if (argvar->v_type == VAR_LIST)
5243 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005244 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005245 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005246 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005247 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005248 if (cmd == NULL || *cmd == NUL)
5249 {
5250 EMSG(_(e_invarg));
5251 goto failed;
5252 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005253
5254 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005255 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005256 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005257 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005258 if (opt->jo_cwd != NULL)
5259 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005260
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005261 win32_build_env(opt->jo_env, &ga_env, TRUE);
5262 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005263
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005264 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5265 if (term->tl_winpty_config == NULL)
5266 goto failed;
5267
5268 winpty_config_set_mouse_mode(term->tl_winpty_config,
5269 WINPTY_MOUSE_MODE_FORCE);
5270 winpty_config_set_initial_size(term->tl_winpty_config,
5271 term->tl_cols, term->tl_rows);
5272 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5273 if (term->tl_winpty == NULL)
5274 goto failed;
5275
5276 spawn_config = winpty_spawn_config_new(
5277 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5278 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5279 NULL,
5280 cmd_wchar,
5281 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005282 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005283 &winpty_err);
5284 if (spawn_config == NULL)
5285 goto failed;
5286
5287 channel = add_channel();
5288 if (channel == NULL)
5289 goto failed;
5290
5291 job = job_alloc();
5292 if (job == NULL)
5293 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005294 if (argvar->v_type == VAR_STRING)
5295 {
5296 int argc;
5297
5298 build_argv_from_string(cmd, &job->jv_argv, &argc);
5299 }
5300 else
5301 {
5302 int argc;
5303
5304 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5305 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005306
5307 if (opt->jo_set & JO_IN_BUF)
5308 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5309
5310 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5311 &child_thread_handle, &error, &winpty_err))
5312 goto failed;
5313
5314 channel_set_pipes(channel,
5315 (sock_T)CreateFileW(
5316 winpty_conin_name(term->tl_winpty),
5317 GENERIC_WRITE, 0, NULL,
5318 OPEN_EXISTING, 0, NULL),
5319 (sock_T)CreateFileW(
5320 winpty_conout_name(term->tl_winpty),
5321 GENERIC_READ, 0, NULL,
5322 OPEN_EXISTING, 0, NULL),
5323 (sock_T)CreateFileW(
5324 winpty_conerr_name(term->tl_winpty),
5325 GENERIC_READ, 0, NULL,
5326 OPEN_EXISTING, 0, NULL));
5327
5328 /* Write lines with CR instead of NL. */
5329 channel->ch_write_text_mode = TRUE;
5330
5331 jo = CreateJobObject(NULL, NULL);
5332 if (jo == NULL)
5333 goto failed;
5334
5335 if (!AssignProcessToJobObject(jo, child_process_handle))
5336 {
5337 /* Failed, switch the way to terminate process with TerminateProcess. */
5338 CloseHandle(jo);
5339 jo = NULL;
5340 }
5341
5342 winpty_spawn_config_free(spawn_config);
5343 vim_free(cmd_wchar);
5344 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005345 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005346
5347 create_vterm(term, term->tl_rows, term->tl_cols);
5348
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005349#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5350 if (opt->jo_set2 & JO2_ANSI_COLORS)
5351 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5352 else
5353 init_vterm_ansi_colors(term->tl_vterm);
5354#endif
5355
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005356 channel_set_job(channel, job, opt);
5357 job_set_options(job, opt);
5358
5359 job->jv_channel = channel;
5360 job->jv_proc_info.hProcess = child_process_handle;
5361 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5362 job->jv_job_object = jo;
5363 job->jv_status = JOB_STARTED;
5364 job->jv_tty_in = utf16_to_enc(
5365 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5366 job->jv_tty_out = utf16_to_enc(
5367 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5368 ++job->jv_refcount;
5369 term->tl_job = job;
5370
5371 return OK;
5372
5373failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005374 ga_clear(&ga_cmd);
5375 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005376 vim_free(cmd_wchar);
5377 vim_free(cwd_wchar);
5378 if (spawn_config != NULL)
5379 winpty_spawn_config_free(spawn_config);
5380 if (channel != NULL)
5381 channel_clear(channel);
5382 if (job != NULL)
5383 {
5384 job->jv_channel = NULL;
5385 job_cleanup(job);
5386 }
5387 term->tl_job = NULL;
5388 if (jo != NULL)
5389 CloseHandle(jo);
5390 if (term->tl_winpty != NULL)
5391 winpty_free(term->tl_winpty);
5392 term->tl_winpty = NULL;
5393 if (term->tl_winpty_config != NULL)
5394 winpty_config_free(term->tl_winpty_config);
5395 term->tl_winpty_config = NULL;
5396 if (winpty_err != NULL)
5397 {
5398 char_u *msg = utf16_to_enc(
5399 (short_u *)winpty_error_msg(winpty_err), NULL);
5400
5401 EMSG(msg);
5402 winpty_error_free(winpty_err);
5403 }
5404 return FAIL;
5405}
5406
5407 static int
5408create_pty_only(term_T *term, jobopt_T *options)
5409{
5410 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5411 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5412 char in_name[80], out_name[80];
5413 channel_T *channel = NULL;
5414
5415 create_vterm(term, term->tl_rows, term->tl_cols);
5416
5417 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5418 GetCurrentProcessId(),
5419 curbuf->b_fnum);
5420 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5421 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5422 PIPE_UNLIMITED_INSTANCES,
5423 0, 0, NMPWAIT_NOWAIT, NULL);
5424 if (hPipeIn == INVALID_HANDLE_VALUE)
5425 goto failed;
5426
5427 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5428 GetCurrentProcessId(),
5429 curbuf->b_fnum);
5430 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5431 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5432 PIPE_UNLIMITED_INSTANCES,
5433 0, 0, 0, NULL);
5434 if (hPipeOut == INVALID_HANDLE_VALUE)
5435 goto failed;
5436
5437 ConnectNamedPipe(hPipeIn, NULL);
5438 ConnectNamedPipe(hPipeOut, NULL);
5439
5440 term->tl_job = job_alloc();
5441 if (term->tl_job == NULL)
5442 goto failed;
5443 ++term->tl_job->jv_refcount;
5444
5445 /* behave like the job is already finished */
5446 term->tl_job->jv_status = JOB_FINISHED;
5447
5448 channel = add_channel();
5449 if (channel == NULL)
5450 goto failed;
5451 term->tl_job->jv_channel = channel;
5452 channel->ch_keep_open = TRUE;
5453 channel->ch_named_pipe = TRUE;
5454
5455 channel_set_pipes(channel,
5456 (sock_T)hPipeIn,
5457 (sock_T)hPipeOut,
5458 (sock_T)hPipeOut);
5459 channel_set_job(channel, term->tl_job, options);
5460 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5461 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5462
5463 return OK;
5464
5465failed:
5466 if (hPipeIn != NULL)
5467 CloseHandle(hPipeIn);
5468 if (hPipeOut != NULL)
5469 CloseHandle(hPipeOut);
5470 return FAIL;
5471}
5472
5473/*
5474 * Free the terminal emulator part of "term".
5475 */
5476 static void
5477term_free_vterm(term_T *term)
5478{
5479 if (term->tl_winpty != NULL)
5480 winpty_free(term->tl_winpty);
5481 term->tl_winpty = NULL;
5482 if (term->tl_winpty_config != NULL)
5483 winpty_config_free(term->tl_winpty_config);
5484 term->tl_winpty_config = NULL;
5485 if (term->tl_vterm != NULL)
5486 vterm_free(term->tl_vterm);
5487 term->tl_vterm = NULL;
5488}
5489
5490/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005491 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005492 */
5493 static void
5494term_report_winsize(term_T *term, int rows, int cols)
5495{
5496 if (term->tl_winpty)
5497 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5498}
5499
5500 int
5501terminal_enabled(void)
5502{
5503 return dyn_winpty_init(FALSE) == OK;
5504}
5505
5506# else
5507
5508/**************************************
5509 * 3. Unix-like implementation.
5510 */
5511
5512/*
5513 * Create a new terminal of "rows" by "cols" cells.
5514 * Start job for "cmd".
5515 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005516 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005517 * Return OK or FAIL.
5518 */
5519 static int
5520term_and_job_init(
5521 term_T *term,
5522 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005523 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005524 jobopt_T *opt)
5525{
5526 create_vterm(term, term->tl_rows, term->tl_cols);
5527
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005528#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5529 if (opt->jo_set2 & JO2_ANSI_COLORS)
5530 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5531 else
5532 init_vterm_ansi_colors(term->tl_vterm);
5533#endif
5534
Bram Moolenaar13568252018-03-16 20:46:58 +01005535 /* This may change a string in "argvar". */
5536 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005537 if (term->tl_job != NULL)
5538 ++term->tl_job->jv_refcount;
5539
5540 return term->tl_job != NULL
5541 && term->tl_job->jv_channel != NULL
5542 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5543}
5544
5545 static int
5546create_pty_only(term_T *term, jobopt_T *opt)
5547{
5548 create_vterm(term, term->tl_rows, term->tl_cols);
5549
5550 term->tl_job = job_alloc();
5551 if (term->tl_job == NULL)
5552 return FAIL;
5553 ++term->tl_job->jv_refcount;
5554
5555 /* behave like the job is already finished */
5556 term->tl_job->jv_status = JOB_FINISHED;
5557
5558 return mch_create_pty_channel(term->tl_job, opt);
5559}
5560
5561/*
5562 * Free the terminal emulator part of "term".
5563 */
5564 static void
5565term_free_vterm(term_T *term)
5566{
5567 if (term->tl_vterm != NULL)
5568 vterm_free(term->tl_vterm);
5569 term->tl_vterm = NULL;
5570}
5571
5572/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005573 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005574 */
5575 static void
5576term_report_winsize(term_T *term, int rows, int cols)
5577{
5578 /* Use an ioctl() to report the new window size to the job. */
5579 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5580 {
5581 int fd = -1;
5582 int part;
5583
5584 for (part = PART_OUT; part < PART_COUNT; ++part)
5585 {
5586 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5587 if (isatty(fd))
5588 break;
5589 }
5590 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5591 mch_signal_job(term->tl_job, (char_u *)"winch");
5592 }
5593}
5594
5595# endif
5596
5597#endif /* FEAT_TERMINAL */