blob: 9b336ee871c66f6f8e357ece724962182edef6db [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 Moolenaar6d150f72018-04-21 20:03:20 +020045 * - Add test for 'termwinkey'.
Bram Moolenaar802bfb12018-04-15 17:28:13 +020046 * - When starting terminal window with shell in terminal, then using :gui to
47 * switch to GUI, shell stops working. Scrollback seems wrong, command
48 * running in shell is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020049 * - GUI: when using tabs, focus in terminal, click on tab does not work.
Bram Moolenaara997b452018-04-17 23:24:06 +020050 * - handle_moverect() scrolls one line at a time. Postpone scrolling, count
51 * the number of lines, until a redraw happens. Then if scrolling many lines
52 * a redraw is faster.
Bram Moolenaar498c2562018-04-15 23:45:15 +020053 * - Copy text in the vterm to the Vim buffer once in a while, so that
54 * completion works.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020055 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020056 * - For the GUI fill termios with default values, perhaps like pangoterm:
57 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar802bfb12018-04-15 17:28:13 +020058 * - When 'encoding' is not utf-8, or the job is using another encoding, setup
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020059 * conversions.
Bram Moolenaar498c2562018-04-15 23:45:15 +020060 * - Termdebug does not work when Vim build with mzscheme: gdb hangs just after
61 * "run". Everything else works, including communication channel. Not
62 * initializing mzscheme avoid the problem, thus it's not some #ifdef.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020063 */
64
65#include "vim.h"
66
67#if defined(FEAT_TERMINAL) || defined(PROTO)
68
69#ifndef MIN
70# define MIN(x,y) ((x) < (y) ? (x) : (y))
71#endif
72#ifndef MAX
73# define MAX(x,y) ((x) > (y) ? (x) : (y))
74#endif
75
76#include "libvterm/include/vterm.h"
77
78/* This is VTermScreenCell without the characters, thus much smaller. */
79typedef struct {
80 VTermScreenCellAttrs attrs;
81 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010082 VTermColor fg;
83 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020084} cellattr_T;
85
86typedef struct sb_line_S {
87 int sb_cols; /* can differ per line */
88 cellattr_T *sb_cells; /* allocated */
89 cellattr_T sb_fill_attr; /* for short line */
90} sb_line_T;
91
92/* typedef term_T in structs.h */
93struct terminal_S {
94 term_T *tl_next;
95
96 VTerm *tl_vterm;
97 job_T *tl_job;
98 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010099#if defined(FEAT_GUI)
100 int tl_system; /* when non-zero used for :!cmd output */
101 int tl_toprow; /* row with first line of system terminal */
102#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200103
104 /* Set when setting the size of a vterm, reset after redrawing. */
105 int tl_vterm_size_changed;
106
107 /* used when tl_job is NULL and only a pty was created */
108 int tl_tty_fd;
109 char_u *tl_tty_in;
110 char_u *tl_tty_out;
111
112 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
113 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100114 int tl_finish;
115#define TL_FINISH_UNSET NUL
116#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
117#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
118#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200119 char_u *tl_opencmd;
120 char_u *tl_eof_chars;
121
122#ifdef WIN3264
123 void *tl_winpty_config;
124 void *tl_winpty;
125#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100126#if defined(FEAT_SESSION)
127 char_u *tl_command;
128#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100129 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200130
131 /* last known vterm size */
132 int tl_rows;
133 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200134
135 char_u *tl_title; /* NULL or allocated */
136 char_u *tl_status_text; /* NULL or allocated */
137
138 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200139 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200140 int tl_dirty_row_end; /* row below last one to update */
141
142 garray_T tl_scrollback;
143 int tl_scrollback_scrolled;
144 cellattr_T tl_default_color;
145
Bram Moolenaard96ff162018-02-18 22:13:29 +0100146 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
147 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
148
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200149 VTermPos tl_cursor_pos;
150 int tl_cursor_visible;
151 int tl_cursor_blink;
152 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
153 char_u *tl_cursor_color; /* NULL or allocated */
154
155 int tl_using_altscreen;
156};
157
158#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
159#define TMODE_LOOP 2 /* CTRL-W N used */
160
161/*
162 * List of all active terminals.
163 */
164static term_T *first_term = NULL;
165
166/* Terminal active in terminal_loop(). */
167static term_T *in_terminal_loop = NULL;
168
169#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
170#define KEY_BUF_LEN 200
171
172/*
173 * Functions with separate implementation for MS-Windows and Unix-like systems.
174 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100175static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200176static int create_pty_only(term_T *term, jobopt_T *opt);
177static void term_report_winsize(term_T *term, int rows, int cols);
178static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100179#ifdef FEAT_GUI
180static void update_system_term(term_T *term);
181#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200182
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100183/* The character that we know (or assume) that the terminal expects for the
184 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200185static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200186
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100187/* "Terminal" highlight group colors. */
188static int term_default_cterm_fg = -1;
189static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200190
Bram Moolenaard317b382018-02-08 22:33:31 +0100191/* Store the last set and the desired cursor properties, so that we only update
192 * them when needed. Doing it unnecessary may result in flicker. */
193static char_u *last_set_cursor_color = (char_u *)"";
194static char_u *desired_cursor_color = (char_u *)"";
195static int last_set_cursor_shape = -1;
196static int desired_cursor_shape = -1;
197static int last_set_cursor_blink = -1;
198static int desired_cursor_blink = -1;
199
200
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200201/**************************************
202 * 1. Generic code for all systems.
203 */
204
205/*
Bram Moolenaar498c2562018-04-15 23:45:15 +0200206 * Parse 'termsize' and set "rows" and "cols" for the terminal size in the
207 * current window.
208 * Sets "rows" and/or "cols" to zero when it should follow the window size.
209 * Return TRUE if the size is the minimum size: "24*80".
210 */
211 static int
212parse_termsize(win_T *wp, int *rows, int *cols)
213{
214 int minsize = FALSE;
215
216 *rows = 0;
217 *cols = 0;
218
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200219 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200220 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200221 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200222
223 /* Syntax of value was already checked when it's set. */
224 if (p == NULL)
225 {
226 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200227 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200228 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200229 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200230 *cols = atoi((char *)p + 1);
231 }
232 return minsize;
233}
234
235/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200236 * Determine the terminal size from 'termsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200237 */
238 static void
239set_term_and_win_size(term_T *term)
240{
Bram Moolenaar13568252018-03-16 20:46:58 +0100241#ifdef FEAT_GUI
242 if (term->tl_system)
243 {
244 /* Use the whole screen for the system command. However, it will start
245 * at the command line and scroll up as needed, using tl_toprow. */
246 term->tl_rows = Rows;
247 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200248 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100249 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100250#endif
Bram Moolenaar498c2562018-04-15 23:45:15 +0200251 if (parse_termsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200252 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200253 if (term->tl_rows != 0)
254 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
255 if (term->tl_cols != 0)
256 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200257 }
258 if (term->tl_rows == 0)
259 term->tl_rows = curwin->w_height;
260 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200261 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200262 if (term->tl_cols == 0)
263 term->tl_cols = curwin->w_width;
264 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200265 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200266}
267
268/*
269 * Initialize job options for a terminal job.
270 * Caller may overrule some of them.
271 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100272 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200273init_job_options(jobopt_T *opt)
274{
275 clear_job_options(opt);
276
277 opt->jo_mode = MODE_RAW;
278 opt->jo_out_mode = MODE_RAW;
279 opt->jo_err_mode = MODE_RAW;
280 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
281}
282
283/*
284 * Set job options mandatory for a terminal job.
285 */
286 static void
287setup_job_options(jobopt_T *opt, int rows, int cols)
288{
289 if (!(opt->jo_set & JO_OUT_IO))
290 {
291 /* Connect stdout to the terminal. */
292 opt->jo_io[PART_OUT] = JIO_BUFFER;
293 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
294 opt->jo_modifiable[PART_OUT] = 0;
295 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
296 }
297
298 if (!(opt->jo_set & JO_ERR_IO))
299 {
300 /* Connect stderr to the terminal. */
301 opt->jo_io[PART_ERR] = JIO_BUFFER;
302 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
303 opt->jo_modifiable[PART_ERR] = 0;
304 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
305 }
306
307 opt->jo_pty = TRUE;
308 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
309 opt->jo_term_rows = rows;
310 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
311 opt->jo_term_cols = cols;
312}
313
314/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100315 * Close a terminal buffer (and its window). Used when creating the terminal
316 * fails.
317 */
318 static void
319term_close_buffer(buf_T *buf, buf_T *old_curbuf)
320{
321 free_terminal(buf);
322 if (old_curbuf != NULL)
323 {
324 --curbuf->b_nwindows;
325 curbuf = old_curbuf;
326 curwin->w_buffer = curbuf;
327 ++curbuf->b_nwindows;
328 }
329
330 /* Wiping out the buffer will also close the window and call
331 * free_terminal(). */
332 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
333}
334
335/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200336 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100337 * Use either "argvar" or "argv", the other must be NULL.
338 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
339 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200340 * Returns NULL when failed.
341 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100342 buf_T *
343term_start(
344 typval_T *argvar,
345 char **argv,
346 jobopt_T *opt,
347 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200348{
349 exarg_T split_ea;
350 win_T *old_curwin = curwin;
351 term_T *term;
352 buf_T *old_curbuf = NULL;
353 int res;
354 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100355 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200356
357 if (check_restricted() || check_secure())
358 return NULL;
359
360 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
361 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
362 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
363 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
364 {
365 EMSG(_(e_invarg));
366 return NULL;
367 }
368
369 term = (term_T *)alloc_clear(sizeof(term_T));
370 if (term == NULL)
371 return NULL;
372 term->tl_dirty_row_end = MAX_ROW;
373 term->tl_cursor_visible = TRUE;
374 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
375 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100376#ifdef FEAT_GUI
377 term->tl_system = (flags & TERM_START_SYSTEM);
378#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200379 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
380
381 vim_memset(&split_ea, 0, sizeof(split_ea));
382 if (opt->jo_curwin)
383 {
384 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100385 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200386 {
387 no_write_message();
388 vim_free(term);
389 return NULL;
390 }
391 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100392 ECMD_HIDE
393 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
394 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200395 {
396 vim_free(term);
397 return NULL;
398 }
399 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100400 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200401 {
402 buf_T *buf;
403
404 /* Create a new buffer without a window. Make it the current buffer for
405 * a moment to be able to do the initialisations. */
406 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
407 BLN_NEW | BLN_LISTED);
408 if (buf == NULL || ml_open(buf) == FAIL)
409 {
410 vim_free(term);
411 return NULL;
412 }
413 old_curbuf = curbuf;
414 --curbuf->b_nwindows;
415 curbuf = buf;
416 curwin->w_buffer = buf;
417 ++curbuf->b_nwindows;
418 }
419 else
420 {
421 /* Open a new window or tab. */
422 split_ea.cmdidx = CMD_new;
423 split_ea.cmd = (char_u *)"new";
424 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100425 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200426 {
427 split_ea.line2 = opt->jo_term_rows;
428 split_ea.addr_count = 1;
429 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100430 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200431 {
432 split_ea.line2 = opt->jo_term_cols;
433 split_ea.addr_count = 1;
434 }
435
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100436 if (vertical)
437 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200438 ex_splitview(&split_ea);
439 if (curwin == old_curwin)
440 {
441 /* split failed */
442 vim_free(term);
443 return NULL;
444 }
445 }
446 term->tl_buffer = curbuf;
447 curbuf->b_term = term;
448
449 if (!opt->jo_hidden)
450 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100451 /* Only one size was taken care of with :new, do the other one. With
452 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100453 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200454 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100455 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200456 win_setwidth(opt->jo_term_cols);
457 }
458
459 /* Link the new terminal in the list of active terminals. */
460 term->tl_next = first_term;
461 first_term = term;
462
463 if (opt->jo_term_name != NULL)
464 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100465 else if (argv != NULL)
466 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200467 else
468 {
469 int i;
470 size_t len;
471 char_u *cmd, *p;
472
473 if (argvar->v_type == VAR_STRING)
474 {
475 cmd = argvar->vval.v_string;
476 if (cmd == NULL)
477 cmd = (char_u *)"";
478 else if (STRCMP(cmd, "NONE") == 0)
479 cmd = (char_u *)"pty";
480 }
481 else if (argvar->v_type != VAR_LIST
482 || argvar->vval.v_list == NULL
483 || argvar->vval.v_list->lv_len < 1
484 || (cmd = get_tv_string_chk(
485 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
486 cmd = (char_u*)"";
487
488 len = STRLEN(cmd) + 10;
489 p = alloc((int)len);
490
491 for (i = 0; p != NULL; ++i)
492 {
493 /* Prepend a ! to the command name to avoid the buffer name equals
494 * the executable, otherwise ":w!" would overwrite it. */
495 if (i == 0)
496 vim_snprintf((char *)p, len, "!%s", cmd);
497 else
498 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
499 if (buflist_findname(p) == NULL)
500 {
501 vim_free(curbuf->b_ffname);
502 curbuf->b_ffname = p;
503 break;
504 }
505 }
506 }
507 curbuf->b_fname = curbuf->b_ffname;
508
509 if (opt->jo_term_opencmd != NULL)
510 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
511
512 if (opt->jo_eof_chars != NULL)
513 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
514
515 set_string_option_direct((char_u *)"buftype", -1,
516 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
517
518 /* Mark the buffer as not modifiable. It can only be made modifiable after
519 * the job finished. */
520 curbuf->b_p_ma = FALSE;
521
522 set_term_and_win_size(term);
523 setup_job_options(opt, term->tl_rows, term->tl_cols);
524
Bram Moolenaar13568252018-03-16 20:46:58 +0100525 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100526 return curbuf;
527
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100528#if defined(FEAT_SESSION)
529 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100530 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100531 {
532 term->tl_command = vim_strsave((char_u *)"NONE");
533 }
534 else if (argvar->v_type == VAR_STRING)
535 {
536 char_u *cmd = argvar->vval.v_string;
537
538 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
539 term->tl_command = vim_strsave(cmd);
540 }
541 else if (argvar->v_type == VAR_LIST
542 && argvar->vval.v_list != NULL
543 && argvar->vval.v_list->lv_len > 0)
544 {
545 garray_T ga;
546 listitem_T *item;
547
548 ga_init2(&ga, 1, 100);
549 for (item = argvar->vval.v_list->lv_first;
550 item != NULL; item = item->li_next)
551 {
552 char_u *s = get_tv_string_chk(&item->li_tv);
553 char_u *p;
554
555 if (s == NULL)
556 break;
557 p = vim_strsave_fnameescape(s, FALSE);
558 if (p == NULL)
559 break;
560 ga_concat(&ga, p);
561 vim_free(p);
562 ga_append(&ga, ' ');
563 }
564 if (item == NULL)
565 {
566 ga_append(&ga, NUL);
567 term->tl_command = ga.ga_data;
568 }
569 else
570 ga_clear(&ga);
571 }
572#endif
573
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100574 if (opt->jo_term_kill != NULL)
575 {
576 char_u *p = skiptowhite(opt->jo_term_kill);
577
578 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
579 }
580
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200581 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100582 if (argv == NULL
583 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200584 && argvar->vval.v_string != NULL
585 && STRCMP(argvar->vval.v_string, "NONE") == 0)
586 res = create_pty_only(term, opt);
587 else
Bram Moolenaar13568252018-03-16 20:46:58 +0100588 res = term_and_job_init(term, argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200589
590 newbuf = curbuf;
591 if (res == OK)
592 {
593 /* Get and remember the size we ended up with. Update the pty. */
594 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
595 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100596#ifdef FEAT_GUI
597 if (term->tl_system)
598 {
599 /* display first line below typed command */
600 term->tl_toprow = msg_row + 1;
601 term->tl_dirty_row_end = 0;
602 }
603#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200604
605 /* Make sure we don't get stuck on sending keys to the job, it leads to
606 * a deadlock if the job is waiting for Vim to read. */
607 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
608
Bram Moolenaar13568252018-03-16 20:46:58 +0100609 if (old_curbuf == NULL)
Bram Moolenaarab5e7c32018-02-13 14:07:18 +0100610 {
611 ++curbuf->b_locked;
612 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
613 --curbuf->b_locked;
614 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100615 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200616 {
617 --curbuf->b_nwindows;
618 curbuf = old_curbuf;
619 curwin->w_buffer = curbuf;
620 ++curbuf->b_nwindows;
621 }
622 }
623 else
624 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100625 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200626 return NULL;
627 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100628
Bram Moolenaar13568252018-03-16 20:46:58 +0100629 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200630 return newbuf;
631}
632
633/*
634 * ":terminal": open a terminal window and execute a job in it.
635 */
636 void
637ex_terminal(exarg_T *eap)
638{
639 typval_T argvar[2];
640 jobopt_T opt;
641 char_u *cmd;
642 char_u *tofree = NULL;
643
644 init_job_options(&opt);
645
646 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100647 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200648 {
649 char_u *p, *ep;
650
651 cmd += 2;
652 p = skiptowhite(cmd);
653 ep = vim_strchr(cmd, '=');
654 if (ep != NULL && ep < p)
655 p = ep;
656
657 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
658 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100659 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
660 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200661 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
662 opt.jo_term_finish = 'o';
663 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
664 opt.jo_curwin = 1;
665 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
666 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100667 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
668 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100669 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
670 && ep != NULL)
671 {
672 opt.jo_set2 |= JO2_TERM_KILL;
673 opt.jo_term_kill = ep + 1;
674 p = skiptowhite(cmd);
675 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200676 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
677 && ep != NULL && isdigit(ep[1]))
678 {
679 opt.jo_set2 |= JO2_TERM_ROWS;
680 opt.jo_term_rows = atoi((char *)ep + 1);
681 p = skiptowhite(cmd);
682 }
683 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
684 && ep != NULL && isdigit(ep[1]))
685 {
686 opt.jo_set2 |= JO2_TERM_COLS;
687 opt.jo_term_cols = atoi((char *)ep + 1);
688 p = skiptowhite(cmd);
689 }
690 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
691 && ep != NULL)
692 {
693 char_u *buf = NULL;
694 char_u *keys;
695
696 p = skiptowhite(cmd);
697 *p = NUL;
698 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
699 opt.jo_set2 |= JO2_EOF_CHARS;
700 opt.jo_eof_chars = vim_strsave(keys);
701 vim_free(buf);
702 *p = ' ';
703 }
704 else
705 {
706 if (*p)
707 *p = NUL;
708 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100709 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200710 }
711 cmd = skipwhite(p);
712 }
713 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100714 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200715 /* Make a copy of 'shell', an autocommand may change the option. */
716 tofree = cmd = vim_strsave(p_sh);
717
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100718 /* default to close when the shell exits */
719 if (opt.jo_term_finish == NUL)
720 opt.jo_term_finish = 'c';
721 }
722
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200723 if (eap->addr_count > 0)
724 {
725 /* Write lines from current buffer to the job. */
726 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
727 opt.jo_io[PART_IN] = JIO_BUFFER;
728 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
729 opt.jo_in_top = eap->line1;
730 opt.jo_in_bot = eap->line2;
731 }
732
733 argvar[0].v_type = VAR_STRING;
734 argvar[0].vval.v_string = cmd;
735 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100736 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200737 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100738
739theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200740 vim_free(opt.jo_eof_chars);
741}
742
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100743#if defined(FEAT_SESSION) || defined(PROTO)
744/*
745 * Write a :terminal command to the session file to restore the terminal in
746 * window "wp".
747 * Return FAIL if writing fails.
748 */
749 int
750term_write_session(FILE *fd, win_T *wp)
751{
752 term_T *term = wp->w_buffer->b_term;
753
754 /* Create the terminal and run the command. This is not without
755 * risk, but let's assume the user only creates a session when this
756 * will be OK. */
757 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
758 term->tl_cols, term->tl_rows) < 0)
759 return FAIL;
760 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
761 return FAIL;
762
763 return put_eol(fd);
764}
765
766/*
767 * Return TRUE if "buf" has a terminal that should be restored.
768 */
769 int
770term_should_restore(buf_T *buf)
771{
772 term_T *term = buf->b_term;
773
774 return term != NULL && (term->tl_command == NULL
775 || STRCMP(term->tl_command, "NONE") != 0);
776}
777#endif
778
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200779/*
780 * Free the scrollback buffer for "term".
781 */
782 static void
783free_scrollback(term_T *term)
784{
785 int i;
786
787 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
788 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
789 ga_clear(&term->tl_scrollback);
790}
791
792/*
793 * Free a terminal and everything it refers to.
794 * Kills the job if there is one.
795 * Called when wiping out a buffer.
796 */
797 void
798free_terminal(buf_T *buf)
799{
800 term_T *term = buf->b_term;
801 term_T *tp;
802
803 if (term == NULL)
804 return;
805 if (first_term == term)
806 first_term = term->tl_next;
807 else
808 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
809 if (tp->tl_next == term)
810 {
811 tp->tl_next = term->tl_next;
812 break;
813 }
814
815 if (term->tl_job != NULL)
816 {
817 if (term->tl_job->jv_status != JOB_ENDED
818 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100819 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200820 job_stop(term->tl_job, NULL, "kill");
821 job_unref(term->tl_job);
822 }
823
824 free_scrollback(term);
825
826 term_free_vterm(term);
827 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100828#ifdef FEAT_SESSION
829 vim_free(term->tl_command);
830#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100831 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200832 vim_free(term->tl_status_text);
833 vim_free(term->tl_opencmd);
834 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100835 if (desired_cursor_color == term->tl_cursor_color)
836 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200837 vim_free(term->tl_cursor_color);
838 vim_free(term);
839 buf->b_term = NULL;
840 if (in_terminal_loop == term)
841 in_terminal_loop = NULL;
842}
843
844/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100845 * Get the part that is connected to the tty. Normally this is PART_IN, but
846 * when writing buffer lines to the job it can be another. This makes it
847 * possible to do "1,5term vim -".
848 */
849 static ch_part_T
850get_tty_part(term_T *term)
851{
852#ifdef UNIX
853 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
854 int i;
855
856 for (i = 0; i < 3; ++i)
857 {
858 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
859
860 if (isatty(fd))
861 return parts[i];
862 }
863#endif
864 return PART_IN;
865}
866
867/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200868 * Write job output "msg[len]" to the vterm.
869 */
870 static void
871term_write_job_output(term_T *term, char_u *msg, size_t len)
872{
873 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100874 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200875
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100876 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200877
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100878 /* flush vterm buffer when vterm responded to control sequence */
879 if (prevlen != vterm_output_get_buffer_current(vterm))
880 {
881 char buf[KEY_BUF_LEN];
882 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
883
884 if (curlen > 0)
885 channel_send(term->tl_job->jv_channel, get_tty_part(term),
886 (char_u *)buf, (int)curlen, NULL);
887 }
888
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200889 /* this invokes the damage callbacks */
890 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
891}
892
893 static void
894update_cursor(term_T *term, int redraw)
895{
896 if (term->tl_normal_mode)
897 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100898#ifdef FEAT_GUI
899 if (term->tl_system)
900 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
901 term->tl_cursor_pos.col);
902 else
903#endif
904 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200905 if (redraw)
906 {
907 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
908 cursor_on();
909 out_flush();
910#ifdef FEAT_GUI
911 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100912 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200913 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100914 gui_mch_flush();
915 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200916#endif
917 }
918}
919
920/*
921 * Invoked when "msg" output from a job was received. Write it to the terminal
922 * of "buffer".
923 */
924 void
925write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
926{
927 size_t len = STRLEN(msg);
928 term_T *term = buffer->b_term;
929
930 if (term->tl_vterm == NULL)
931 {
932 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
933 return;
934 }
935 ch_log(channel, "writing %d bytes to terminal", (int)len);
936 term_write_job_output(term, msg, len);
937
Bram Moolenaar13568252018-03-16 20:46:58 +0100938#ifdef FEAT_GUI
939 if (term->tl_system)
940 {
941 /* show system output, scrolling up the screen as needed */
942 update_system_term(term);
943 update_cursor(term, TRUE);
944 }
945 else
946#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200947 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
948 * contents, thus no screen update is needed. */
949 if (!term->tl_normal_mode)
950 {
951 /* TODO: only update once in a while. */
952 ch_log(term->tl_job->jv_channel, "updating screen");
953 if (buffer == curbuf)
954 {
955 update_screen(0);
956 update_cursor(term, TRUE);
957 }
958 else
959 redraw_after_callback(TRUE);
960 }
961}
962
963/*
964 * Send a mouse position and click to the vterm
965 */
966 static int
967term_send_mouse(VTerm *vterm, int button, int pressed)
968{
969 VTermModifier mod = VTERM_MOD_NONE;
970
971 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200972 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100973 if (button != 0)
974 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200975 return TRUE;
976}
977
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100978static int enter_mouse_col = -1;
979static int enter_mouse_row = -1;
980
981/*
982 * Handle a mouse click, drag or release.
983 * Return TRUE when a mouse event is sent to the terminal.
984 */
985 static int
986term_mouse_click(VTerm *vterm, int key)
987{
988#if defined(FEAT_CLIPBOARD)
989 /* For modeless selection mouse drag and release events are ignored, unless
990 * they are preceded with a mouse down event */
991 static int ignore_drag_release = TRUE;
992 VTermMouseState mouse_state;
993
994 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
995 if (mouse_state.flags == 0)
996 {
997 /* Terminal is not using the mouse, use modeless selection. */
998 switch (key)
999 {
1000 case K_LEFTDRAG:
1001 case K_LEFTRELEASE:
1002 case K_RIGHTDRAG:
1003 case K_RIGHTRELEASE:
1004 /* Ignore drag and release events when the button-down wasn't
1005 * seen before. */
1006 if (ignore_drag_release)
1007 {
1008 int save_mouse_col, save_mouse_row;
1009
1010 if (enter_mouse_col < 0)
1011 break;
1012
1013 /* mouse click in the window gave us focus, handle that
1014 * click now */
1015 save_mouse_col = mouse_col;
1016 save_mouse_row = mouse_row;
1017 mouse_col = enter_mouse_col;
1018 mouse_row = enter_mouse_row;
1019 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1020 mouse_col = save_mouse_col;
1021 mouse_row = save_mouse_row;
1022 }
1023 /* FALLTHROUGH */
1024 case K_LEFTMOUSE:
1025 case K_RIGHTMOUSE:
1026 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1027 ignore_drag_release = TRUE;
1028 else
1029 ignore_drag_release = FALSE;
1030 /* Should we call mouse_has() here? */
1031 if (clip_star.available)
1032 {
1033 int button, is_click, is_drag;
1034
1035 button = get_mouse_button(KEY2TERMCAP1(key),
1036 &is_click, &is_drag);
1037 if (mouse_model_popup() && button == MOUSE_LEFT
1038 && (mod_mask & MOD_MASK_SHIFT))
1039 {
1040 /* Translate shift-left to right button. */
1041 button = MOUSE_RIGHT;
1042 mod_mask &= ~MOD_MASK_SHIFT;
1043 }
1044 clip_modeless(button, is_click, is_drag);
1045 }
1046 break;
1047
1048 case K_MIDDLEMOUSE:
1049 if (clip_star.available)
1050 insert_reg('*', TRUE);
1051 break;
1052 }
1053 enter_mouse_col = -1;
1054 return FALSE;
1055 }
1056#endif
1057 enter_mouse_col = -1;
1058
1059 switch (key)
1060 {
1061 case K_LEFTMOUSE:
1062 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1063 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1064 case K_LEFTRELEASE:
1065 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1066 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1067 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1068 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1069 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1070 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1071 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1072 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1073 }
1074 return TRUE;
1075}
1076
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001077/*
1078 * Convert typed key "c" into bytes to send to the job.
1079 * Return the number of bytes in "buf".
1080 */
1081 static int
1082term_convert_key(term_T *term, int c, char *buf)
1083{
1084 VTerm *vterm = term->tl_vterm;
1085 VTermKey key = VTERM_KEY_NONE;
1086 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001087 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001088
1089 switch (c)
1090 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001091 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1092
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001093 /* don't use VTERM_KEY_BACKSPACE, it always
1094 * becomes 0x7f DEL */
1095 case K_BS: c = term_backspace_char; break;
1096
1097 case ESC: key = VTERM_KEY_ESCAPE; break;
1098 case K_DEL: key = VTERM_KEY_DEL; break;
1099 case K_DOWN: key = VTERM_KEY_DOWN; break;
1100 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1101 key = VTERM_KEY_DOWN; break;
1102 case K_END: key = VTERM_KEY_END; break;
1103 case K_S_END: mod = VTERM_MOD_SHIFT;
1104 key = VTERM_KEY_END; break;
1105 case K_C_END: mod = VTERM_MOD_CTRL;
1106 key = VTERM_KEY_END; break;
1107 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1108 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1109 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1110 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1111 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1112 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1113 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1114 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1115 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1116 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1117 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1118 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1119 case K_HOME: key = VTERM_KEY_HOME; break;
1120 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1121 key = VTERM_KEY_HOME; break;
1122 case K_C_HOME: mod = VTERM_MOD_CTRL;
1123 key = VTERM_KEY_HOME; break;
1124 case K_INS: key = VTERM_KEY_INS; break;
1125 case K_K0: key = VTERM_KEY_KP_0; break;
1126 case K_K1: key = VTERM_KEY_KP_1; break;
1127 case K_K2: key = VTERM_KEY_KP_2; break;
1128 case K_K3: key = VTERM_KEY_KP_3; break;
1129 case K_K4: key = VTERM_KEY_KP_4; break;
1130 case K_K5: key = VTERM_KEY_KP_5; break;
1131 case K_K6: key = VTERM_KEY_KP_6; break;
1132 case K_K7: key = VTERM_KEY_KP_7; break;
1133 case K_K8: key = VTERM_KEY_KP_8; break;
1134 case K_K9: key = VTERM_KEY_KP_9; break;
1135 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1136 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1137 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1138 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1139 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1140 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1141 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1142 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1143 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1144 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1145 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1146 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1147 case K_LEFT: key = VTERM_KEY_LEFT; break;
1148 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1149 key = VTERM_KEY_LEFT; break;
1150 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1151 key = VTERM_KEY_LEFT; break;
1152 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1153 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1154 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1155 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1156 key = VTERM_KEY_RIGHT; break;
1157 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1158 key = VTERM_KEY_RIGHT; break;
1159 case K_UP: key = VTERM_KEY_UP; break;
1160 case K_S_UP: mod = VTERM_MOD_SHIFT;
1161 key = VTERM_KEY_UP; break;
1162 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001163 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1164 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001165
Bram Moolenaara42ad572017-11-16 13:08:04 +01001166 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1167 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001168 case K_MOUSELEFT: /* TODO */ return 0;
1169 case K_MOUSERIGHT: /* TODO */ return 0;
1170
1171 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001172 case K_LEFTMOUSE_NM:
1173 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001174 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001175 case K_LEFTRELEASE_NM:
1176 case K_MOUSEMOVE:
1177 case K_MIDDLEMOUSE:
1178 case K_MIDDLEDRAG:
1179 case K_MIDDLERELEASE:
1180 case K_RIGHTMOUSE:
1181 case K_RIGHTDRAG:
1182 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1183 return 0;
1184 other = TRUE;
1185 break;
1186
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001187 case K_X1MOUSE: /* TODO */ return 0;
1188 case K_X1DRAG: /* TODO */ return 0;
1189 case K_X1RELEASE: /* TODO */ return 0;
1190 case K_X2MOUSE: /* TODO */ return 0;
1191 case K_X2DRAG: /* TODO */ return 0;
1192 case K_X2RELEASE: /* TODO */ return 0;
1193
1194 case K_IGNORE: return 0;
1195 case K_NOP: return 0;
1196 case K_UNDO: return 0;
1197 case K_HELP: return 0;
1198 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1199 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1200 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1201 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1202 case K_SELECT: return 0;
1203#ifdef FEAT_GUI
1204 case K_VER_SCROLLBAR: return 0;
1205 case K_HOR_SCROLLBAR: return 0;
1206#endif
1207#ifdef FEAT_GUI_TABLINE
1208 case K_TABLINE: return 0;
1209 case K_TABMENU: return 0;
1210#endif
1211#ifdef FEAT_NETBEANS_INTG
1212 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1213#endif
1214#ifdef FEAT_DND
1215 case K_DROP: return 0;
1216#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001217 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001218 case K_PS: vterm_keyboard_start_paste(vterm);
1219 other = TRUE;
1220 break;
1221 case K_PE: vterm_keyboard_end_paste(vterm);
1222 other = TRUE;
1223 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001224 }
1225
1226 /*
1227 * Convert special keys to vterm keys:
1228 * - Write keys to vterm: vterm_keyboard_key()
1229 * - Write output to channel.
1230 * TODO: use mod_mask
1231 */
1232 if (key != VTERM_KEY_NONE)
1233 /* Special key, let vterm convert it. */
1234 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001235 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001236 /* Normal character, let vterm convert it. */
1237 vterm_keyboard_unichar(vterm, c, mod);
1238
1239 /* Read back the converted escape sequence. */
1240 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1241}
1242
1243/*
1244 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001245 * If "check_job_status" is TRUE update the job status.
1246 */
1247 static int
1248term_job_running_check(term_T *term, int check_job_status)
1249{
1250 /* Also consider the job finished when the channel is closed, to avoid a
1251 * race condition when updating the title. */
1252 if (term != NULL
1253 && term->tl_job != NULL
1254 && channel_is_open(term->tl_job->jv_channel))
1255 {
1256 if (check_job_status)
1257 job_status(term->tl_job);
1258 return (term->tl_job->jv_status == JOB_STARTED
1259 || term->tl_job->jv_channel->ch_keep_open);
1260 }
1261 return FALSE;
1262}
1263
1264/*
1265 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001266 */
1267 int
1268term_job_running(term_T *term)
1269{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001270 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001271}
1272
1273/*
1274 * Return TRUE if "term" has an active channel and used ":term NONE".
1275 */
1276 int
1277term_none_open(term_T *term)
1278{
1279 /* Also consider the job finished when the channel is closed, to avoid a
1280 * race condition when updating the title. */
1281 return term != NULL
1282 && term->tl_job != NULL
1283 && channel_is_open(term->tl_job->jv_channel)
1284 && term->tl_job->jv_channel->ch_keep_open;
1285}
1286
1287/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001288 * Used when exiting: kill the job in "buf" if so desired.
1289 * Return OK when the job finished.
1290 * Return FAIL when the job is still running.
1291 */
1292 int
1293term_try_stop_job(buf_T *buf)
1294{
1295 int count;
1296 char *how = (char *)buf->b_term->tl_kill;
1297
1298#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1299 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1300 {
1301 char_u buff[DIALOG_MSG_SIZE];
1302 int ret;
1303
1304 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1305 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1306 if (ret == VIM_YES)
1307 how = "kill";
1308 else if (ret == VIM_CANCEL)
1309 return FAIL;
1310 }
1311#endif
1312 if (how == NULL || *how == NUL)
1313 return FAIL;
1314
1315 job_stop(buf->b_term->tl_job, NULL, how);
1316
1317 /* wait for up to a second for the job to die */
1318 for (count = 0; count < 100; ++count)
1319 {
1320 /* buffer, terminal and job may be cleaned up while waiting */
1321 if (!buf_valid(buf)
1322 || buf->b_term == NULL
1323 || buf->b_term->tl_job == NULL)
1324 return OK;
1325
1326 /* call job_status() to update jv_status */
1327 job_status(buf->b_term->tl_job);
1328 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1329 return OK;
1330 ui_delay(10L, FALSE);
1331 mch_check_messages();
1332 parse_queued_messages();
1333 }
1334 return FAIL;
1335}
1336
1337/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001338 * Add the last line of the scrollback buffer to the buffer in the window.
1339 */
1340 static void
1341add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1342{
1343 buf_T *buf = term->tl_buffer;
1344 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1345 linenr_T lnum = buf->b_ml.ml_line_count;
1346
1347#ifdef WIN3264
1348 if (!enc_utf8 && enc_codepage > 0)
1349 {
1350 WCHAR *ret = NULL;
1351 int length = 0;
1352
1353 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1354 &ret, &length);
1355 if (ret != NULL)
1356 {
1357 WideCharToMultiByte_alloc(enc_codepage, 0,
1358 ret, length, (char **)&text, &len, 0, 0);
1359 vim_free(ret);
1360 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1361 vim_free(text);
1362 }
1363 }
1364 else
1365#endif
1366 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1367 if (empty)
1368 {
1369 /* Delete the empty line that was in the empty buffer. */
1370 curbuf = buf;
1371 ml_delete(1, FALSE);
1372 curbuf = curwin->w_buffer;
1373 }
1374}
1375
1376 static void
1377cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1378{
1379 attr->width = cell->width;
1380 attr->attrs = cell->attrs;
1381 attr->fg = cell->fg;
1382 attr->bg = cell->bg;
1383}
1384
1385 static int
1386equal_celattr(cellattr_T *a, cellattr_T *b)
1387{
1388 /* Comparing the colors should be sufficient. */
1389 return a->fg.red == b->fg.red
1390 && a->fg.green == b->fg.green
1391 && a->fg.blue == b->fg.blue
1392 && a->bg.red == b->bg.red
1393 && a->bg.green == b->bg.green
1394 && a->bg.blue == b->bg.blue;
1395}
1396
Bram Moolenaard96ff162018-02-18 22:13:29 +01001397/*
1398 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1399 * line at this position. Otherwise at the end.
1400 */
1401 static int
1402add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1403{
1404 if (ga_grow(&term->tl_scrollback, 1) == OK)
1405 {
1406 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1407 + term->tl_scrollback.ga_len;
1408
1409 if (lnum > 0)
1410 {
1411 int i;
1412
1413 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1414 {
1415 *line = *(line - 1);
1416 --line;
1417 }
1418 }
1419 line->sb_cols = 0;
1420 line->sb_cells = NULL;
1421 line->sb_fill_attr = *fill_attr;
1422 ++term->tl_scrollback.ga_len;
1423 return OK;
1424 }
1425 return FALSE;
1426}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001427
1428/*
1429 * Add the current lines of the terminal to scrollback and to the buffer.
1430 * Called after the job has ended and when switching to Terminal-Normal mode.
1431 */
1432 static void
1433move_terminal_to_buffer(term_T *term)
1434{
1435 win_T *wp;
1436 int len;
1437 int lines_skipped = 0;
1438 VTermPos pos;
1439 VTermScreenCell cell;
1440 cellattr_T fill_attr, new_fill_attr;
1441 cellattr_T *p;
1442 VTermScreen *screen;
1443
1444 if (term->tl_vterm == NULL)
1445 return;
1446 screen = vterm_obtain_screen(term->tl_vterm);
1447 fill_attr = new_fill_attr = term->tl_default_color;
1448
1449 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1450 {
1451 len = 0;
1452 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1453 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1454 && cell.chars[0] != NUL)
1455 {
1456 len = pos.col + 1;
1457 new_fill_attr = term->tl_default_color;
1458 }
1459 else
1460 /* Assume the last attr is the filler attr. */
1461 cell2cellattr(&cell, &new_fill_attr);
1462
1463 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1464 ++lines_skipped;
1465 else
1466 {
1467 while (lines_skipped > 0)
1468 {
1469 /* Line was skipped, add an empty line. */
1470 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001471 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001472 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001473 }
1474
1475 if (len == 0)
1476 p = NULL;
1477 else
1478 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1479 if ((p != NULL || len == 0)
1480 && ga_grow(&term->tl_scrollback, 1) == OK)
1481 {
1482 garray_T ga;
1483 int width;
1484 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1485 + term->tl_scrollback.ga_len;
1486
1487 ga_init2(&ga, 1, 100);
1488 for (pos.col = 0; pos.col < len; pos.col += width)
1489 {
1490 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1491 {
1492 width = 1;
1493 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1494 if (ga_grow(&ga, 1) == OK)
1495 ga.ga_len += utf_char2bytes(' ',
1496 (char_u *)ga.ga_data + ga.ga_len);
1497 }
1498 else
1499 {
1500 width = cell.width;
1501
1502 cell2cellattr(&cell, &p[pos.col]);
1503
1504 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1505 {
1506 int i;
1507 int c;
1508
1509 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1510 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1511 (char_u *)ga.ga_data + ga.ga_len);
1512 }
1513 }
1514 }
1515 line->sb_cols = len;
1516 line->sb_cells = p;
1517 line->sb_fill_attr = new_fill_attr;
1518 fill_attr = new_fill_attr;
1519 ++term->tl_scrollback.ga_len;
1520
1521 if (ga_grow(&ga, 1) == FAIL)
1522 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1523 else
1524 {
1525 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1526 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1527 }
1528 ga_clear(&ga);
1529 }
1530 else
1531 vim_free(p);
1532 }
1533 }
1534
1535 /* Obtain the current background color. */
1536 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1537 &term->tl_default_color.fg, &term->tl_default_color.bg);
1538
1539 FOR_ALL_WINDOWS(wp)
1540 {
1541 if (wp->w_buffer == term->tl_buffer)
1542 {
1543 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1544 wp->w_cursor.col = 0;
1545 wp->w_valid = 0;
1546 if (wp->w_cursor.lnum >= wp->w_height)
1547 {
1548 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1549
1550 if (wp->w_topline < min_topline)
1551 wp->w_topline = min_topline;
1552 }
1553 redraw_win_later(wp, NOT_VALID);
1554 }
1555 }
1556}
1557
1558 static void
1559set_terminal_mode(term_T *term, int normal_mode)
1560{
1561 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001562 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001563 if (term->tl_buffer == curbuf)
1564 maketitle();
1565}
1566
1567/*
1568 * Called after the job if finished and Terminal mode is not active:
1569 * Move the vterm contents into the scrollback buffer and free the vterm.
1570 */
1571 static void
1572cleanup_vterm(term_T *term)
1573{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001574 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001575 move_terminal_to_buffer(term);
1576 term_free_vterm(term);
1577 set_terminal_mode(term, FALSE);
1578}
1579
1580/*
1581 * Switch from Terminal-Job mode to Terminal-Normal mode.
1582 * Suspends updating the terminal window.
1583 */
1584 static void
1585term_enter_normal_mode(void)
1586{
1587 term_T *term = curbuf->b_term;
1588
1589 /* Append the current terminal contents to the buffer. */
1590 move_terminal_to_buffer(term);
1591
1592 set_terminal_mode(term, TRUE);
1593
1594 /* Move the window cursor to the position of the cursor in the
1595 * terminal. */
1596 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1597 + term->tl_cursor_pos.row + 1;
1598 check_cursor();
1599 coladvance(term->tl_cursor_pos.col);
1600
1601 /* Display the same lines as in the terminal. */
1602 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1603}
1604
1605/*
1606 * Returns TRUE if the current window contains a terminal and we are in
1607 * Terminal-Normal mode.
1608 */
1609 int
1610term_in_normal_mode(void)
1611{
1612 term_T *term = curbuf->b_term;
1613
1614 return term != NULL && term->tl_normal_mode;
1615}
1616
1617/*
1618 * Switch from Terminal-Normal mode to Terminal-Job mode.
1619 * Restores updating the terminal window.
1620 */
1621 void
1622term_enter_job_mode()
1623{
1624 term_T *term = curbuf->b_term;
1625 sb_line_T *line;
1626 garray_T *gap;
1627
1628 /* Remove the terminal contents from the scrollback and the buffer. */
1629 gap = &term->tl_scrollback;
1630 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1631 && gap->ga_len > 0)
1632 {
1633 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1634 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1635 vim_free(line->sb_cells);
1636 --gap->ga_len;
1637 }
1638 check_cursor();
1639
1640 set_terminal_mode(term, FALSE);
1641
1642 if (term->tl_channel_closed)
1643 cleanup_vterm(term);
1644 redraw_buf_and_status_later(curbuf, NOT_VALID);
1645}
1646
1647/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001648 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001649 * Note: while waiting a terminal may be closed and freed if the channel is
1650 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001651 */
1652 static int
1653term_vgetc()
1654{
1655 int c;
1656 int save_State = State;
1657
1658 State = TERMINAL;
1659 got_int = FALSE;
1660#ifdef WIN3264
1661 ctrl_break_was_pressed = FALSE;
1662#endif
1663 c = vgetc();
1664 got_int = FALSE;
1665 State = save_State;
1666 return c;
1667}
1668
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001669static int mouse_was_outside = FALSE;
1670
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001671/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001672 * Send keys to terminal.
1673 * Return FAIL when the key needs to be handled in Normal mode.
1674 * Return OK when the key was dropped or sent to the terminal.
1675 */
1676 int
1677send_keys_to_term(term_T *term, int c, int typed)
1678{
1679 char msg[KEY_BUF_LEN];
1680 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001681 int dragging_outside = FALSE;
1682
1683 /* Catch keys that need to be handled as in Normal mode. */
1684 switch (c)
1685 {
1686 case NUL:
1687 case K_ZERO:
1688 if (typed)
1689 stuffcharReadbuff(c);
1690 return FAIL;
1691
1692 case K_IGNORE:
1693 return FAIL;
1694
1695 case K_LEFTDRAG:
1696 case K_MIDDLEDRAG:
1697 case K_RIGHTDRAG:
1698 case K_X1DRAG:
1699 case K_X2DRAG:
1700 dragging_outside = mouse_was_outside;
1701 /* FALLTHROUGH */
1702 case K_LEFTMOUSE:
1703 case K_LEFTMOUSE_NM:
1704 case K_LEFTRELEASE:
1705 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001706 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001707 case K_MIDDLEMOUSE:
1708 case K_MIDDLERELEASE:
1709 case K_RIGHTMOUSE:
1710 case K_RIGHTRELEASE:
1711 case K_X1MOUSE:
1712 case K_X1RELEASE:
1713 case K_X2MOUSE:
1714 case K_X2RELEASE:
1715
1716 case K_MOUSEUP:
1717 case K_MOUSEDOWN:
1718 case K_MOUSELEFT:
1719 case K_MOUSERIGHT:
1720 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001721 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001722 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001723 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001724 || dragging_outside)
1725 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001726 /* click or scroll outside the current window or on status line
1727 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001728 if (typed)
1729 {
1730 stuffcharReadbuff(c);
1731 mouse_was_outside = TRUE;
1732 }
1733 return FAIL;
1734 }
1735 }
1736 if (typed)
1737 mouse_was_outside = FALSE;
1738
1739 /* Convert the typed key to a sequence of bytes for the job. */
1740 len = term_convert_key(term, c, msg);
1741 if (len > 0)
1742 /* TODO: if FAIL is returned, stop? */
1743 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1744 (char_u *)msg, (int)len, NULL);
1745
1746 return OK;
1747}
1748
1749 static void
1750position_cursor(win_T *wp, VTermPos *pos)
1751{
1752 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1753 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1754 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1755}
1756
1757/*
1758 * Handle CTRL-W "": send register contents to the job.
1759 */
1760 static void
1761term_paste_register(int prev_c UNUSED)
1762{
1763 int c;
1764 list_T *l;
1765 listitem_T *item;
1766 long reglen = 0;
1767 int type;
1768
1769#ifdef FEAT_CMDL_INFO
1770 if (add_to_showcmd(prev_c))
1771 if (add_to_showcmd('"'))
1772 out_flush();
1773#endif
1774 c = term_vgetc();
1775#ifdef FEAT_CMDL_INFO
1776 clear_showcmd();
1777#endif
1778 if (!term_use_loop())
1779 /* job finished while waiting for a character */
1780 return;
1781
1782 /* CTRL-W "= prompt for expression to evaluate. */
1783 if (c == '=' && get_expr_register() != '=')
1784 return;
1785 if (!term_use_loop())
1786 /* job finished while waiting for a character */
1787 return;
1788
1789 l = (list_T *)get_reg_contents(c, GREG_LIST);
1790 if (l != NULL)
1791 {
1792 type = get_reg_type(c, &reglen);
1793 for (item = l->lv_first; item != NULL; item = item->li_next)
1794 {
1795 char_u *s = get_tv_string(&item->li_tv);
1796#ifdef WIN3264
1797 char_u *tmp = s;
1798
1799 if (!enc_utf8 && enc_codepage > 0)
1800 {
1801 WCHAR *ret = NULL;
1802 int length = 0;
1803
1804 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1805 (int)STRLEN(s), &ret, &length);
1806 if (ret != NULL)
1807 {
1808 WideCharToMultiByte_alloc(CP_UTF8, 0,
1809 ret, length, (char **)&s, &length, 0, 0);
1810 vim_free(ret);
1811 }
1812 }
1813#endif
1814 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1815 s, (int)STRLEN(s), NULL);
1816#ifdef WIN3264
1817 if (tmp != s)
1818 vim_free(s);
1819#endif
1820
1821 if (item->li_next != NULL || type == MLINE)
1822 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1823 (char_u *)"\r", 1, NULL);
1824 }
1825 list_free(l);
1826 }
1827}
1828
1829#if defined(FEAT_GUI) || defined(PROTO)
1830/*
1831 * Return TRUE when the cursor of the terminal should be displayed.
1832 */
1833 int
1834terminal_is_active()
1835{
1836 return in_terminal_loop != NULL;
1837}
1838
1839 cursorentry_T *
1840term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1841{
1842 term_T *term = in_terminal_loop;
1843 static cursorentry_T entry;
1844
1845 vim_memset(&entry, 0, sizeof(entry));
1846 entry.shape = entry.mshape =
1847 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1848 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1849 SHAPE_BLOCK;
1850 entry.percentage = 20;
1851 if (term->tl_cursor_blink)
1852 {
1853 entry.blinkwait = 700;
1854 entry.blinkon = 400;
1855 entry.blinkoff = 250;
1856 }
1857 *fg = gui.back_pixel;
1858 if (term->tl_cursor_color == NULL)
1859 *bg = gui.norm_pixel;
1860 else
1861 *bg = color_name2handle(term->tl_cursor_color);
1862 entry.name = "n";
1863 entry.used_for = SHAPE_CURSOR;
1864
1865 return &entry;
1866}
1867#endif
1868
Bram Moolenaard317b382018-02-08 22:33:31 +01001869 static void
1870may_output_cursor_props(void)
1871{
1872 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1873 || last_set_cursor_shape != desired_cursor_shape
1874 || last_set_cursor_blink != desired_cursor_blink)
1875 {
1876 last_set_cursor_color = desired_cursor_color;
1877 last_set_cursor_shape = desired_cursor_shape;
1878 last_set_cursor_blink = desired_cursor_blink;
1879 term_cursor_color(desired_cursor_color);
1880 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1881 /* this will restore the initial cursor style, if possible */
1882 ui_cursor_shape_forced(TRUE);
1883 else
1884 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1885 }
1886}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001887
Bram Moolenaard317b382018-02-08 22:33:31 +01001888/*
1889 * Set the cursor color and shape, if not last set to these.
1890 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001891 static void
1892may_set_cursor_props(term_T *term)
1893{
1894#ifdef FEAT_GUI
1895 /* For the GUI the cursor properties are obtained with
1896 * term_get_cursor_shape(). */
1897 if (gui.in_use)
1898 return;
1899#endif
1900 if (in_terminal_loop == term)
1901 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001902 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001903 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001904 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001905 desired_cursor_color = (char_u *)"";
1906 desired_cursor_shape = term->tl_cursor_shape;
1907 desired_cursor_blink = term->tl_cursor_blink;
1908 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001909 }
1910}
1911
Bram Moolenaard317b382018-02-08 22:33:31 +01001912/*
1913 * Reset the desired cursor properties and restore them when needed.
1914 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001915 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001916prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001917{
1918#ifdef FEAT_GUI
1919 if (gui.in_use)
1920 return;
1921#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001922 desired_cursor_color = (char_u *)"";
1923 desired_cursor_shape = -1;
1924 desired_cursor_blink = -1;
1925 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001926}
1927
1928/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001929 * Returns TRUE if the current window contains a terminal and we are sending
1930 * keys to the job.
1931 * If "check_job_status" is TRUE update the job status.
1932 */
1933 static int
1934term_use_loop_check(int check_job_status)
1935{
1936 term_T *term = curbuf->b_term;
1937
1938 return term != NULL
1939 && !term->tl_normal_mode
1940 && term->tl_vterm != NULL
1941 && term_job_running_check(term, check_job_status);
1942}
1943
1944/*
1945 * Returns TRUE if the current window contains a terminal and we are sending
1946 * keys to the job.
1947 */
1948 int
1949term_use_loop(void)
1950{
1951 return term_use_loop_check(FALSE);
1952}
1953
1954/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001955 * Called when entering a window with the mouse. If this is a terminal window
1956 * we may want to change state.
1957 */
1958 void
1959term_win_entered()
1960{
1961 term_T *term = curbuf->b_term;
1962
1963 if (term != NULL)
1964 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001965 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001966 {
1967 reset_VIsual_and_resel();
1968 if (State & INSERT)
1969 stop_insert_mode = TRUE;
1970 }
1971 mouse_was_outside = FALSE;
1972 enter_mouse_col = mouse_col;
1973 enter_mouse_row = mouse_row;
1974 }
1975}
1976
1977/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001978 * Wait for input and send it to the job.
1979 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1980 * when there is no more typahead.
1981 * Return when the start of a CTRL-W command is typed or anything else that
1982 * should be handled as a Normal mode command.
1983 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1984 * the terminal was closed.
1985 */
1986 int
1987terminal_loop(int blocking)
1988{
1989 int c;
1990 int termkey = 0;
1991 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001992#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001993 int tty_fd = curbuf->b_term->tl_job->jv_channel
1994 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001995#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001996 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001997
1998 /* Remember the terminal we are sending keys to. However, the terminal
1999 * might be closed while waiting for a character, e.g. typing "exit" in a
2000 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2001 * stored reference. */
2002 in_terminal_loop = curbuf->b_term;
2003
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002004 if (*curwin->w_p_twk != NUL)
2005 termkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002006 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2007 may_set_cursor_props(curbuf->b_term);
2008
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002009 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002010 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002011#ifdef FEAT_GUI
2012 if (!curbuf->b_term->tl_system)
2013#endif
2014 /* TODO: skip screen update when handling a sequence of keys. */
2015 /* Repeat redrawing in case a message is received while redrawing.
2016 */
2017 while (must_redraw != 0)
2018 if (update_screen(0) == FAIL)
2019 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002020 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002021 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002022
2023 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002024 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002025 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002026 /* Job finished while waiting for a character. Push back the
2027 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002028 if (c != K_IGNORE)
2029 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002030 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002031 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002032 if (c == K_IGNORE)
2033 continue;
2034
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002035#ifdef UNIX
2036 /*
2037 * The shell or another program may change the tty settings. Getting
2038 * them for every typed character is a bit of overhead, but it's needed
2039 * for the first character typed, e.g. when Vim starts in a shell.
2040 */
2041 if (isatty(tty_fd))
2042 {
2043 ttyinfo_T info;
2044
2045 /* Get the current backspace character of the pty. */
2046 if (get_tty_info(tty_fd, &info) == OK)
2047 term_backspace_char = info.backspace;
2048 }
2049#endif
2050
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002051#ifdef WIN3264
2052 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2053 * Use CTRL-BREAK to kill the job. */
2054 if (ctrl_break_was_pressed)
2055 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2056#endif
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002057 /* Was either CTRL-W (termkey) or CTRL-\ pressed?
2058 * Not in a system terminal. */
2059 if ((c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2060#ifdef FEAT_GUI
2061 && !curbuf->b_term->tl_system
2062#endif
2063 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002064 {
2065 int prev_c = c;
2066
2067#ifdef FEAT_CMDL_INFO
2068 if (add_to_showcmd(c))
2069 out_flush();
2070#endif
2071 c = term_vgetc();
2072#ifdef FEAT_CMDL_INFO
2073 clear_showcmd();
2074#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002075 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002076 /* job finished while waiting for a character */
2077 break;
2078
2079 if (prev_c == Ctrl_BSL)
2080 {
2081 if (c == Ctrl_N)
2082 {
2083 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2084 term_enter_normal_mode();
2085 ret = FAIL;
2086 goto theend;
2087 }
2088 /* Send both keys to the terminal. */
2089 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2090 }
2091 else if (c == Ctrl_C)
2092 {
2093 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2094 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2095 }
2096 else if (termkey == 0 && c == '.')
2097 {
2098 /* "CTRL-W .": send CTRL-W to the job */
2099 c = Ctrl_W;
2100 }
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002101 else if (termkey == 0 && c == Ctrl_BSL)
2102 {
2103 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2104 c = Ctrl_BSL;
2105 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002106 else if (c == 'N')
2107 {
2108 /* CTRL-W N : go to Terminal-Normal mode. */
2109 term_enter_normal_mode();
2110 ret = FAIL;
2111 goto theend;
2112 }
2113 else if (c == '"')
2114 {
2115 term_paste_register(prev_c);
2116 continue;
2117 }
2118 else if (termkey == 0 || c != termkey)
2119 {
2120 stuffcharReadbuff(Ctrl_W);
2121 stuffcharReadbuff(c);
2122 ret = OK;
2123 goto theend;
2124 }
2125 }
2126# ifdef WIN3264
2127 if (!enc_utf8 && has_mbyte && c >= 0x80)
2128 {
2129 WCHAR wc;
2130 char_u mb[3];
2131
2132 mb[0] = (unsigned)c >> 8;
2133 mb[1] = c;
2134 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2135 c = wc;
2136 }
2137# endif
2138 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2139 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002140 if (c == K_MOUSEMOVE)
2141 /* We are sure to come back here, don't reset the cursor color
2142 * and shape to avoid flickering. */
2143 restore_cursor = FALSE;
2144
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002145 ret = OK;
2146 goto theend;
2147 }
2148 }
2149 ret = FAIL;
2150
2151theend:
2152 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002153 if (restore_cursor)
2154 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002155 return ret;
2156}
2157
2158/*
2159 * Called when a job has finished.
2160 * This updates the title and status, but does not close the vterm, because
2161 * there might still be pending output in the channel.
2162 */
2163 void
2164term_job_ended(job_T *job)
2165{
2166 term_T *term;
2167 int did_one = FALSE;
2168
2169 for (term = first_term; term != NULL; term = term->tl_next)
2170 if (term->tl_job == job)
2171 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002172 VIM_CLEAR(term->tl_title);
2173 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002174 redraw_buf_and_status_later(term->tl_buffer, VALID);
2175 did_one = TRUE;
2176 }
2177 if (did_one)
2178 redraw_statuslines();
2179 if (curbuf->b_term != NULL)
2180 {
2181 if (curbuf->b_term->tl_job == job)
2182 maketitle();
2183 update_cursor(curbuf->b_term, TRUE);
2184 }
2185}
2186
2187 static void
2188may_toggle_cursor(term_T *term)
2189{
2190 if (in_terminal_loop == term)
2191 {
2192 if (term->tl_cursor_visible)
2193 cursor_on();
2194 else
2195 cursor_off();
2196 }
2197}
2198
2199/*
2200 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002201 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002202 */
2203 static int
2204color2index(VTermColor *color, int fg, int *boldp)
2205{
2206 int red = color->red;
2207 int blue = color->blue;
2208 int green = color->green;
2209
Bram Moolenaar46359e12017-11-29 22:33:38 +01002210 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002211 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002212 /* First 16 colors and default: use the ANSI index, because these
2213 * colors can be redefined. */
2214 if (t_colors >= 16)
2215 return color->ansi_index;
2216 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002217 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002218 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002219 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002220 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2221 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2222 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002223 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002224 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2225 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2226 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2227 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2228 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2229 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2230 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2231 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2232 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2233 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2234 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002235 }
2236 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002237
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002238 if (t_colors >= 256)
2239 {
2240 if (red == blue && red == green)
2241 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002242 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002243 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002244 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2245 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2246 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002247 int i;
2248
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002249 if (red < 5)
2250 return 17; /* 00/00/00 */
2251 if (red > 245) /* ff/ff/ff */
2252 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002253 for (i = 0; i < 23; ++i)
2254 if (red < cutoff[i])
2255 return i + 233;
2256 return 256;
2257 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002258 {
2259 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2260 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002261
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002262 /* 216-color cube */
2263 for (ri = 0; ri < 5; ++ri)
2264 if (red < cutoff[ri])
2265 break;
2266 for (gi = 0; gi < 5; ++gi)
2267 if (green < cutoff[gi])
2268 break;
2269 for (bi = 0; bi < 5; ++bi)
2270 if (blue < cutoff[bi])
2271 break;
2272 return 17 + ri * 36 + gi * 6 + bi;
2273 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002274 }
2275 return 0;
2276}
2277
2278/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002279 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002280 */
2281 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002282vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002283{
2284 int attr = 0;
2285
2286 if (cellattrs.bold)
2287 attr |= HL_BOLD;
2288 if (cellattrs.underline)
2289 attr |= HL_UNDERLINE;
2290 if (cellattrs.italic)
2291 attr |= HL_ITALIC;
2292 if (cellattrs.strike)
2293 attr |= HL_STRIKETHROUGH;
2294 if (cellattrs.reverse)
2295 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002296 return attr;
2297}
2298
2299/*
2300 * Store Vterm attributes in "cell" from highlight flags.
2301 */
2302 static void
2303hl2vtermAttr(int attr, cellattr_T *cell)
2304{
2305 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2306 if (attr & HL_BOLD)
2307 cell->attrs.bold = 1;
2308 if (attr & HL_UNDERLINE)
2309 cell->attrs.underline = 1;
2310 if (attr & HL_ITALIC)
2311 cell->attrs.italic = 1;
2312 if (attr & HL_STRIKETHROUGH)
2313 cell->attrs.strike = 1;
2314 if (attr & HL_INVERSE)
2315 cell->attrs.reverse = 1;
2316}
2317
2318/*
2319 * Convert the attributes of a vterm cell into an attribute index.
2320 */
2321 static int
2322cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2323{
2324 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002325
2326#ifdef FEAT_GUI
2327 if (gui.in_use)
2328 {
2329 guicolor_T fg, bg;
2330
2331 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2332 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2333 return get_gui_attr_idx(attr, fg, bg);
2334 }
2335 else
2336#endif
2337#ifdef FEAT_TERMGUICOLORS
2338 if (p_tgc)
2339 {
2340 guicolor_T fg, bg;
2341
2342 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2343 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2344
2345 return get_tgc_attr_idx(attr, fg, bg);
2346 }
2347 else
2348#endif
2349 {
2350 int bold = MAYBE;
2351 int fg = color2index(&cellfg, TRUE, &bold);
2352 int bg = color2index(&cellbg, FALSE, &bold);
2353
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002354 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002355 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002356 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002357 if (fg == 0 && term_default_cterm_fg >= 0)
2358 fg = term_default_cterm_fg + 1;
2359 if (bg == 0 && term_default_cterm_bg >= 0)
2360 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002361 }
2362
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002363 /* with 8 colors set the bold attribute to get a bright foreground */
2364 if (bold == TRUE)
2365 attr |= HL_BOLD;
2366 return get_cterm_attr_idx(attr, fg, bg);
2367 }
2368 return 0;
2369}
2370
2371 static int
2372handle_damage(VTermRect rect, void *user)
2373{
2374 term_T *term = (term_T *)user;
2375
2376 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2377 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2378 redraw_buf_later(term->tl_buffer, NOT_VALID);
2379 return 1;
2380}
2381
2382 static int
2383handle_moverect(VTermRect dest, VTermRect src, void *user)
2384{
2385 term_T *term = (term_T *)user;
2386
2387 /* Scrolling up is done much more efficiently by deleting lines instead of
2388 * redrawing the text. */
2389 if (dest.start_col == src.start_col
2390 && dest.end_col == src.end_col
2391 && dest.start_row < src.start_row)
2392 {
2393 win_T *wp;
2394 VTermColor fg, bg;
2395 VTermScreenCellAttrs attr;
2396 int clear_attr;
2397
2398 /* Set the color to clear lines with. */
2399 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2400 &fg, &bg);
2401 vim_memset(&attr, 0, sizeof(attr));
2402 clear_attr = cell2attr(attr, fg, bg);
2403
2404 FOR_ALL_WINDOWS(wp)
2405 {
2406 if (wp->w_buffer == term->tl_buffer)
2407 win_del_lines(wp, dest.start_row,
2408 src.start_row - dest.start_row, FALSE, FALSE,
2409 clear_attr);
2410 }
2411 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002412
2413 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2414 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2415
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002416 redraw_buf_later(term->tl_buffer, NOT_VALID);
2417 return 1;
2418}
2419
2420 static int
2421handle_movecursor(
2422 VTermPos pos,
2423 VTermPos oldpos UNUSED,
2424 int visible,
2425 void *user)
2426{
2427 term_T *term = (term_T *)user;
2428 win_T *wp;
2429
2430 term->tl_cursor_pos = pos;
2431 term->tl_cursor_visible = visible;
2432
2433 FOR_ALL_WINDOWS(wp)
2434 {
2435 if (wp->w_buffer == term->tl_buffer)
2436 position_cursor(wp, &pos);
2437 }
2438 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2439 {
2440 may_toggle_cursor(term);
2441 update_cursor(term, term->tl_cursor_visible);
2442 }
2443
2444 return 1;
2445}
2446
2447 static int
2448handle_settermprop(
2449 VTermProp prop,
2450 VTermValue *value,
2451 void *user)
2452{
2453 term_T *term = (term_T *)user;
2454
2455 switch (prop)
2456 {
2457 case VTERM_PROP_TITLE:
2458 vim_free(term->tl_title);
2459 /* a blank title isn't useful, make it empty, so that "running" is
2460 * displayed */
2461 if (*skipwhite((char_u *)value->string) == NUL)
2462 term->tl_title = NULL;
2463#ifdef WIN3264
2464 else if (!enc_utf8 && enc_codepage > 0)
2465 {
2466 WCHAR *ret = NULL;
2467 int length = 0;
2468
2469 MultiByteToWideChar_alloc(CP_UTF8, 0,
2470 (char*)value->string, (int)STRLEN(value->string),
2471 &ret, &length);
2472 if (ret != NULL)
2473 {
2474 WideCharToMultiByte_alloc(enc_codepage, 0,
2475 ret, length, (char**)&term->tl_title,
2476 &length, 0, 0);
2477 vim_free(ret);
2478 }
2479 }
2480#endif
2481 else
2482 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002483 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002484 if (term == curbuf->b_term)
2485 maketitle();
2486 break;
2487
2488 case VTERM_PROP_CURSORVISIBLE:
2489 term->tl_cursor_visible = value->boolean;
2490 may_toggle_cursor(term);
2491 out_flush();
2492 break;
2493
2494 case VTERM_PROP_CURSORBLINK:
2495 term->tl_cursor_blink = value->boolean;
2496 may_set_cursor_props(term);
2497 break;
2498
2499 case VTERM_PROP_CURSORSHAPE:
2500 term->tl_cursor_shape = value->number;
2501 may_set_cursor_props(term);
2502 break;
2503
2504 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002505 if (desired_cursor_color == term->tl_cursor_color)
2506 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002507 vim_free(term->tl_cursor_color);
2508 if (*value->string == NUL)
2509 term->tl_cursor_color = NULL;
2510 else
2511 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2512 may_set_cursor_props(term);
2513 break;
2514
2515 case VTERM_PROP_ALTSCREEN:
2516 /* TODO: do anything else? */
2517 term->tl_using_altscreen = value->boolean;
2518 break;
2519
2520 default:
2521 break;
2522 }
2523 /* Always return 1, otherwise vterm doesn't store the value internally. */
2524 return 1;
2525}
2526
2527/*
2528 * The job running in the terminal resized the terminal.
2529 */
2530 static int
2531handle_resize(int rows, int cols, void *user)
2532{
2533 term_T *term = (term_T *)user;
2534 win_T *wp;
2535
2536 term->tl_rows = rows;
2537 term->tl_cols = cols;
2538 if (term->tl_vterm_size_changed)
2539 /* Size was set by vterm_set_size(), don't set the window size. */
2540 term->tl_vterm_size_changed = FALSE;
2541 else
2542 {
2543 FOR_ALL_WINDOWS(wp)
2544 {
2545 if (wp->w_buffer == term->tl_buffer)
2546 {
2547 win_setheight_win(rows, wp);
2548 win_setwidth_win(cols, wp);
2549 }
2550 }
2551 redraw_buf_later(term->tl_buffer, NOT_VALID);
2552 }
2553 return 1;
2554}
2555
2556/*
2557 * Handle a line that is pushed off the top of the screen.
2558 */
2559 static int
2560handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2561{
2562 term_T *term = (term_T *)user;
2563
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002564 /* If the number of lines that are stored goes over 'termscrollback' then
2565 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002566 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002567 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002568 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002569 int i;
2570
2571 curbuf = term->tl_buffer;
2572 for (i = 0; i < todo; ++i)
2573 {
2574 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2575 ml_delete(1, FALSE);
2576 }
2577 curbuf = curwin->w_buffer;
2578
2579 term->tl_scrollback.ga_len -= todo;
2580 mch_memmove(term->tl_scrollback.ga_data,
2581 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2582 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2583 }
2584
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002585 if (ga_grow(&term->tl_scrollback, 1) == OK)
2586 {
2587 cellattr_T *p = NULL;
2588 int len = 0;
2589 int i;
2590 int c;
2591 int col;
2592 sb_line_T *line;
2593 garray_T ga;
2594 cellattr_T fill_attr = term->tl_default_color;
2595
2596 /* do not store empty cells at the end */
2597 for (i = 0; i < cols; ++i)
2598 if (cells[i].chars[0] != 0)
2599 len = i + 1;
2600 else
2601 cell2cellattr(&cells[i], &fill_attr);
2602
2603 ga_init2(&ga, 1, 100);
2604 if (len > 0)
2605 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2606 if (p != NULL)
2607 {
2608 for (col = 0; col < len; col += cells[col].width)
2609 {
2610 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2611 {
2612 ga.ga_len = 0;
2613 break;
2614 }
2615 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2616 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2617 (char_u *)ga.ga_data + ga.ga_len);
2618 cell2cellattr(&cells[col], &p[col]);
2619 }
2620 }
2621 if (ga_grow(&ga, 1) == FAIL)
2622 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2623 else
2624 {
2625 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2626 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2627 }
2628 ga_clear(&ga);
2629
2630 line = (sb_line_T *)term->tl_scrollback.ga_data
2631 + term->tl_scrollback.ga_len;
2632 line->sb_cols = len;
2633 line->sb_cells = p;
2634 line->sb_fill_attr = fill_attr;
2635 ++term->tl_scrollback.ga_len;
2636 ++term->tl_scrollback_scrolled;
2637 }
2638 return 0; /* ignored */
2639}
2640
2641static VTermScreenCallbacks screen_callbacks = {
2642 handle_damage, /* damage */
2643 handle_moverect, /* moverect */
2644 handle_movecursor, /* movecursor */
2645 handle_settermprop, /* settermprop */
2646 NULL, /* bell */
2647 handle_resize, /* resize */
2648 handle_pushline, /* sb_pushline */
2649 NULL /* sb_popline */
2650};
2651
2652/*
2653 * Called when a channel has been closed.
2654 * If this was a channel for a terminal window then finish it up.
2655 */
2656 void
2657term_channel_closed(channel_T *ch)
2658{
2659 term_T *term;
2660 int did_one = FALSE;
2661
2662 for (term = first_term; term != NULL; term = term->tl_next)
2663 if (term->tl_job == ch->ch_job)
2664 {
2665 term->tl_channel_closed = TRUE;
2666 did_one = TRUE;
2667
Bram Moolenaard23a8232018-02-10 18:45:26 +01002668 VIM_CLEAR(term->tl_title);
2669 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002670
2671 /* Unless in Terminal-Normal mode: clear the vterm. */
2672 if (!term->tl_normal_mode)
2673 {
2674 int fnum = term->tl_buffer->b_fnum;
2675
2676 cleanup_vterm(term);
2677
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002678 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002679 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002680 aco_save_T aco;
2681
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002682 /* ++close or term_finish == "close" */
2683 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002684 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002685 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002686 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002687 break;
2688 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002689 if (term->tl_finish == TL_FINISH_OPEN
2690 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002691 {
2692 char buf[50];
2693
2694 /* TODO: use term_opencmd */
2695 ch_log(NULL, "terminal job finished, opening window");
2696 vim_snprintf(buf, sizeof(buf),
2697 term->tl_opencmd == NULL
2698 ? "botright sbuf %d"
2699 : (char *)term->tl_opencmd, fnum);
2700 do_cmdline_cmd((char_u *)buf);
2701 }
2702 else
2703 ch_log(NULL, "terminal job finished");
2704 }
2705
2706 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2707 }
2708 if (did_one)
2709 {
2710 redraw_statuslines();
2711
2712 /* Need to break out of vgetc(). */
2713 ins_char_typebuf(K_IGNORE);
2714 typebuf_was_filled = TRUE;
2715
2716 term = curbuf->b_term;
2717 if (term != NULL)
2718 {
2719 if (term->tl_job == ch->ch_job)
2720 maketitle();
2721 update_cursor(term, term->tl_cursor_visible);
2722 }
2723 }
2724}
2725
2726/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002727 * Fill one screen line from a line of the terminal.
2728 * Advances "pos" to past the last column.
2729 */
2730 static void
2731term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2732{
2733 int off = screen_get_current_line_off();
2734
2735 for (pos->col = 0; pos->col < max_col; )
2736 {
2737 VTermScreenCell cell;
2738 int c;
2739
2740 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2741 vim_memset(&cell, 0, sizeof(cell));
2742
2743 c = cell.chars[0];
2744 if (c == NUL)
2745 {
2746 ScreenLines[off] = ' ';
2747 if (enc_utf8)
2748 ScreenLinesUC[off] = NUL;
2749 }
2750 else
2751 {
2752 if (enc_utf8)
2753 {
2754 int i;
2755
2756 /* composing chars */
2757 for (i = 0; i < Screen_mco
2758 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2759 {
2760 ScreenLinesC[i][off] = cell.chars[i + 1];
2761 if (cell.chars[i + 1] == 0)
2762 break;
2763 }
2764 if (c >= 0x80 || (Screen_mco > 0
2765 && ScreenLinesC[0][off] != 0))
2766 {
2767 ScreenLines[off] = ' ';
2768 ScreenLinesUC[off] = c;
2769 }
2770 else
2771 {
2772 ScreenLines[off] = c;
2773 ScreenLinesUC[off] = NUL;
2774 }
2775 }
2776#ifdef WIN3264
2777 else if (has_mbyte && c >= 0x80)
2778 {
2779 char_u mb[MB_MAXBYTES+1];
2780 WCHAR wc = c;
2781
2782 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2783 (char*)mb, 2, 0, 0) > 1)
2784 {
2785 ScreenLines[off] = mb[0];
2786 ScreenLines[off + 1] = mb[1];
2787 cell.width = mb_ptr2cells(mb);
2788 }
2789 else
2790 ScreenLines[off] = c;
2791 }
2792#endif
2793 else
2794 ScreenLines[off] = c;
2795 }
2796 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2797
2798 ++pos->col;
2799 ++off;
2800 if (cell.width == 2)
2801 {
2802 if (enc_utf8)
2803 ScreenLinesUC[off] = NUL;
2804
2805 /* don't set the second byte to NUL for a DBCS encoding, it
2806 * has been set above */
2807 if (enc_utf8 || !has_mbyte)
2808 ScreenLines[off] = NUL;
2809
2810 ++pos->col;
2811 ++off;
2812 }
2813 }
2814}
2815
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002816#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002817 static void
2818update_system_term(term_T *term)
2819{
2820 VTermPos pos;
2821 VTermScreen *screen;
2822
2823 if (term->tl_vterm == NULL)
2824 return;
2825 screen = vterm_obtain_screen(term->tl_vterm);
2826
2827 /* Scroll up to make more room for terminal lines if needed. */
2828 while (term->tl_toprow > 0
2829 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2830 {
2831 int save_p_more = p_more;
2832
2833 p_more = FALSE;
2834 msg_row = Rows - 1;
2835 msg_puts((char_u *)"\n");
2836 p_more = save_p_more;
2837 --term->tl_toprow;
2838 }
2839
2840 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2841 && pos.row < Rows; ++pos.row)
2842 {
2843 if (pos.row < term->tl_rows)
2844 {
2845 int max_col = MIN(Columns, term->tl_cols);
2846
2847 term_line2screenline(screen, &pos, max_col);
2848 }
2849 else
2850 pos.col = 0;
2851
2852 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2853 }
2854
2855 term->tl_dirty_row_start = MAX_ROW;
2856 term->tl_dirty_row_end = 0;
2857 update_cursor(term, TRUE);
2858}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002859#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002860
2861/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002862 * Called to update a window that contains an active terminal.
2863 * Returns FAIL when there is no terminal running in this window or in
2864 * Terminal-Normal mode.
2865 */
2866 int
2867term_update_window(win_T *wp)
2868{
2869 term_T *term = wp->w_buffer->b_term;
2870 VTerm *vterm;
2871 VTermScreen *screen;
2872 VTermState *state;
2873 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002874 int rows, cols;
2875 int newrows, newcols;
2876 int minsize;
2877 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002878
2879 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2880 return FAIL;
2881
2882 vterm = term->tl_vterm;
2883 screen = vterm_obtain_screen(vterm);
2884 state = vterm_obtain_state(vterm);
2885
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002886 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002887 {
2888 term->tl_dirty_row_start = 0;
2889 term->tl_dirty_row_end = MAX_ROW;
2890 }
2891
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002892 /*
2893 * If the window was resized a redraw will be triggered and we get here.
2894 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2895 */
Bram Moolenaar498c2562018-04-15 23:45:15 +02002896 minsize = parse_termsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002897
Bram Moolenaar498c2562018-04-15 23:45:15 +02002898 newrows = 99999;
2899 newcols = 99999;
2900 FOR_ALL_WINDOWS(twp)
2901 {
2902 /* When more than one window shows the same terminal, use the
2903 * smallest size. */
2904 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002905 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02002906 newrows = MIN(newrows, twp->w_height);
2907 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002908 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02002909 }
2910 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
2911 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
2912
2913 if (term->tl_rows != newrows || term->tl_cols != newcols)
2914 {
2915
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002916
2917 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002918 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002919 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02002920 newrows);
2921 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002922 }
2923
2924 /* The cursor may have been moved when resizing. */
2925 vterm_state_get_cursorpos(state, &pos);
2926 position_cursor(wp, &pos);
2927
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002928 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2929 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002930 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002931 if (pos.row < term->tl_rows)
2932 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002933 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002934
Bram Moolenaar13568252018-03-16 20:46:58 +01002935 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002936 }
2937 else
2938 pos.col = 0;
2939
Bram Moolenaarf118d482018-03-13 13:14:00 +01002940 screen_line(wp->w_winrow + pos.row
2941#ifdef FEAT_MENU
2942 + winbar_height(wp)
2943#endif
2944 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002945 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002946 term->tl_dirty_row_start = MAX_ROW;
2947 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002948
2949 return OK;
2950}
2951
2952/*
2953 * Return TRUE if "wp" is a terminal window where the job has finished.
2954 */
2955 int
2956term_is_finished(buf_T *buf)
2957{
2958 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2959}
2960
2961/*
2962 * Return TRUE if "wp" is a terminal window where the job has finished or we
2963 * are in Terminal-Normal mode, thus we show the buffer contents.
2964 */
2965 int
2966term_show_buffer(buf_T *buf)
2967{
2968 term_T *term = buf->b_term;
2969
2970 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2971}
2972
2973/*
2974 * The current buffer is going to be changed. If there is terminal
2975 * highlighting remove it now.
2976 */
2977 void
2978term_change_in_curbuf(void)
2979{
2980 term_T *term = curbuf->b_term;
2981
2982 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2983 {
2984 free_scrollback(term);
2985 redraw_buf_later(term->tl_buffer, NOT_VALID);
2986
2987 /* The buffer is now like a normal buffer, it cannot be easily
2988 * abandoned when changed. */
2989 set_string_option_direct((char_u *)"buftype", -1,
2990 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2991 }
2992}
2993
2994/*
2995 * Get the screen attribute for a position in the buffer.
2996 * Use a negative "col" to get the filler background color.
2997 */
2998 int
2999term_get_attr(buf_T *buf, linenr_T lnum, int col)
3000{
3001 term_T *term = buf->b_term;
3002 sb_line_T *line;
3003 cellattr_T *cellattr;
3004
3005 if (lnum > term->tl_scrollback.ga_len)
3006 cellattr = &term->tl_default_color;
3007 else
3008 {
3009 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3010 if (col < 0 || col >= line->sb_cols)
3011 cellattr = &line->sb_fill_attr;
3012 else
3013 cellattr = line->sb_cells + col;
3014 }
3015 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3016}
3017
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003018/*
3019 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003020 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003021 */
3022 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003023cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003024{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003025 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003026}
3027
3028/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003029 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003030 */
3031 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003032init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003033{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003034 VTermColor *fg, *bg;
3035 int fgval, bgval;
3036 int id;
3037
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003038 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3039 term->tl_default_color.width = 1;
3040 fg = &term->tl_default_color.fg;
3041 bg = &term->tl_default_color.bg;
3042
3043 /* Vterm uses a default black background. Set it to white when
3044 * 'background' is "light". */
3045 if (*p_bg == 'l')
3046 {
3047 fgval = 0;
3048 bgval = 255;
3049 }
3050 else
3051 {
3052 fgval = 255;
3053 bgval = 0;
3054 }
3055 fg->red = fg->green = fg->blue = fgval;
3056 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003057 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003058
3059 /* The "Terminal" highlight group overrules the defaults. */
3060 id = syn_name2id((char_u *)"Terminal");
3061
Bram Moolenaar46359e12017-11-29 22:33:38 +01003062 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003063#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3064 if (0
3065# ifdef FEAT_GUI
3066 || gui.in_use
3067# endif
3068# ifdef FEAT_TERMGUICOLORS
3069 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003070# ifdef FEAT_VTP
3071 /* Finally get INVALCOLOR on this execution path */
3072 || (!p_tgc && t_colors >= 256)
3073# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003074# endif
3075 )
3076 {
3077 guicolor_T fg_rgb = INVALCOLOR;
3078 guicolor_T bg_rgb = INVALCOLOR;
3079
3080 if (id != 0)
3081 syn_id2colors(id, &fg_rgb, &bg_rgb);
3082
3083# ifdef FEAT_GUI
3084 if (gui.in_use)
3085 {
3086 if (fg_rgb == INVALCOLOR)
3087 fg_rgb = gui.norm_pixel;
3088 if (bg_rgb == INVALCOLOR)
3089 bg_rgb = gui.back_pixel;
3090 }
3091# ifdef FEAT_TERMGUICOLORS
3092 else
3093# endif
3094# endif
3095# ifdef FEAT_TERMGUICOLORS
3096 {
3097 if (fg_rgb == INVALCOLOR)
3098 fg_rgb = cterm_normal_fg_gui_color;
3099 if (bg_rgb == INVALCOLOR)
3100 bg_rgb = cterm_normal_bg_gui_color;
3101 }
3102# endif
3103 if (fg_rgb != INVALCOLOR)
3104 {
3105 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3106
3107 fg->red = (unsigned)(rgb >> 16);
3108 fg->green = (unsigned)(rgb >> 8) & 255;
3109 fg->blue = (unsigned)rgb & 255;
3110 }
3111 if (bg_rgb != INVALCOLOR)
3112 {
3113 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3114
3115 bg->red = (unsigned)(rgb >> 16);
3116 bg->green = (unsigned)(rgb >> 8) & 255;
3117 bg->blue = (unsigned)rgb & 255;
3118 }
3119 }
3120 else
3121#endif
3122 if (id != 0 && t_colors >= 16)
3123 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003124 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003125 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003126 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003127 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003128 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003129 else
3130 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003131#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003132 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003133#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003134
3135 /* In an MS-Windows console we know the normal colors. */
3136 if (cterm_normal_fg_color > 0)
3137 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003138 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003139# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003140 tmp = fg->red;
3141 fg->red = fg->blue;
3142 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003143# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003144 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003145# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003146 else
3147 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003148# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003149
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003150 if (cterm_normal_bg_color > 0)
3151 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003152 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003153# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003154 tmp = bg->red;
3155 bg->red = bg->blue;
3156 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003157# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003158 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003159# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003160 else
3161 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003162# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003163 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003164}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003165
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003166#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3167/*
3168 * Set the 16 ANSI colors from array of RGB values
3169 */
3170 static void
3171set_vterm_palette(VTerm *vterm, long_u *rgb)
3172{
3173 int index = 0;
3174 VTermState *state = vterm_obtain_state(vterm);
3175 for (; index < 16; index++)
3176 {
3177 VTermColor color;
3178 color.red = (unsigned)(rgb[index] >> 16);
3179 color.green = (unsigned)(rgb[index] >> 8) & 255;
3180 color.blue = (unsigned)rgb[index] & 255;
3181 vterm_state_set_palette_color(state, index, &color);
3182 }
3183}
3184
3185/*
3186 * Set the ANSI color palette from a list of colors
3187 */
3188 static int
3189set_ansi_colors_list(VTerm *vterm, list_T *list)
3190{
3191 int n = 0;
3192 long_u rgb[16];
3193 listitem_T *li = list->lv_first;
3194
3195 for (; li != NULL && n < 16; li = li->li_next, n++)
3196 {
3197 char_u *color_name;
3198 guicolor_T guicolor;
3199
3200 color_name = get_tv_string_chk(&li->li_tv);
3201 if (color_name == NULL)
3202 return FAIL;
3203
3204 guicolor = GUI_GET_COLOR(color_name);
3205 if (guicolor == INVALCOLOR)
3206 return FAIL;
3207
3208 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3209 }
3210
3211 if (n != 16 || li != NULL)
3212 return FAIL;
3213
3214 set_vterm_palette(vterm, rgb);
3215
3216 return OK;
3217}
3218
3219/*
3220 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3221 */
3222 static void
3223init_vterm_ansi_colors(VTerm *vterm)
3224{
3225 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3226
3227 if (var != NULL
3228 && (var->di_tv.v_type != VAR_LIST
3229 || var->di_tv.vval.v_list == NULL
3230 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3231 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3232}
3233#endif
3234
Bram Moolenaar52acb112018-03-18 19:20:22 +01003235/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003236 * Handles a "drop" command from the job in the terminal.
3237 * "item" is the file name, "item->li_next" may have options.
3238 */
3239 static void
3240handle_drop_command(listitem_T *item)
3241{
3242 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003243 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003244 int bufnr;
3245 win_T *wp;
3246 tabpage_T *tp;
3247 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003248 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003249
3250 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3251 FOR_ALL_TAB_WINDOWS(tp, wp)
3252 {
3253 if (wp->w_buffer->b_fnum == bufnr)
3254 {
3255 /* buffer is in a window already, go there */
3256 goto_tabpage_win(tp, wp);
3257 return;
3258 }
3259 }
3260
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003261 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003262
3263 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3264 && opt_item->li_tv.vval.v_dict != NULL)
3265 {
3266 dict_T *dict = opt_item->li_tv.vval.v_dict;
3267 char_u *p;
3268
3269 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3270 if (p == NULL)
3271 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3272 if (p != NULL)
3273 {
3274 if (check_ff_value(p) == FAIL)
3275 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3276 else
3277 ea.force_ff = *p;
3278 }
3279 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3280 if (p == NULL)
3281 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3282 if (p != NULL)
3283 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003284 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003285 if (ea.cmd != NULL)
3286 {
3287 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3288 ea.force_enc = 11;
3289 tofree = ea.cmd;
3290 }
3291 }
3292
3293 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3294 if (p != NULL)
3295 get_bad_opt(p, &ea);
3296
3297 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3298 ea.force_bin = FORCE_BIN;
3299 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3300 ea.force_bin = FORCE_BIN;
3301 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3302 ea.force_bin = FORCE_NOBIN;
3303 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3304 ea.force_bin = FORCE_NOBIN;
3305 }
3306
3307 /* open in new window, like ":split fname" */
3308 if (ea.cmd == NULL)
3309 ea.cmd = (char_u *)"split";
3310 ea.arg = fname;
3311 ea.cmdidx = CMD_split;
3312 ex_splitview(&ea);
3313
3314 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003315}
3316
3317/*
3318 * Handles a function call from the job running in a terminal.
3319 * "item" is the function name, "item->li_next" has the arguments.
3320 */
3321 static void
3322handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3323{
3324 char_u *func;
3325 typval_T argvars[2];
3326 typval_T rettv;
3327 int doesrange;
3328
3329 if (item->li_next == NULL)
3330 {
3331 ch_log(channel, "Missing function arguments for call");
3332 return;
3333 }
3334 func = get_tv_string(&item->li_tv);
3335
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003336 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003337 {
3338 ch_log(channel, "Invalid function name: %s", func);
3339 return;
3340 }
3341
3342 argvars[0].v_type = VAR_NUMBER;
3343 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3344 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003345 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003346 2, argvars, /* argv_func */ NULL,
3347 /* firstline */ 1, /* lastline */ 1,
3348 &doesrange, /* evaluate */ TRUE,
3349 /* partial */ NULL, /* selfdict */ NULL) == OK)
3350 {
3351 clear_tv(&rettv);
3352 ch_log(channel, "Function %s called", func);
3353 }
3354 else
3355 ch_log(channel, "Calling function %s failed", func);
3356}
3357
3358/*
3359 * Called by libvterm when it cannot recognize an OSC sequence.
3360 * We recognize a terminal API command.
3361 */
3362 static int
3363parse_osc(const char *command, size_t cmdlen, void *user)
3364{
3365 term_T *term = (term_T *)user;
3366 js_read_T reader;
3367 typval_T tv;
3368 channel_T *channel = term->tl_job == NULL ? NULL
3369 : term->tl_job->jv_channel;
3370
3371 /* We recognize only OSC 5 1 ; {command} */
3372 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3373 return 0; /* not handled */
3374
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003375 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003376 if (reader.js_buf == NULL)
3377 return 1;
3378 reader.js_fill = NULL;
3379 reader.js_used = 0;
3380 if (json_decode(&reader, &tv, 0) == OK
3381 && tv.v_type == VAR_LIST
3382 && tv.vval.v_list != NULL)
3383 {
3384 listitem_T *item = tv.vval.v_list->lv_first;
3385
3386 if (item == NULL)
3387 ch_log(channel, "Missing command");
3388 else
3389 {
3390 char_u *cmd = get_tv_string(&item->li_tv);
3391
Bram Moolenaara997b452018-04-17 23:24:06 +02003392 /* Make sure an invoked command doesn't delete the buffer (and the
3393 * terminal) under our fingers. */
3394 ++term->tl_buffer->b_locked;
3395
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003396 item = item->li_next;
3397 if (item == NULL)
3398 ch_log(channel, "Missing argument for %s", cmd);
3399 else if (STRCMP(cmd, "drop") == 0)
3400 handle_drop_command(item);
3401 else if (STRCMP(cmd, "call") == 0)
3402 handle_call_command(term, channel, item);
3403 else
3404 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003405 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003406 }
3407 }
3408 else
3409 ch_log(channel, "Invalid JSON received");
3410
3411 vim_free(reader.js_buf);
3412 clear_tv(&tv);
3413 return 1;
3414}
3415
3416static VTermParserCallbacks parser_fallbacks = {
3417 NULL, /* text */
3418 NULL, /* control */
3419 NULL, /* escape */
3420 NULL, /* csi */
3421 parse_osc, /* osc */
3422 NULL, /* dcs */
3423 NULL /* resize */
3424};
3425
3426/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003427 * Use Vim's allocation functions for vterm so profiling works.
3428 */
3429 static void *
3430vterm_malloc(size_t size, void *data UNUSED)
3431{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003432 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003433}
3434
3435 static void
3436vterm_memfree(void *ptr, void *data UNUSED)
3437{
3438 vim_free(ptr);
3439}
3440
3441static VTermAllocatorFunctions vterm_allocator = {
3442 &vterm_malloc,
3443 &vterm_memfree
3444};
3445
3446/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003447 * Create a new vterm and initialize it.
3448 */
3449 static void
3450create_vterm(term_T *term, int rows, int cols)
3451{
3452 VTerm *vterm;
3453 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003454 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003455 VTermValue value;
3456
Bram Moolenaar756ef112018-04-10 12:04:27 +02003457 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003458 term->tl_vterm = vterm;
3459 screen = vterm_obtain_screen(vterm);
3460 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3461 /* TODO: depends on 'encoding'. */
3462 vterm_set_utf8(vterm, 1);
3463
3464 init_default_colors(term);
3465
3466 vterm_state_set_default_colors(
3467 vterm_obtain_state(vterm),
3468 &term->tl_default_color.fg,
3469 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003470
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003471 if (t_colors >= 16)
3472 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3473
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003474 /* Required to initialize most things. */
3475 vterm_screen_reset(screen, 1 /* hard */);
3476
3477 /* Allow using alternate screen. */
3478 vterm_screen_enable_altscreen(screen, 1);
3479
3480 /* For unix do not use a blinking cursor. In an xterm this causes the
3481 * cursor to blink if it's blinking in the xterm.
3482 * For Windows we respect the system wide setting. */
3483#ifdef WIN3264
3484 if (GetCaretBlinkTime() == INFINITE)
3485 value.boolean = 0;
3486 else
3487 value.boolean = 1;
3488#else
3489 value.boolean = 0;
3490#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003491 state = vterm_obtain_state(vterm);
3492 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3493 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003494}
3495
3496/*
3497 * Return the text to show for the buffer name and status.
3498 */
3499 char_u *
3500term_get_status_text(term_T *term)
3501{
3502 if (term->tl_status_text == NULL)
3503 {
3504 char_u *txt;
3505 size_t len;
3506
3507 if (term->tl_normal_mode)
3508 {
3509 if (term_job_running(term))
3510 txt = (char_u *)_("Terminal");
3511 else
3512 txt = (char_u *)_("Terminal-finished");
3513 }
3514 else if (term->tl_title != NULL)
3515 txt = term->tl_title;
3516 else if (term_none_open(term))
3517 txt = (char_u *)_("active");
3518 else if (term_job_running(term))
3519 txt = (char_u *)_("running");
3520 else
3521 txt = (char_u *)_("finished");
3522 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3523 term->tl_status_text = alloc((int)len);
3524 if (term->tl_status_text != NULL)
3525 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3526 term->tl_buffer->b_fname, txt);
3527 }
3528 return term->tl_status_text;
3529}
3530
3531/*
3532 * Mark references in jobs of terminals.
3533 */
3534 int
3535set_ref_in_term(int copyID)
3536{
3537 int abort = FALSE;
3538 term_T *term;
3539 typval_T tv;
3540
3541 for (term = first_term; term != NULL; term = term->tl_next)
3542 if (term->tl_job != NULL)
3543 {
3544 tv.v_type = VAR_JOB;
3545 tv.vval.v_job = term->tl_job;
3546 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3547 }
3548 return abort;
3549}
3550
3551/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003552 * Cache "Terminal" highlight group colors.
3553 */
3554 void
3555set_terminal_default_colors(int cterm_fg, int cterm_bg)
3556{
3557 term_default_cterm_fg = cterm_fg - 1;
3558 term_default_cterm_bg = cterm_bg - 1;
3559}
3560
3561/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003562 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003563 * Returns NULL when the buffer is not for a terminal window and logs a message
3564 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003565 */
3566 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003567term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003568{
3569 buf_T *buf;
3570
3571 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3572 ++emsg_off;
3573 buf = get_buf_tv(&argvars[0], FALSE);
3574 --emsg_off;
3575 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003576 {
3577 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003578 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003579 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003580 return buf;
3581}
3582
Bram Moolenaard96ff162018-02-18 22:13:29 +01003583 static int
3584same_color(VTermColor *a, VTermColor *b)
3585{
3586 return a->red == b->red
3587 && a->green == b->green
3588 && a->blue == b->blue
3589 && a->ansi_index == b->ansi_index;
3590}
3591
3592 static void
3593dump_term_color(FILE *fd, VTermColor *color)
3594{
3595 fprintf(fd, "%02x%02x%02x%d",
3596 (int)color->red, (int)color->green, (int)color->blue,
3597 (int)color->ansi_index);
3598}
3599
3600/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003601 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003602 *
3603 * Each screen cell in full is:
3604 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3605 * {characters} is a space for an empty cell
3606 * For a double-width character "+" is changed to "*" and the next cell is
3607 * skipped.
3608 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3609 * when "&" use the same as the previous cell.
3610 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3611 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3612 * {color-idx} is a number from 0 to 255
3613 *
3614 * Screen cell with same width, attributes and color as the previous one:
3615 * |{characters}
3616 *
3617 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3618 *
3619 * Repeating the previous screen cell:
3620 * @{count}
3621 */
3622 void
3623f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3624{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003625 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003626 term_T *term;
3627 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003628 int max_height = 0;
3629 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003630 stat_T st;
3631 FILE *fd;
3632 VTermPos pos;
3633 VTermScreen *screen;
3634 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003635 VTermState *state;
3636 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003637
3638 if (check_restricted() || check_secure())
3639 return;
3640 if (buf == NULL)
3641 return;
3642 term = buf->b_term;
3643
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003644 if (argvars[2].v_type != VAR_UNKNOWN)
3645 {
3646 dict_T *d;
3647
3648 if (argvars[2].v_type != VAR_DICT)
3649 {
3650 EMSG(_(e_dictreq));
3651 return;
3652 }
3653 d = argvars[2].vval.v_dict;
3654 if (d != NULL)
3655 {
3656 max_height = get_dict_number(d, (char_u *)"rows");
3657 max_width = get_dict_number(d, (char_u *)"columns");
3658 }
3659 }
3660
Bram Moolenaard96ff162018-02-18 22:13:29 +01003661 fname = get_tv_string_chk(&argvars[1]);
3662 if (fname == NULL)
3663 return;
3664 if (mch_stat((char *)fname, &st) >= 0)
3665 {
3666 EMSG2(_("E953: File exists: %s"), fname);
3667 return;
3668 }
3669
Bram Moolenaard96ff162018-02-18 22:13:29 +01003670 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3671 {
3672 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3673 return;
3674 }
3675
3676 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3677
3678 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003679 state = vterm_obtain_state(term->tl_vterm);
3680 vterm_state_get_cursorpos(state, &cursor_pos);
3681
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003682 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3683 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003684 {
3685 int repeat = 0;
3686
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003687 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3688 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003689 {
3690 VTermScreenCell cell;
3691 int same_attr;
3692 int same_chars = TRUE;
3693 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003694 int is_cursor_pos = (pos.col == cursor_pos.col
3695 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003696
3697 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3698 vim_memset(&cell, 0, sizeof(cell));
3699
3700 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3701 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003702 int c = cell.chars[i];
3703 int pc = prev_cell.chars[i];
3704
3705 /* For the first character NUL is the same as space. */
3706 if (i == 0)
3707 {
3708 c = (c == NUL) ? ' ' : c;
3709 pc = (pc == NUL) ? ' ' : pc;
3710 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003711 if (cell.chars[i] != prev_cell.chars[i])
3712 same_chars = FALSE;
3713 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3714 break;
3715 }
3716 same_attr = vtermAttr2hl(cell.attrs)
3717 == vtermAttr2hl(prev_cell.attrs)
3718 && same_color(&cell.fg, &prev_cell.fg)
3719 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003720 if (same_chars && cell.width == prev_cell.width && same_attr
3721 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003722 {
3723 ++repeat;
3724 }
3725 else
3726 {
3727 if (repeat > 0)
3728 {
3729 fprintf(fd, "@%d", repeat);
3730 repeat = 0;
3731 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003732 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003733
3734 if (cell.chars[0] == NUL)
3735 fputs(" ", fd);
3736 else
3737 {
3738 char_u charbuf[10];
3739 int len;
3740
3741 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3742 && cell.chars[i] != NUL; ++i)
3743 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003744 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003745 fwrite(charbuf, len, 1, fd);
3746 }
3747 }
3748
3749 /* When only the characters differ we don't write anything, the
3750 * following "|", "@" or NL will indicate using the same
3751 * attributes. */
3752 if (cell.width != prev_cell.width || !same_attr)
3753 {
3754 if (cell.width == 2)
3755 {
3756 fputs("*", fd);
3757 ++pos.col;
3758 }
3759 else
3760 fputs("+", fd);
3761
3762 if (same_attr)
3763 {
3764 fputs("&", fd);
3765 }
3766 else
3767 {
3768 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3769 if (same_color(&cell.fg, &prev_cell.fg))
3770 fputs("&", fd);
3771 else
3772 {
3773 fputs("#", fd);
3774 dump_term_color(fd, &cell.fg);
3775 }
3776 if (same_color(&cell.bg, &prev_cell.bg))
3777 fputs("&", fd);
3778 else
3779 {
3780 fputs("#", fd);
3781 dump_term_color(fd, &cell.bg);
3782 }
3783 }
3784 }
3785
3786 prev_cell = cell;
3787 }
3788 }
3789 if (repeat > 0)
3790 fprintf(fd, "@%d", repeat);
3791 fputs("\n", fd);
3792 }
3793
3794 fclose(fd);
3795}
3796
3797/*
3798 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3799 */
3800 static void
3801dump_is_corrupt(garray_T *gap)
3802{
3803 ga_concat(gap, (char_u *)"CORRUPT");
3804}
3805
3806 static void
3807append_cell(garray_T *gap, cellattr_T *cell)
3808{
3809 if (ga_grow(gap, 1) == OK)
3810 {
3811 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3812 ++gap->ga_len;
3813 }
3814}
3815
3816/*
3817 * Read the dump file from "fd" and append lines to the current buffer.
3818 * Return the cell width of the longest line.
3819 */
3820 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003821read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003822{
3823 int c;
3824 garray_T ga_text;
3825 garray_T ga_cell;
3826 char_u *prev_char = NULL;
3827 int attr = 0;
3828 cellattr_T cell;
3829 term_T *term = curbuf->b_term;
3830 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003831 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003832
3833 ga_init2(&ga_text, 1, 90);
3834 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3835 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003836 cursor_pos->row = -1;
3837 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003838
3839 c = fgetc(fd);
3840 for (;;)
3841 {
3842 if (c == EOF)
3843 break;
3844 if (c == '\n')
3845 {
3846 /* End of a line: append it to the buffer. */
3847 if (ga_text.ga_data == NULL)
3848 dump_is_corrupt(&ga_text);
3849 if (ga_grow(&term->tl_scrollback, 1) == OK)
3850 {
3851 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3852 + term->tl_scrollback.ga_len;
3853
3854 if (max_cells < ga_cell.ga_len)
3855 max_cells = ga_cell.ga_len;
3856 line->sb_cols = ga_cell.ga_len;
3857 line->sb_cells = ga_cell.ga_data;
3858 line->sb_fill_attr = term->tl_default_color;
3859 ++term->tl_scrollback.ga_len;
3860 ga_init(&ga_cell);
3861
3862 ga_append(&ga_text, NUL);
3863 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3864 ga_text.ga_len, FALSE);
3865 }
3866 else
3867 ga_clear(&ga_cell);
3868 ga_text.ga_len = 0;
3869
3870 c = fgetc(fd);
3871 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003872 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003873 {
3874 int prev_len = ga_text.ga_len;
3875
Bram Moolenaar9271d052018-02-25 21:39:46 +01003876 if (c == '>')
3877 {
3878 if (cursor_pos->row != -1)
3879 dump_is_corrupt(&ga_text); /* duplicate cursor */
3880 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3881 cursor_pos->col = ga_cell.ga_len;
3882 }
3883
Bram Moolenaard96ff162018-02-18 22:13:29 +01003884 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3885 c = fgetc(fd);
3886 if (c != EOF)
3887 ga_append(&ga_text, c);
3888 for (;;)
3889 {
3890 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003891 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003892 || c == EOF || c == '\n')
3893 break;
3894 ga_append(&ga_text, c);
3895 }
3896
3897 /* save the character for repeating it */
3898 vim_free(prev_char);
3899 if (ga_text.ga_data != NULL)
3900 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3901 ga_text.ga_len - prev_len);
3902
Bram Moolenaar9271d052018-02-25 21:39:46 +01003903 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003904 {
3905 /* use all attributes from previous cell */
3906 }
3907 else if (c == '+' || c == '*')
3908 {
3909 int is_bg;
3910
3911 cell.width = c == '+' ? 1 : 2;
3912
3913 c = fgetc(fd);
3914 if (c == '&')
3915 {
3916 /* use same attr as previous cell */
3917 c = fgetc(fd);
3918 }
3919 else if (isdigit(c))
3920 {
3921 /* get the decimal attribute */
3922 attr = 0;
3923 while (isdigit(c))
3924 {
3925 attr = attr * 10 + (c - '0');
3926 c = fgetc(fd);
3927 }
3928 hl2vtermAttr(attr, &cell);
3929 }
3930 else
3931 dump_is_corrupt(&ga_text);
3932
3933 /* is_bg == 0: fg, is_bg == 1: bg */
3934 for (is_bg = 0; is_bg <= 1; ++is_bg)
3935 {
3936 if (c == '&')
3937 {
3938 /* use same color as previous cell */
3939 c = fgetc(fd);
3940 }
3941 else if (c == '#')
3942 {
3943 int red, green, blue, index = 0;
3944
3945 c = fgetc(fd);
3946 red = hex2nr(c);
3947 c = fgetc(fd);
3948 red = (red << 4) + hex2nr(c);
3949 c = fgetc(fd);
3950 green = hex2nr(c);
3951 c = fgetc(fd);
3952 green = (green << 4) + hex2nr(c);
3953 c = fgetc(fd);
3954 blue = hex2nr(c);
3955 c = fgetc(fd);
3956 blue = (blue << 4) + hex2nr(c);
3957 c = fgetc(fd);
3958 if (!isdigit(c))
3959 dump_is_corrupt(&ga_text);
3960 while (isdigit(c))
3961 {
3962 index = index * 10 + (c - '0');
3963 c = fgetc(fd);
3964 }
3965
3966 if (is_bg)
3967 {
3968 cell.bg.red = red;
3969 cell.bg.green = green;
3970 cell.bg.blue = blue;
3971 cell.bg.ansi_index = index;
3972 }
3973 else
3974 {
3975 cell.fg.red = red;
3976 cell.fg.green = green;
3977 cell.fg.blue = blue;
3978 cell.fg.ansi_index = index;
3979 }
3980 }
3981 else
3982 dump_is_corrupt(&ga_text);
3983 }
3984 }
3985 else
3986 dump_is_corrupt(&ga_text);
3987
3988 append_cell(&ga_cell, &cell);
3989 }
3990 else if (c == '@')
3991 {
3992 if (prev_char == NULL)
3993 dump_is_corrupt(&ga_text);
3994 else
3995 {
3996 int count = 0;
3997
3998 /* repeat previous character, get the count */
3999 for (;;)
4000 {
4001 c = fgetc(fd);
4002 if (!isdigit(c))
4003 break;
4004 count = count * 10 + (c - '0');
4005 }
4006
4007 while (count-- > 0)
4008 {
4009 ga_concat(&ga_text, prev_char);
4010 append_cell(&ga_cell, &cell);
4011 }
4012 }
4013 }
4014 else
4015 {
4016 dump_is_corrupt(&ga_text);
4017 c = fgetc(fd);
4018 }
4019 }
4020
4021 if (ga_text.ga_len > 0)
4022 {
4023 /* trailing characters after last NL */
4024 dump_is_corrupt(&ga_text);
4025 ga_append(&ga_text, NUL);
4026 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4027 ga_text.ga_len, FALSE);
4028 }
4029
4030 ga_clear(&ga_text);
4031 vim_free(prev_char);
4032
4033 return max_cells;
4034}
4035
4036/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004037 * Return an allocated string with at least "text_width" "=" characters and
4038 * "fname" inserted in the middle.
4039 */
4040 static char_u *
4041get_separator(int text_width, char_u *fname)
4042{
4043 int width = MAX(text_width, curwin->w_width);
4044 char_u *textline;
4045 int fname_size;
4046 char_u *p = fname;
4047 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004048 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004049
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004050 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004051 if (textline == NULL)
4052 return NULL;
4053
4054 fname_size = vim_strsize(fname);
4055 if (fname_size < width - 8)
4056 {
4057 /* enough room, don't use the full window width */
4058 width = MAX(text_width, fname_size + 8);
4059 }
4060 else if (fname_size > width - 8)
4061 {
4062 /* full name doesn't fit, use only the tail */
4063 p = gettail(fname);
4064 fname_size = vim_strsize(p);
4065 }
4066 /* skip characters until the name fits */
4067 while (fname_size > width - 8)
4068 {
4069 p += (*mb_ptr2len)(p);
4070 fname_size = vim_strsize(p);
4071 }
4072
4073 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4074 textline[i] = '=';
4075 textline[i++] = ' ';
4076
4077 STRCPY(textline + i, p);
4078 off = STRLEN(textline);
4079 textline[off] = ' ';
4080 for (i = 1; i < (width - fname_size) / 2; ++i)
4081 textline[off + i] = '=';
4082 textline[off + i] = NUL;
4083
4084 return textline;
4085}
4086
4087/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004088 * Common for "term_dumpdiff()" and "term_dumpload()".
4089 */
4090 static void
4091term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4092{
4093 jobopt_T opt;
4094 buf_T *buf;
4095 char_u buf1[NUMBUFLEN];
4096 char_u buf2[NUMBUFLEN];
4097 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004098 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004099 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004100 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004101 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004102 char_u *textline = NULL;
4103
4104 /* First open the files. If this fails bail out. */
4105 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4106 if (do_diff)
4107 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4108 if (fname1 == NULL || (do_diff && fname2 == NULL))
4109 {
4110 EMSG(_(e_invarg));
4111 return;
4112 }
4113 fd1 = mch_fopen((char *)fname1, READBIN);
4114 if (fd1 == NULL)
4115 {
4116 EMSG2(_(e_notread), fname1);
4117 return;
4118 }
4119 if (do_diff)
4120 {
4121 fd2 = mch_fopen((char *)fname2, READBIN);
4122 if (fd2 == NULL)
4123 {
4124 fclose(fd1);
4125 EMSG2(_(e_notread), fname2);
4126 return;
4127 }
4128 }
4129
4130 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004131 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4132 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4133 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4134 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4135 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004136
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004137 if (opt.jo_term_name == NULL)
4138 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004139 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004140
Bram Moolenaarb571c632018-03-21 22:27:59 +01004141 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004142 if (fname_tofree != NULL)
4143 {
4144 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4145 opt.jo_term_name = fname_tofree;
4146 }
4147 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004148
Bram Moolenaar13568252018-03-16 20:46:58 +01004149 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004150 if (buf != NULL && buf->b_term != NULL)
4151 {
4152 int i;
4153 linenr_T bot_lnum;
4154 linenr_T lnum;
4155 term_T *term = buf->b_term;
4156 int width;
4157 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004158 VTermPos cursor_pos1;
4159 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004160
Bram Moolenaar52acb112018-03-18 19:20:22 +01004161 init_default_colors(term);
4162
Bram Moolenaard96ff162018-02-18 22:13:29 +01004163 rettv->vval.v_number = buf->b_fnum;
4164
4165 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004166 width = read_dump_file(fd1, &cursor_pos1);
4167
4168 /* position the cursor */
4169 if (cursor_pos1.row >= 0)
4170 {
4171 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4172 coladvance(cursor_pos1.col);
4173 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004174
4175 /* Delete the empty line that was in the empty buffer. */
4176 ml_delete(1, FALSE);
4177
4178 /* For term_dumpload() we are done here. */
4179 if (!do_diff)
4180 goto theend;
4181
4182 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4183
Bram Moolenaar4a696342018-04-05 18:45:26 +02004184 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004185 if (textline == NULL)
4186 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004187 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4188 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4189 vim_free(textline);
4190
4191 textline = get_separator(width, fname2);
4192 if (textline == NULL)
4193 goto theend;
4194 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4195 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004196 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004197
4198 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004199 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004200 if (width2 > width)
4201 {
4202 vim_free(textline);
4203 textline = alloc(width2 + 1);
4204 if (textline == NULL)
4205 goto theend;
4206 width = width2;
4207 textline[width] = NUL;
4208 }
4209 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4210
4211 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4212 {
4213 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4214 {
4215 /* bottom part has fewer rows, fill with "-" */
4216 for (i = 0; i < width; ++i)
4217 textline[i] = '-';
4218 }
4219 else
4220 {
4221 char_u *line1;
4222 char_u *line2;
4223 char_u *p1;
4224 char_u *p2;
4225 int col;
4226 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4227 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4228 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4229 ->sb_cells;
4230
4231 /* Make a copy, getting the second line will invalidate it. */
4232 line1 = vim_strsave(ml_get(lnum));
4233 if (line1 == NULL)
4234 break;
4235 p1 = line1;
4236
4237 line2 = ml_get(lnum + bot_lnum);
4238 p2 = line2;
4239 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4240 {
4241 int len1 = utfc_ptr2len(p1);
4242 int len2 = utfc_ptr2len(p2);
4243
4244 textline[col] = ' ';
4245 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004246 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004247 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004248 else if (lnum == cursor_pos1.row + 1
4249 && col == cursor_pos1.col
4250 && (cursor_pos1.row != cursor_pos2.row
4251 || cursor_pos1.col != cursor_pos2.col))
4252 /* cursor in first but not in second */
4253 textline[col] = '>';
4254 else if (lnum == cursor_pos2.row + 1
4255 && col == cursor_pos2.col
4256 && (cursor_pos1.row != cursor_pos2.row
4257 || cursor_pos1.col != cursor_pos2.col))
4258 /* cursor in second but not in first */
4259 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004260 else if (cellattr1 != NULL && cellattr2 != NULL)
4261 {
4262 if ((cellattr1 + col)->width
4263 != (cellattr2 + col)->width)
4264 textline[col] = 'w';
4265 else if (!same_color(&(cellattr1 + col)->fg,
4266 &(cellattr2 + col)->fg))
4267 textline[col] = 'f';
4268 else if (!same_color(&(cellattr1 + col)->bg,
4269 &(cellattr2 + col)->bg))
4270 textline[col] = 'b';
4271 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4272 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4273 textline[col] = 'a';
4274 }
4275 p1 += len1;
4276 p2 += len2;
4277 /* TODO: handle different width */
4278 }
4279 vim_free(line1);
4280
4281 while (col < width)
4282 {
4283 if (*p1 == NUL && *p2 == NUL)
4284 textline[col] = '?';
4285 else if (*p1 == NUL)
4286 {
4287 textline[col] = '+';
4288 p2 += utfc_ptr2len(p2);
4289 }
4290 else
4291 {
4292 textline[col] = '-';
4293 p1 += utfc_ptr2len(p1);
4294 }
4295 ++col;
4296 }
4297 }
4298 if (add_empty_scrollback(term, &term->tl_default_color,
4299 term->tl_top_diff_rows) == OK)
4300 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4301 ++bot_lnum;
4302 }
4303
4304 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4305 {
4306 /* bottom part has more rows, fill with "+" */
4307 for (i = 0; i < width; ++i)
4308 textline[i] = '+';
4309 if (add_empty_scrollback(term, &term->tl_default_color,
4310 term->tl_top_diff_rows) == OK)
4311 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4312 ++lnum;
4313 ++bot_lnum;
4314 }
4315
4316 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004317
4318 /* looks better without wrapping */
4319 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004320 }
4321
4322theend:
4323 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004324 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004325 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004326 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004327 fclose(fd2);
4328}
4329
4330/*
4331 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4332 * bottom files.
4333 * Return FAIL when this is not possible.
4334 */
4335 int
4336term_swap_diff()
4337{
4338 term_T *term = curbuf->b_term;
4339 linenr_T line_count;
4340 linenr_T top_rows;
4341 linenr_T bot_rows;
4342 linenr_T bot_start;
4343 linenr_T lnum;
4344 char_u *p;
4345 sb_line_T *sb_line;
4346
4347 if (term == NULL
4348 || !term_is_finished(curbuf)
4349 || term->tl_top_diff_rows == 0
4350 || term->tl_scrollback.ga_len == 0)
4351 return FAIL;
4352
4353 line_count = curbuf->b_ml.ml_line_count;
4354 top_rows = term->tl_top_diff_rows;
4355 bot_rows = term->tl_bot_diff_rows;
4356 bot_start = line_count - bot_rows;
4357 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4358
4359 /* move lines from top to above the bottom part */
4360 for (lnum = 1; lnum <= top_rows; ++lnum)
4361 {
4362 p = vim_strsave(ml_get(1));
4363 if (p == NULL)
4364 return OK;
4365 ml_append(bot_start, p, 0, FALSE);
4366 ml_delete(1, FALSE);
4367 vim_free(p);
4368 }
4369
4370 /* move lines from bottom to the top */
4371 for (lnum = 1; lnum <= bot_rows; ++lnum)
4372 {
4373 p = vim_strsave(ml_get(bot_start + lnum));
4374 if (p == NULL)
4375 return OK;
4376 ml_delete(bot_start + lnum, FALSE);
4377 ml_append(lnum - 1, p, 0, FALSE);
4378 vim_free(p);
4379 }
4380
4381 if (top_rows == bot_rows)
4382 {
4383 /* rows counts are equal, can swap cell properties */
4384 for (lnum = 0; lnum < top_rows; ++lnum)
4385 {
4386 sb_line_T temp;
4387
4388 temp = *(sb_line + lnum);
4389 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4390 *(sb_line + bot_start + lnum) = temp;
4391 }
4392 }
4393 else
4394 {
4395 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4396 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4397
4398 /* need to copy cell properties into temp memory */
4399 if (temp != NULL)
4400 {
4401 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4402 mch_memmove(term->tl_scrollback.ga_data,
4403 temp + bot_start,
4404 sizeof(sb_line_T) * bot_rows);
4405 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4406 temp + top_rows,
4407 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4408 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4409 + line_count - top_rows,
4410 temp,
4411 sizeof(sb_line_T) * top_rows);
4412 vim_free(temp);
4413 }
4414 }
4415
4416 term->tl_top_diff_rows = bot_rows;
4417 term->tl_bot_diff_rows = top_rows;
4418
4419 update_screen(NOT_VALID);
4420 return OK;
4421}
4422
4423/*
4424 * "term_dumpdiff(filename, filename, options)" function
4425 */
4426 void
4427f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4428{
4429 term_load_dump(argvars, rettv, TRUE);
4430}
4431
4432/*
4433 * "term_dumpload(filename, options)" function
4434 */
4435 void
4436f_term_dumpload(typval_T *argvars, typval_T *rettv)
4437{
4438 term_load_dump(argvars, rettv, FALSE);
4439}
4440
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004441/*
4442 * "term_getaltscreen(buf)" function
4443 */
4444 void
4445f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4446{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004447 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004448
4449 if (buf == NULL)
4450 return;
4451 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4452}
4453
4454/*
4455 * "term_getattr(attr, name)" function
4456 */
4457 void
4458f_term_getattr(typval_T *argvars, typval_T *rettv)
4459{
4460 int attr;
4461 size_t i;
4462 char_u *name;
4463
4464 static struct {
4465 char *name;
4466 int attr;
4467 } attrs[] = {
4468 {"bold", HL_BOLD},
4469 {"italic", HL_ITALIC},
4470 {"underline", HL_UNDERLINE},
4471 {"strike", HL_STRIKETHROUGH},
4472 {"reverse", HL_INVERSE},
4473 };
4474
4475 attr = get_tv_number(&argvars[0]);
4476 name = get_tv_string_chk(&argvars[1]);
4477 if (name == NULL)
4478 return;
4479
4480 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4481 if (STRCMP(name, attrs[i].name) == 0)
4482 {
4483 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4484 break;
4485 }
4486}
4487
4488/*
4489 * "term_getcursor(buf)" function
4490 */
4491 void
4492f_term_getcursor(typval_T *argvars, typval_T *rettv)
4493{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004494 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004495 term_T *term;
4496 list_T *l;
4497 dict_T *d;
4498
4499 if (rettv_list_alloc(rettv) == FAIL)
4500 return;
4501 if (buf == NULL)
4502 return;
4503 term = buf->b_term;
4504
4505 l = rettv->vval.v_list;
4506 list_append_number(l, term->tl_cursor_pos.row + 1);
4507 list_append_number(l, term->tl_cursor_pos.col + 1);
4508
4509 d = dict_alloc();
4510 if (d != NULL)
4511 {
4512 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4513 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4514 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4515 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4516 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4517 ? (char_u *)"" : term->tl_cursor_color);
4518 list_append_dict(l, d);
4519 }
4520}
4521
4522/*
4523 * "term_getjob(buf)" function
4524 */
4525 void
4526f_term_getjob(typval_T *argvars, typval_T *rettv)
4527{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004528 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004529
4530 rettv->v_type = VAR_JOB;
4531 rettv->vval.v_job = NULL;
4532 if (buf == NULL)
4533 return;
4534
4535 rettv->vval.v_job = buf->b_term->tl_job;
4536 if (rettv->vval.v_job != NULL)
4537 ++rettv->vval.v_job->jv_refcount;
4538}
4539
4540 static int
4541get_row_number(typval_T *tv, term_T *term)
4542{
4543 if (tv->v_type == VAR_STRING
4544 && tv->vval.v_string != NULL
4545 && STRCMP(tv->vval.v_string, ".") == 0)
4546 return term->tl_cursor_pos.row;
4547 return (int)get_tv_number(tv) - 1;
4548}
4549
4550/*
4551 * "term_getline(buf, row)" function
4552 */
4553 void
4554f_term_getline(typval_T *argvars, typval_T *rettv)
4555{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004556 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004557 term_T *term;
4558 int row;
4559
4560 rettv->v_type = VAR_STRING;
4561 if (buf == NULL)
4562 return;
4563 term = buf->b_term;
4564 row = get_row_number(&argvars[1], term);
4565
4566 if (term->tl_vterm == NULL)
4567 {
4568 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4569
4570 /* vterm is finished, get the text from the buffer */
4571 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4572 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4573 }
4574 else
4575 {
4576 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4577 VTermRect rect;
4578 int len;
4579 char_u *p;
4580
4581 if (row < 0 || row >= term->tl_rows)
4582 return;
4583 len = term->tl_cols * MB_MAXBYTES + 1;
4584 p = alloc(len);
4585 if (p == NULL)
4586 return;
4587 rettv->vval.v_string = p;
4588
4589 rect.start_col = 0;
4590 rect.end_col = term->tl_cols;
4591 rect.start_row = row;
4592 rect.end_row = row + 1;
4593 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4594 }
4595}
4596
4597/*
4598 * "term_getscrolled(buf)" function
4599 */
4600 void
4601f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4602{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004603 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004604
4605 if (buf == NULL)
4606 return;
4607 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4608}
4609
4610/*
4611 * "term_getsize(buf)" function
4612 */
4613 void
4614f_term_getsize(typval_T *argvars, typval_T *rettv)
4615{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004616 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004617 list_T *l;
4618
4619 if (rettv_list_alloc(rettv) == FAIL)
4620 return;
4621 if (buf == NULL)
4622 return;
4623
4624 l = rettv->vval.v_list;
4625 list_append_number(l, buf->b_term->tl_rows);
4626 list_append_number(l, buf->b_term->tl_cols);
4627}
4628
4629/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004630 * "term_setsize(buf, rows, cols)" function
4631 */
4632 void
4633f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4634{
4635 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4636 term_T *term;
4637 varnumber_T rows, cols;
4638
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004639 if (buf == NULL)
4640 {
4641 EMSG(_("E955: Not a terminal buffer"));
4642 return;
4643 }
4644 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004645 return;
4646 term = buf->b_term;
4647 rows = get_tv_number(&argvars[1]);
4648 rows = rows <= 0 ? term->tl_rows : rows;
4649 cols = get_tv_number(&argvars[2]);
4650 cols = cols <= 0 ? term->tl_cols : cols;
4651 vterm_set_size(term->tl_vterm, rows, cols);
4652 /* handle_resize() will resize the windows */
4653
4654 /* Get and remember the size we ended up with. Update the pty. */
4655 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4656 term_report_winsize(term, term->tl_rows, term->tl_cols);
4657}
4658
4659/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004660 * "term_getstatus(buf)" function
4661 */
4662 void
4663f_term_getstatus(typval_T *argvars, typval_T *rettv)
4664{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004665 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004666 term_T *term;
4667 char_u val[100];
4668
4669 rettv->v_type = VAR_STRING;
4670 if (buf == NULL)
4671 return;
4672 term = buf->b_term;
4673
4674 if (term_job_running(term))
4675 STRCPY(val, "running");
4676 else
4677 STRCPY(val, "finished");
4678 if (term->tl_normal_mode)
4679 STRCAT(val, ",normal");
4680 rettv->vval.v_string = vim_strsave(val);
4681}
4682
4683/*
4684 * "term_gettitle(buf)" function
4685 */
4686 void
4687f_term_gettitle(typval_T *argvars, typval_T *rettv)
4688{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004689 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004690
4691 rettv->v_type = VAR_STRING;
4692 if (buf == NULL)
4693 return;
4694
4695 if (buf->b_term->tl_title != NULL)
4696 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4697}
4698
4699/*
4700 * "term_gettty(buf)" function
4701 */
4702 void
4703f_term_gettty(typval_T *argvars, typval_T *rettv)
4704{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004705 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004706 char_u *p;
4707 int num = 0;
4708
4709 rettv->v_type = VAR_STRING;
4710 if (buf == NULL)
4711 return;
4712 if (argvars[1].v_type != VAR_UNKNOWN)
4713 num = get_tv_number(&argvars[1]);
4714
4715 switch (num)
4716 {
4717 case 0:
4718 if (buf->b_term->tl_job != NULL)
4719 p = buf->b_term->tl_job->jv_tty_out;
4720 else
4721 p = buf->b_term->tl_tty_out;
4722 break;
4723 case 1:
4724 if (buf->b_term->tl_job != NULL)
4725 p = buf->b_term->tl_job->jv_tty_in;
4726 else
4727 p = buf->b_term->tl_tty_in;
4728 break;
4729 default:
4730 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4731 return;
4732 }
4733 if (p != NULL)
4734 rettv->vval.v_string = vim_strsave(p);
4735}
4736
4737/*
4738 * "term_list()" function
4739 */
4740 void
4741f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4742{
4743 term_T *tp;
4744 list_T *l;
4745
4746 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4747 return;
4748
4749 l = rettv->vval.v_list;
4750 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4751 if (tp != NULL && tp->tl_buffer != NULL)
4752 if (list_append_number(l,
4753 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4754 return;
4755}
4756
4757/*
4758 * "term_scrape(buf, row)" function
4759 */
4760 void
4761f_term_scrape(typval_T *argvars, typval_T *rettv)
4762{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004763 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004764 VTermScreen *screen = NULL;
4765 VTermPos pos;
4766 list_T *l;
4767 term_T *term;
4768 char_u *p;
4769 sb_line_T *line;
4770
4771 if (rettv_list_alloc(rettv) == FAIL)
4772 return;
4773 if (buf == NULL)
4774 return;
4775 term = buf->b_term;
4776
4777 l = rettv->vval.v_list;
4778 pos.row = get_row_number(&argvars[1], term);
4779
4780 if (term->tl_vterm != NULL)
4781 {
4782 screen = vterm_obtain_screen(term->tl_vterm);
4783 p = NULL;
4784 line = NULL;
4785 }
4786 else
4787 {
4788 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4789
4790 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4791 return;
4792 p = ml_get_buf(buf, lnum + 1, FALSE);
4793 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4794 }
4795
4796 for (pos.col = 0; pos.col < term->tl_cols; )
4797 {
4798 dict_T *dcell;
4799 int width;
4800 VTermScreenCellAttrs attrs;
4801 VTermColor fg, bg;
4802 char_u rgb[8];
4803 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4804 int off = 0;
4805 int i;
4806
4807 if (screen == NULL)
4808 {
4809 cellattr_T *cellattr;
4810 int len;
4811
4812 /* vterm has finished, get the cell from scrollback */
4813 if (pos.col >= line->sb_cols)
4814 break;
4815 cellattr = line->sb_cells + pos.col;
4816 width = cellattr->width;
4817 attrs = cellattr->attrs;
4818 fg = cellattr->fg;
4819 bg = cellattr->bg;
4820 len = MB_PTR2LEN(p);
4821 mch_memmove(mbs, p, len);
4822 mbs[len] = NUL;
4823 p += len;
4824 }
4825 else
4826 {
4827 VTermScreenCell cell;
4828 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4829 break;
4830 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4831 {
4832 if (cell.chars[i] == 0)
4833 break;
4834 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4835 }
4836 mbs[off] = NUL;
4837 width = cell.width;
4838 attrs = cell.attrs;
4839 fg = cell.fg;
4840 bg = cell.bg;
4841 }
4842 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004843 if (dcell == NULL)
4844 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004845 list_append_dict(l, dcell);
4846
4847 dict_add_nr_str(dcell, "chars", 0, mbs);
4848
4849 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4850 fg.red, fg.green, fg.blue);
4851 dict_add_nr_str(dcell, "fg", 0, rgb);
4852 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4853 bg.red, bg.green, bg.blue);
4854 dict_add_nr_str(dcell, "bg", 0, rgb);
4855
4856 dict_add_nr_str(dcell, "attr",
4857 cell2attr(attrs, fg, bg), NULL);
4858 dict_add_nr_str(dcell, "width", width, NULL);
4859
4860 ++pos.col;
4861 if (width == 2)
4862 ++pos.col;
4863 }
4864}
4865
4866/*
4867 * "term_sendkeys(buf, keys)" function
4868 */
4869 void
4870f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4871{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004872 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004873 char_u *msg;
4874 term_T *term;
4875
4876 rettv->v_type = VAR_UNKNOWN;
4877 if (buf == NULL)
4878 return;
4879
4880 msg = get_tv_string_chk(&argvars[1]);
4881 if (msg == NULL)
4882 return;
4883 term = buf->b_term;
4884 if (term->tl_vterm == NULL)
4885 return;
4886
4887 while (*msg != NUL)
4888 {
4889 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004890 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004891 }
4892}
4893
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004894#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
4895/*
4896 * "term_getansicolors(buf)" function
4897 */
4898 void
4899f_term_getansicolors(typval_T *argvars, typval_T *rettv)
4900{
4901 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
4902 term_T *term;
4903 VTermState *state;
4904 VTermColor color;
4905 char_u hexbuf[10];
4906 int index;
4907 list_T *list;
4908
4909 if (rettv_list_alloc(rettv) == FAIL)
4910 return;
4911
4912 if (buf == NULL)
4913 return;
4914 term = buf->b_term;
4915 if (term->tl_vterm == NULL)
4916 return;
4917
4918 list = rettv->vval.v_list;
4919 state = vterm_obtain_state(term->tl_vterm);
4920 for (index = 0; index < 16; index++)
4921 {
4922 vterm_state_get_palette_color(state, index, &color);
4923 sprintf((char *)hexbuf, "#%02x%02x%02x",
4924 color.red, color.green, color.blue);
4925 if (list_append_string(list, hexbuf, 7) == FAIL)
4926 return;
4927 }
4928}
4929
4930/*
4931 * "term_setansicolors(buf, list)" function
4932 */
4933 void
4934f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
4935{
4936 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
4937 term_T *term;
4938
4939 if (buf == NULL)
4940 return;
4941 term = buf->b_term;
4942 if (term->tl_vterm == NULL)
4943 return;
4944
4945 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
4946 {
4947 EMSG(_(e_listreq));
4948 return;
4949 }
4950
4951 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
4952 EMSG(_(e_invarg));
4953}
4954#endif
4955
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004956/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004957 * "term_setrestore(buf, command)" function
4958 */
4959 void
4960f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4961{
4962#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004963 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004964 term_T *term;
4965 char_u *cmd;
4966
4967 if (buf == NULL)
4968 return;
4969 term = buf->b_term;
4970 vim_free(term->tl_command);
4971 cmd = get_tv_string_chk(&argvars[1]);
4972 if (cmd != NULL)
4973 term->tl_command = vim_strsave(cmd);
4974 else
4975 term->tl_command = NULL;
4976#endif
4977}
4978
4979/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004980 * "term_setkill(buf, how)" function
4981 */
4982 void
4983f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4984{
4985 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4986 term_T *term;
4987 char_u *how;
4988
4989 if (buf == NULL)
4990 return;
4991 term = buf->b_term;
4992 vim_free(term->tl_kill);
4993 how = get_tv_string_chk(&argvars[1]);
4994 if (how != NULL)
4995 term->tl_kill = vim_strsave(how);
4996 else
4997 term->tl_kill = NULL;
4998}
4999
5000/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005001 * "term_start(command, options)" function
5002 */
5003 void
5004f_term_start(typval_T *argvars, typval_T *rettv)
5005{
5006 jobopt_T opt;
5007 buf_T *buf;
5008
5009 init_job_options(&opt);
5010 if (argvars[1].v_type != VAR_UNKNOWN
5011 && get_job_options(&argvars[1], &opt,
5012 JO_TIMEOUT_ALL + JO_STOPONEXIT
5013 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5014 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5015 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5016 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005017 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005018 + JO2_NORESTORE + JO2_TERM_KILL
5019 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005020 return;
5021
Bram Moolenaar13568252018-03-16 20:46:58 +01005022 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005023
5024 if (buf != NULL && buf->b_term != NULL)
5025 rettv->vval.v_number = buf->b_fnum;
5026}
5027
5028/*
5029 * "term_wait" function
5030 */
5031 void
5032f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5033{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005034 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005035
5036 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005037 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005038 if (buf->b_term->tl_job == NULL)
5039 {
5040 ch_log(NULL, "term_wait(): no job to wait for");
5041 return;
5042 }
5043 if (buf->b_term->tl_job->jv_channel == NULL)
5044 /* channel is closed, nothing to do */
5045 return;
5046
5047 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005048 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005049 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5050 {
5051 /* The job is dead, keep reading channel I/O until the channel is
5052 * closed. buf->b_term may become NULL if the terminal was closed while
5053 * waiting. */
5054 ch_log(NULL, "term_wait(): waiting for channel to close");
5055 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5056 {
5057 mch_check_messages();
5058 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005059 if (!buf_valid(buf))
5060 /* If the terminal is closed when the channel is closed the
5061 * buffer disappears. */
5062 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005063 ui_delay(10L, FALSE);
5064 }
5065 mch_check_messages();
5066 parse_queued_messages();
5067 }
5068 else
5069 {
5070 long wait = 10L;
5071
5072 mch_check_messages();
5073 parse_queued_messages();
5074
5075 /* Wait for some time for any channel I/O. */
5076 if (argvars[1].v_type != VAR_UNKNOWN)
5077 wait = get_tv_number(&argvars[1]);
5078 ui_delay(wait, TRUE);
5079 mch_check_messages();
5080
5081 /* Flushing messages on channels is hopefully sufficient.
5082 * TODO: is there a better way? */
5083 parse_queued_messages();
5084 }
5085}
5086
5087/*
5088 * Called when a channel has sent all the lines to a terminal.
5089 * Send a CTRL-D to mark the end of the text.
5090 */
5091 void
5092term_send_eof(channel_T *ch)
5093{
5094 term_T *term;
5095
5096 for (term = first_term; term != NULL; term = term->tl_next)
5097 if (term->tl_job == ch->ch_job)
5098 {
5099 if (term->tl_eof_chars != NULL)
5100 {
5101 channel_send(ch, PART_IN, term->tl_eof_chars,
5102 (int)STRLEN(term->tl_eof_chars), NULL);
5103 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5104 }
5105# ifdef WIN3264
5106 else
5107 /* Default: CTRL-D */
5108 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5109# endif
5110 }
5111}
5112
5113# if defined(WIN3264) || defined(PROTO)
5114
5115/**************************************
5116 * 2. MS-Windows implementation.
5117 */
5118
5119# ifndef PROTO
5120
5121#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5122#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005123#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005124
5125void* (*winpty_config_new)(UINT64, void*);
5126void* (*winpty_open)(void*, void*);
5127void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5128BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5129void (*winpty_config_set_mouse_mode)(void*, int);
5130void (*winpty_config_set_initial_size)(void*, int, int);
5131LPCWSTR (*winpty_conin_name)(void*);
5132LPCWSTR (*winpty_conout_name)(void*);
5133LPCWSTR (*winpty_conerr_name)(void*);
5134void (*winpty_free)(void*);
5135void (*winpty_config_free)(void*);
5136void (*winpty_spawn_config_free)(void*);
5137void (*winpty_error_free)(void*);
5138LPCWSTR (*winpty_error_msg)(void*);
5139BOOL (*winpty_set_size)(void*, int, int, void*);
5140HANDLE (*winpty_agent_process)(void*);
5141
5142#define WINPTY_DLL "winpty.dll"
5143
5144static HINSTANCE hWinPtyDLL = NULL;
5145# endif
5146
5147 static int
5148dyn_winpty_init(int verbose)
5149{
5150 int i;
5151 static struct
5152 {
5153 char *name;
5154 FARPROC *ptr;
5155 } winpty_entry[] =
5156 {
5157 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5158 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5159 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5160 {"winpty_config_set_mouse_mode",
5161 (FARPROC*)&winpty_config_set_mouse_mode},
5162 {"winpty_config_set_initial_size",
5163 (FARPROC*)&winpty_config_set_initial_size},
5164 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5165 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5166 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5167 {"winpty_free", (FARPROC*)&winpty_free},
5168 {"winpty_open", (FARPROC*)&winpty_open},
5169 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5170 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5171 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5172 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5173 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5174 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5175 {NULL, NULL}
5176 };
5177
5178 /* No need to initialize twice. */
5179 if (hWinPtyDLL)
5180 return OK;
5181 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5182 * winpty.dll. */
5183 if (*p_winptydll != NUL)
5184 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5185 if (!hWinPtyDLL)
5186 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5187 if (!hWinPtyDLL)
5188 {
5189 if (verbose)
5190 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5191 : (char_u *)WINPTY_DLL);
5192 return FAIL;
5193 }
5194 for (i = 0; winpty_entry[i].name != NULL
5195 && winpty_entry[i].ptr != NULL; ++i)
5196 {
5197 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5198 winpty_entry[i].name)) == NULL)
5199 {
5200 if (verbose)
5201 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5202 return FAIL;
5203 }
5204 }
5205
5206 return OK;
5207}
5208
5209/*
5210 * Create a new terminal of "rows" by "cols" cells.
5211 * Store a reference in "term".
5212 * Return OK or FAIL.
5213 */
5214 static int
5215term_and_job_init(
5216 term_T *term,
5217 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005218 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005219 jobopt_T *opt)
5220{
5221 WCHAR *cmd_wchar = NULL;
5222 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005223 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005224 channel_T *channel = NULL;
5225 job_T *job = NULL;
5226 DWORD error;
5227 HANDLE jo = NULL;
5228 HANDLE child_process_handle;
5229 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005230 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005231 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005232 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005233 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005234
5235 if (dyn_winpty_init(TRUE) == FAIL)
5236 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005237 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5238 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005239
5240 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005241 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005242 cmd = argvar->vval.v_string;
5243 }
5244 else if (argvar->v_type == VAR_LIST)
5245 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005246 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005247 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005248 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005249 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005250 if (cmd == NULL || *cmd == NUL)
5251 {
5252 EMSG(_(e_invarg));
5253 goto failed;
5254 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005255
5256 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005257 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005258 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005259 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005260 if (opt->jo_cwd != NULL)
5261 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005262
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005263 win32_build_env(opt->jo_env, &ga_env, TRUE);
5264 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005265
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005266 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5267 if (term->tl_winpty_config == NULL)
5268 goto failed;
5269
5270 winpty_config_set_mouse_mode(term->tl_winpty_config,
5271 WINPTY_MOUSE_MODE_FORCE);
5272 winpty_config_set_initial_size(term->tl_winpty_config,
5273 term->tl_cols, term->tl_rows);
5274 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5275 if (term->tl_winpty == NULL)
5276 goto failed;
5277
5278 spawn_config = winpty_spawn_config_new(
5279 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5280 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5281 NULL,
5282 cmd_wchar,
5283 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005284 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005285 &winpty_err);
5286 if (spawn_config == NULL)
5287 goto failed;
5288
5289 channel = add_channel();
5290 if (channel == NULL)
5291 goto failed;
5292
5293 job = job_alloc();
5294 if (job == NULL)
5295 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005296 if (argvar->v_type == VAR_STRING)
5297 {
5298 int argc;
5299
5300 build_argv_from_string(cmd, &job->jv_argv, &argc);
5301 }
5302 else
5303 {
5304 int argc;
5305
5306 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5307 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005308
5309 if (opt->jo_set & JO_IN_BUF)
5310 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5311
5312 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5313 &child_thread_handle, &error, &winpty_err))
5314 goto failed;
5315
5316 channel_set_pipes(channel,
5317 (sock_T)CreateFileW(
5318 winpty_conin_name(term->tl_winpty),
5319 GENERIC_WRITE, 0, NULL,
5320 OPEN_EXISTING, 0, NULL),
5321 (sock_T)CreateFileW(
5322 winpty_conout_name(term->tl_winpty),
5323 GENERIC_READ, 0, NULL,
5324 OPEN_EXISTING, 0, NULL),
5325 (sock_T)CreateFileW(
5326 winpty_conerr_name(term->tl_winpty),
5327 GENERIC_READ, 0, NULL,
5328 OPEN_EXISTING, 0, NULL));
5329
5330 /* Write lines with CR instead of NL. */
5331 channel->ch_write_text_mode = TRUE;
5332
5333 jo = CreateJobObject(NULL, NULL);
5334 if (jo == NULL)
5335 goto failed;
5336
5337 if (!AssignProcessToJobObject(jo, child_process_handle))
5338 {
5339 /* Failed, switch the way to terminate process with TerminateProcess. */
5340 CloseHandle(jo);
5341 jo = NULL;
5342 }
5343
5344 winpty_spawn_config_free(spawn_config);
5345 vim_free(cmd_wchar);
5346 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005347 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005348
5349 create_vterm(term, term->tl_rows, term->tl_cols);
5350
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005351#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5352 if (opt->jo_set2 & JO2_ANSI_COLORS)
5353 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5354 else
5355 init_vterm_ansi_colors(term->tl_vterm);
5356#endif
5357
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005358 channel_set_job(channel, job, opt);
5359 job_set_options(job, opt);
5360
5361 job->jv_channel = channel;
5362 job->jv_proc_info.hProcess = child_process_handle;
5363 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5364 job->jv_job_object = jo;
5365 job->jv_status = JOB_STARTED;
5366 job->jv_tty_in = utf16_to_enc(
5367 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5368 job->jv_tty_out = utf16_to_enc(
5369 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5370 ++job->jv_refcount;
5371 term->tl_job = job;
5372
5373 return OK;
5374
5375failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005376 ga_clear(&ga_cmd);
5377 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005378 vim_free(cmd_wchar);
5379 vim_free(cwd_wchar);
5380 if (spawn_config != NULL)
5381 winpty_spawn_config_free(spawn_config);
5382 if (channel != NULL)
5383 channel_clear(channel);
5384 if (job != NULL)
5385 {
5386 job->jv_channel = NULL;
5387 job_cleanup(job);
5388 }
5389 term->tl_job = NULL;
5390 if (jo != NULL)
5391 CloseHandle(jo);
5392 if (term->tl_winpty != NULL)
5393 winpty_free(term->tl_winpty);
5394 term->tl_winpty = NULL;
5395 if (term->tl_winpty_config != NULL)
5396 winpty_config_free(term->tl_winpty_config);
5397 term->tl_winpty_config = NULL;
5398 if (winpty_err != NULL)
5399 {
5400 char_u *msg = utf16_to_enc(
5401 (short_u *)winpty_error_msg(winpty_err), NULL);
5402
5403 EMSG(msg);
5404 winpty_error_free(winpty_err);
5405 }
5406 return FAIL;
5407}
5408
5409 static int
5410create_pty_only(term_T *term, jobopt_T *options)
5411{
5412 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5413 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5414 char in_name[80], out_name[80];
5415 channel_T *channel = NULL;
5416
5417 create_vterm(term, term->tl_rows, term->tl_cols);
5418
5419 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5420 GetCurrentProcessId(),
5421 curbuf->b_fnum);
5422 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5423 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5424 PIPE_UNLIMITED_INSTANCES,
5425 0, 0, NMPWAIT_NOWAIT, NULL);
5426 if (hPipeIn == INVALID_HANDLE_VALUE)
5427 goto failed;
5428
5429 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5430 GetCurrentProcessId(),
5431 curbuf->b_fnum);
5432 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5433 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5434 PIPE_UNLIMITED_INSTANCES,
5435 0, 0, 0, NULL);
5436 if (hPipeOut == INVALID_HANDLE_VALUE)
5437 goto failed;
5438
5439 ConnectNamedPipe(hPipeIn, NULL);
5440 ConnectNamedPipe(hPipeOut, NULL);
5441
5442 term->tl_job = job_alloc();
5443 if (term->tl_job == NULL)
5444 goto failed;
5445 ++term->tl_job->jv_refcount;
5446
5447 /* behave like the job is already finished */
5448 term->tl_job->jv_status = JOB_FINISHED;
5449
5450 channel = add_channel();
5451 if (channel == NULL)
5452 goto failed;
5453 term->tl_job->jv_channel = channel;
5454 channel->ch_keep_open = TRUE;
5455 channel->ch_named_pipe = TRUE;
5456
5457 channel_set_pipes(channel,
5458 (sock_T)hPipeIn,
5459 (sock_T)hPipeOut,
5460 (sock_T)hPipeOut);
5461 channel_set_job(channel, term->tl_job, options);
5462 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5463 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5464
5465 return OK;
5466
5467failed:
5468 if (hPipeIn != NULL)
5469 CloseHandle(hPipeIn);
5470 if (hPipeOut != NULL)
5471 CloseHandle(hPipeOut);
5472 return FAIL;
5473}
5474
5475/*
5476 * Free the terminal emulator part of "term".
5477 */
5478 static void
5479term_free_vterm(term_T *term)
5480{
5481 if (term->tl_winpty != NULL)
5482 winpty_free(term->tl_winpty);
5483 term->tl_winpty = NULL;
5484 if (term->tl_winpty_config != NULL)
5485 winpty_config_free(term->tl_winpty_config);
5486 term->tl_winpty_config = NULL;
5487 if (term->tl_vterm != NULL)
5488 vterm_free(term->tl_vterm);
5489 term->tl_vterm = NULL;
5490}
5491
5492/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005493 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005494 */
5495 static void
5496term_report_winsize(term_T *term, int rows, int cols)
5497{
5498 if (term->tl_winpty)
5499 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5500}
5501
5502 int
5503terminal_enabled(void)
5504{
5505 return dyn_winpty_init(FALSE) == OK;
5506}
5507
5508# else
5509
5510/**************************************
5511 * 3. Unix-like implementation.
5512 */
5513
5514/*
5515 * Create a new terminal of "rows" by "cols" cells.
5516 * Start job for "cmd".
5517 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005518 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005519 * Return OK or FAIL.
5520 */
5521 static int
5522term_and_job_init(
5523 term_T *term,
5524 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005525 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005526 jobopt_T *opt)
5527{
5528 create_vterm(term, term->tl_rows, term->tl_cols);
5529
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005530#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5531 if (opt->jo_set2 & JO2_ANSI_COLORS)
5532 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5533 else
5534 init_vterm_ansi_colors(term->tl_vterm);
5535#endif
5536
Bram Moolenaar13568252018-03-16 20:46:58 +01005537 /* This may change a string in "argvar". */
5538 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005539 if (term->tl_job != NULL)
5540 ++term->tl_job->jv_refcount;
5541
5542 return term->tl_job != NULL
5543 && term->tl_job->jv_channel != NULL
5544 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5545}
5546
5547 static int
5548create_pty_only(term_T *term, jobopt_T *opt)
5549{
5550 create_vterm(term, term->tl_rows, term->tl_cols);
5551
5552 term->tl_job = job_alloc();
5553 if (term->tl_job == NULL)
5554 return FAIL;
5555 ++term->tl_job->jv_refcount;
5556
5557 /* behave like the job is already finished */
5558 term->tl_job->jv_status = JOB_FINISHED;
5559
5560 return mch_create_pty_channel(term->tl_job, opt);
5561}
5562
5563/*
5564 * Free the terminal emulator part of "term".
5565 */
5566 static void
5567term_free_vterm(term_T *term)
5568{
5569 if (term->tl_vterm != NULL)
5570 vterm_free(term->tl_vterm);
5571 term->tl_vterm = NULL;
5572}
5573
5574/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005575 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005576 */
5577 static void
5578term_report_winsize(term_T *term, int rows, int cols)
5579{
5580 /* Use an ioctl() to report the new window size to the job. */
5581 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5582 {
5583 int fd = -1;
5584 int part;
5585
5586 for (part = PART_OUT; part < PART_COUNT; ++part)
5587 {
5588 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5589 if (isatty(fd))
5590 break;
5591 }
5592 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5593 mch_signal_job(term->tl_job, (char_u *)"winch");
5594 }
5595}
5596
5597# endif
5598
5599#endif /* FEAT_TERMINAL */