blob: 56e08db50f2b568164807f58040e395bace8b2b4 [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 Moolenaar802bfb12018-04-15 17:28:13 +020045 * - When starting terminal window with shell in terminal, then using :gui to
46 * switch to GUI, shell stops working. Scrollback seems wrong, command
47 * running in shell is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020048 * - GUI: when using tabs, focus in terminal, click on tab does not work.
Bram Moolenaara997b452018-04-17 23:24:06 +020049 * - handle_moverect() scrolls one line at a time. Postpone scrolling, count
50 * the number of lines, until a redraw happens. Then if scrolling many lines
51 * a redraw is faster.
Bram Moolenaar498c2562018-04-15 23:45:15 +020052 * - Copy text in the vterm to the Vim buffer once in a while, so that
53 * completion works.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020054 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020055 * - For the GUI fill termios with default values, perhaps like pangoterm:
56 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar802bfb12018-04-15 17:28:13 +020057 * - When 'encoding' is not utf-8, or the job is using another encoding, setup
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020058 * conversions.
Bram Moolenaar498c2562018-04-15 23:45:15 +020059 * - Termdebug does not work when Vim build with mzscheme: gdb hangs just after
60 * "run". Everything else works, including communication channel. Not
61 * initializing mzscheme avoid the problem, thus it's not some #ifdef.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020062 */
63
64#include "vim.h"
65
66#if defined(FEAT_TERMINAL) || defined(PROTO)
67
68#ifndef MIN
69# define MIN(x,y) ((x) < (y) ? (x) : (y))
70#endif
71#ifndef MAX
72# define MAX(x,y) ((x) > (y) ? (x) : (y))
73#endif
74
75#include "libvterm/include/vterm.h"
76
77/* This is VTermScreenCell without the characters, thus much smaller. */
78typedef struct {
79 VTermScreenCellAttrs attrs;
80 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010081 VTermColor fg;
82 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020083} cellattr_T;
84
85typedef struct sb_line_S {
86 int sb_cols; /* can differ per line */
87 cellattr_T *sb_cells; /* allocated */
88 cellattr_T sb_fill_attr; /* for short line */
89} sb_line_T;
90
91/* typedef term_T in structs.h */
92struct terminal_S {
93 term_T *tl_next;
94
95 VTerm *tl_vterm;
96 job_T *tl_job;
97 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010098#if defined(FEAT_GUI)
99 int tl_system; /* when non-zero used for :!cmd output */
100 int tl_toprow; /* row with first line of system terminal */
101#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200102
103 /* Set when setting the size of a vterm, reset after redrawing. */
104 int tl_vterm_size_changed;
105
106 /* used when tl_job is NULL and only a pty was created */
107 int tl_tty_fd;
108 char_u *tl_tty_in;
109 char_u *tl_tty_out;
110
111 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
112 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100113 int tl_finish;
114#define TL_FINISH_UNSET NUL
115#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
116#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
117#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200118 char_u *tl_opencmd;
119 char_u *tl_eof_chars;
120
121#ifdef WIN3264
122 void *tl_winpty_config;
123 void *tl_winpty;
124#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100125#if defined(FEAT_SESSION)
126 char_u *tl_command;
127#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100128 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200129
130 /* last known vterm size */
131 int tl_rows;
132 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200133
134 char_u *tl_title; /* NULL or allocated */
135 char_u *tl_status_text; /* NULL or allocated */
136
137 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200138 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200139 int tl_dirty_row_end; /* row below last one to update */
140
141 garray_T tl_scrollback;
142 int tl_scrollback_scrolled;
143 cellattr_T tl_default_color;
144
Bram Moolenaard96ff162018-02-18 22:13:29 +0100145 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
146 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
147
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200148 VTermPos tl_cursor_pos;
149 int tl_cursor_visible;
150 int tl_cursor_blink;
151 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
152 char_u *tl_cursor_color; /* NULL or allocated */
153
154 int tl_using_altscreen;
155};
156
157#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
158#define TMODE_LOOP 2 /* CTRL-W N used */
159
160/*
161 * List of all active terminals.
162 */
163static term_T *first_term = NULL;
164
165/* Terminal active in terminal_loop(). */
166static term_T *in_terminal_loop = NULL;
167
168#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
169#define KEY_BUF_LEN 200
170
171/*
172 * Functions with separate implementation for MS-Windows and Unix-like systems.
173 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100174static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200175static int create_pty_only(term_T *term, jobopt_T *opt);
176static void term_report_winsize(term_T *term, int rows, int cols);
177static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100178#ifdef FEAT_GUI
179static void update_system_term(term_T *term);
180#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200181
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100182/* The character that we know (or assume) that the terminal expects for the
183 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200184static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200185
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100186/* "Terminal" highlight group colors. */
187static int term_default_cterm_fg = -1;
188static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200189
Bram Moolenaard317b382018-02-08 22:33:31 +0100190/* Store the last set and the desired cursor properties, so that we only update
191 * them when needed. Doing it unnecessary may result in flicker. */
192static char_u *last_set_cursor_color = (char_u *)"";
193static char_u *desired_cursor_color = (char_u *)"";
194static int last_set_cursor_shape = -1;
195static int desired_cursor_shape = -1;
196static int last_set_cursor_blink = -1;
197static int desired_cursor_blink = -1;
198
199
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200200/**************************************
201 * 1. Generic code for all systems.
202 */
203
204/*
Bram Moolenaar498c2562018-04-15 23:45:15 +0200205 * Parse 'termsize' and set "rows" and "cols" for the terminal size in the
206 * current window.
207 * Sets "rows" and/or "cols" to zero when it should follow the window size.
208 * Return TRUE if the size is the minimum size: "24*80".
209 */
210 static int
211parse_termsize(win_T *wp, int *rows, int *cols)
212{
213 int minsize = FALSE;
214
215 *rows = 0;
216 *cols = 0;
217
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200218 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200219 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200220 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200221
222 /* Syntax of value was already checked when it's set. */
223 if (p == NULL)
224 {
225 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200226 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200227 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200228 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200229 *cols = atoi((char *)p + 1);
230 }
231 return minsize;
232}
233
234/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200235 * Determine the terminal size from 'termsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200236 */
237 static void
238set_term_and_win_size(term_T *term)
239{
Bram Moolenaar13568252018-03-16 20:46:58 +0100240#ifdef FEAT_GUI
241 if (term->tl_system)
242 {
243 /* Use the whole screen for the system command. However, it will start
244 * at the command line and scroll up as needed, using tl_toprow. */
245 term->tl_rows = Rows;
246 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200247 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100248 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100249#endif
Bram Moolenaar498c2562018-04-15 23:45:15 +0200250 if (parse_termsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200251 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200252 if (term->tl_rows != 0)
253 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
254 if (term->tl_cols != 0)
255 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200256 }
257 if (term->tl_rows == 0)
258 term->tl_rows = curwin->w_height;
259 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200260 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200261 if (term->tl_cols == 0)
262 term->tl_cols = curwin->w_width;
263 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200264 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200265}
266
267/*
268 * Initialize job options for a terminal job.
269 * Caller may overrule some of them.
270 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100271 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200272init_job_options(jobopt_T *opt)
273{
274 clear_job_options(opt);
275
276 opt->jo_mode = MODE_RAW;
277 opt->jo_out_mode = MODE_RAW;
278 opt->jo_err_mode = MODE_RAW;
279 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
280}
281
282/*
283 * Set job options mandatory for a terminal job.
284 */
285 static void
286setup_job_options(jobopt_T *opt, int rows, int cols)
287{
288 if (!(opt->jo_set & JO_OUT_IO))
289 {
290 /* Connect stdout to the terminal. */
291 opt->jo_io[PART_OUT] = JIO_BUFFER;
292 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
293 opt->jo_modifiable[PART_OUT] = 0;
294 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
295 }
296
297 if (!(opt->jo_set & JO_ERR_IO))
298 {
299 /* Connect stderr to the terminal. */
300 opt->jo_io[PART_ERR] = JIO_BUFFER;
301 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
302 opt->jo_modifiable[PART_ERR] = 0;
303 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
304 }
305
306 opt->jo_pty = TRUE;
307 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
308 opt->jo_term_rows = rows;
309 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
310 opt->jo_term_cols = cols;
311}
312
313/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100314 * Close a terminal buffer (and its window). Used when creating the terminal
315 * fails.
316 */
317 static void
318term_close_buffer(buf_T *buf, buf_T *old_curbuf)
319{
320 free_terminal(buf);
321 if (old_curbuf != NULL)
322 {
323 --curbuf->b_nwindows;
324 curbuf = old_curbuf;
325 curwin->w_buffer = curbuf;
326 ++curbuf->b_nwindows;
327 }
328
329 /* Wiping out the buffer will also close the window and call
330 * free_terminal(). */
331 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
332}
333
334/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200335 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100336 * Use either "argvar" or "argv", the other must be NULL.
337 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
338 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200339 * Returns NULL when failed.
340 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100341 buf_T *
342term_start(
343 typval_T *argvar,
344 char **argv,
345 jobopt_T *opt,
346 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200347{
348 exarg_T split_ea;
349 win_T *old_curwin = curwin;
350 term_T *term;
351 buf_T *old_curbuf = NULL;
352 int res;
353 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100354 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200355
356 if (check_restricted() || check_secure())
357 return NULL;
358
359 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
360 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
361 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
362 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
363 {
364 EMSG(_(e_invarg));
365 return NULL;
366 }
367
368 term = (term_T *)alloc_clear(sizeof(term_T));
369 if (term == NULL)
370 return NULL;
371 term->tl_dirty_row_end = MAX_ROW;
372 term->tl_cursor_visible = TRUE;
373 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
374 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100375#ifdef FEAT_GUI
376 term->tl_system = (flags & TERM_START_SYSTEM);
377#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200378 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
379
380 vim_memset(&split_ea, 0, sizeof(split_ea));
381 if (opt->jo_curwin)
382 {
383 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100384 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200385 {
386 no_write_message();
387 vim_free(term);
388 return NULL;
389 }
390 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100391 ECMD_HIDE
392 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
393 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200394 {
395 vim_free(term);
396 return NULL;
397 }
398 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100399 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200400 {
401 buf_T *buf;
402
403 /* Create a new buffer without a window. Make it the current buffer for
404 * a moment to be able to do the initialisations. */
405 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
406 BLN_NEW | BLN_LISTED);
407 if (buf == NULL || ml_open(buf) == FAIL)
408 {
409 vim_free(term);
410 return NULL;
411 }
412 old_curbuf = curbuf;
413 --curbuf->b_nwindows;
414 curbuf = buf;
415 curwin->w_buffer = buf;
416 ++curbuf->b_nwindows;
417 }
418 else
419 {
420 /* Open a new window or tab. */
421 split_ea.cmdidx = CMD_new;
422 split_ea.cmd = (char_u *)"new";
423 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100424 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200425 {
426 split_ea.line2 = opt->jo_term_rows;
427 split_ea.addr_count = 1;
428 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100429 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200430 {
431 split_ea.line2 = opt->jo_term_cols;
432 split_ea.addr_count = 1;
433 }
434
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100435 if (vertical)
436 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200437 ex_splitview(&split_ea);
438 if (curwin == old_curwin)
439 {
440 /* split failed */
441 vim_free(term);
442 return NULL;
443 }
444 }
445 term->tl_buffer = curbuf;
446 curbuf->b_term = term;
447
448 if (!opt->jo_hidden)
449 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100450 /* Only one size was taken care of with :new, do the other one. With
451 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100452 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200453 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100454 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200455 win_setwidth(opt->jo_term_cols);
456 }
457
458 /* Link the new terminal in the list of active terminals. */
459 term->tl_next = first_term;
460 first_term = term;
461
462 if (opt->jo_term_name != NULL)
463 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100464 else if (argv != NULL)
465 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200466 else
467 {
468 int i;
469 size_t len;
470 char_u *cmd, *p;
471
472 if (argvar->v_type == VAR_STRING)
473 {
474 cmd = argvar->vval.v_string;
475 if (cmd == NULL)
476 cmd = (char_u *)"";
477 else if (STRCMP(cmd, "NONE") == 0)
478 cmd = (char_u *)"pty";
479 }
480 else if (argvar->v_type != VAR_LIST
481 || argvar->vval.v_list == NULL
482 || argvar->vval.v_list->lv_len < 1
483 || (cmd = get_tv_string_chk(
484 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
485 cmd = (char_u*)"";
486
487 len = STRLEN(cmd) + 10;
488 p = alloc((int)len);
489
490 for (i = 0; p != NULL; ++i)
491 {
492 /* Prepend a ! to the command name to avoid the buffer name equals
493 * the executable, otherwise ":w!" would overwrite it. */
494 if (i == 0)
495 vim_snprintf((char *)p, len, "!%s", cmd);
496 else
497 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
498 if (buflist_findname(p) == NULL)
499 {
500 vim_free(curbuf->b_ffname);
501 curbuf->b_ffname = p;
502 break;
503 }
504 }
505 }
506 curbuf->b_fname = curbuf->b_ffname;
507
508 if (opt->jo_term_opencmd != NULL)
509 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
510
511 if (opt->jo_eof_chars != NULL)
512 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
513
514 set_string_option_direct((char_u *)"buftype", -1,
515 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
516
517 /* Mark the buffer as not modifiable. It can only be made modifiable after
518 * the job finished. */
519 curbuf->b_p_ma = FALSE;
520
521 set_term_and_win_size(term);
522 setup_job_options(opt, term->tl_rows, term->tl_cols);
523
Bram Moolenaar13568252018-03-16 20:46:58 +0100524 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100525 return curbuf;
526
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100527#if defined(FEAT_SESSION)
528 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100529 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100530 {
531 term->tl_command = vim_strsave((char_u *)"NONE");
532 }
533 else if (argvar->v_type == VAR_STRING)
534 {
535 char_u *cmd = argvar->vval.v_string;
536
537 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
538 term->tl_command = vim_strsave(cmd);
539 }
540 else if (argvar->v_type == VAR_LIST
541 && argvar->vval.v_list != NULL
542 && argvar->vval.v_list->lv_len > 0)
543 {
544 garray_T ga;
545 listitem_T *item;
546
547 ga_init2(&ga, 1, 100);
548 for (item = argvar->vval.v_list->lv_first;
549 item != NULL; item = item->li_next)
550 {
551 char_u *s = get_tv_string_chk(&item->li_tv);
552 char_u *p;
553
554 if (s == NULL)
555 break;
556 p = vim_strsave_fnameescape(s, FALSE);
557 if (p == NULL)
558 break;
559 ga_concat(&ga, p);
560 vim_free(p);
561 ga_append(&ga, ' ');
562 }
563 if (item == NULL)
564 {
565 ga_append(&ga, NUL);
566 term->tl_command = ga.ga_data;
567 }
568 else
569 ga_clear(&ga);
570 }
571#endif
572
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100573 if (opt->jo_term_kill != NULL)
574 {
575 char_u *p = skiptowhite(opt->jo_term_kill);
576
577 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
578 }
579
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200580 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100581 if (argv == NULL
582 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200583 && argvar->vval.v_string != NULL
584 && STRCMP(argvar->vval.v_string, "NONE") == 0)
585 res = create_pty_only(term, opt);
586 else
Bram Moolenaar13568252018-03-16 20:46:58 +0100587 res = term_and_job_init(term, argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200588
589 newbuf = curbuf;
590 if (res == OK)
591 {
592 /* Get and remember the size we ended up with. Update the pty. */
593 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
594 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100595#ifdef FEAT_GUI
596 if (term->tl_system)
597 {
598 /* display first line below typed command */
599 term->tl_toprow = msg_row + 1;
600 term->tl_dirty_row_end = 0;
601 }
602#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200603
604 /* Make sure we don't get stuck on sending keys to the job, it leads to
605 * a deadlock if the job is waiting for Vim to read. */
606 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
607
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200608 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200609 {
610 --curbuf->b_nwindows;
611 curbuf = old_curbuf;
612 curwin->w_buffer = curbuf;
613 ++curbuf->b_nwindows;
614 }
615 }
616 else
617 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100618 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200619 return NULL;
620 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100621
Bram Moolenaar13568252018-03-16 20:46:58 +0100622 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200623 return newbuf;
624}
625
626/*
627 * ":terminal": open a terminal window and execute a job in it.
628 */
629 void
630ex_terminal(exarg_T *eap)
631{
632 typval_T argvar[2];
633 jobopt_T opt;
634 char_u *cmd;
635 char_u *tofree = NULL;
636
637 init_job_options(&opt);
638
639 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100640 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200641 {
642 char_u *p, *ep;
643
644 cmd += 2;
645 p = skiptowhite(cmd);
646 ep = vim_strchr(cmd, '=');
647 if (ep != NULL && ep < p)
648 p = ep;
649
650 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
651 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100652 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
653 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200654 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
655 opt.jo_term_finish = 'o';
656 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
657 opt.jo_curwin = 1;
658 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
659 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100660 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
661 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100662 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
663 && ep != NULL)
664 {
665 opt.jo_set2 |= JO2_TERM_KILL;
666 opt.jo_term_kill = ep + 1;
667 p = skiptowhite(cmd);
668 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200669 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
670 && ep != NULL && isdigit(ep[1]))
671 {
672 opt.jo_set2 |= JO2_TERM_ROWS;
673 opt.jo_term_rows = atoi((char *)ep + 1);
674 p = skiptowhite(cmd);
675 }
676 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
677 && ep != NULL && isdigit(ep[1]))
678 {
679 opt.jo_set2 |= JO2_TERM_COLS;
680 opt.jo_term_cols = atoi((char *)ep + 1);
681 p = skiptowhite(cmd);
682 }
683 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
684 && ep != NULL)
685 {
686 char_u *buf = NULL;
687 char_u *keys;
688
689 p = skiptowhite(cmd);
690 *p = NUL;
691 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
692 opt.jo_set2 |= JO2_EOF_CHARS;
693 opt.jo_eof_chars = vim_strsave(keys);
694 vim_free(buf);
695 *p = ' ';
696 }
697 else
698 {
699 if (*p)
700 *p = NUL;
701 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100702 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200703 }
704 cmd = skipwhite(p);
705 }
706 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100707 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200708 /* Make a copy of 'shell', an autocommand may change the option. */
709 tofree = cmd = vim_strsave(p_sh);
710
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100711 /* default to close when the shell exits */
712 if (opt.jo_term_finish == NUL)
713 opt.jo_term_finish = 'c';
714 }
715
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200716 if (eap->addr_count > 0)
717 {
718 /* Write lines from current buffer to the job. */
719 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
720 opt.jo_io[PART_IN] = JIO_BUFFER;
721 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
722 opt.jo_in_top = eap->line1;
723 opt.jo_in_bot = eap->line2;
724 }
725
726 argvar[0].v_type = VAR_STRING;
727 argvar[0].vval.v_string = cmd;
728 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100729 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200730 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100731
732theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200733 vim_free(opt.jo_eof_chars);
734}
735
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100736#if defined(FEAT_SESSION) || defined(PROTO)
737/*
738 * Write a :terminal command to the session file to restore the terminal in
739 * window "wp".
740 * Return FAIL if writing fails.
741 */
742 int
743term_write_session(FILE *fd, win_T *wp)
744{
745 term_T *term = wp->w_buffer->b_term;
746
747 /* Create the terminal and run the command. This is not without
748 * risk, but let's assume the user only creates a session when this
749 * will be OK. */
750 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
751 term->tl_cols, term->tl_rows) < 0)
752 return FAIL;
753 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
754 return FAIL;
755
756 return put_eol(fd);
757}
758
759/*
760 * Return TRUE if "buf" has a terminal that should be restored.
761 */
762 int
763term_should_restore(buf_T *buf)
764{
765 term_T *term = buf->b_term;
766
767 return term != NULL && (term->tl_command == NULL
768 || STRCMP(term->tl_command, "NONE") != 0);
769}
770#endif
771
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200772/*
773 * Free the scrollback buffer for "term".
774 */
775 static void
776free_scrollback(term_T *term)
777{
778 int i;
779
780 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
781 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
782 ga_clear(&term->tl_scrollback);
783}
784
785/*
786 * Free a terminal and everything it refers to.
787 * Kills the job if there is one.
788 * Called when wiping out a buffer.
789 */
790 void
791free_terminal(buf_T *buf)
792{
793 term_T *term = buf->b_term;
794 term_T *tp;
795
796 if (term == NULL)
797 return;
798 if (first_term == term)
799 first_term = term->tl_next;
800 else
801 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
802 if (tp->tl_next == term)
803 {
804 tp->tl_next = term->tl_next;
805 break;
806 }
807
808 if (term->tl_job != NULL)
809 {
810 if (term->tl_job->jv_status != JOB_ENDED
811 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100812 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200813 job_stop(term->tl_job, NULL, "kill");
814 job_unref(term->tl_job);
815 }
816
817 free_scrollback(term);
818
819 term_free_vterm(term);
820 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100821#ifdef FEAT_SESSION
822 vim_free(term->tl_command);
823#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100824 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200825 vim_free(term->tl_status_text);
826 vim_free(term->tl_opencmd);
827 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100828 if (desired_cursor_color == term->tl_cursor_color)
829 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200830 vim_free(term->tl_cursor_color);
831 vim_free(term);
832 buf->b_term = NULL;
833 if (in_terminal_loop == term)
834 in_terminal_loop = NULL;
835}
836
837/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100838 * Get the part that is connected to the tty. Normally this is PART_IN, but
839 * when writing buffer lines to the job it can be another. This makes it
840 * possible to do "1,5term vim -".
841 */
842 static ch_part_T
843get_tty_part(term_T *term)
844{
845#ifdef UNIX
846 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
847 int i;
848
849 for (i = 0; i < 3; ++i)
850 {
851 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
852
853 if (isatty(fd))
854 return parts[i];
855 }
856#endif
857 return PART_IN;
858}
859
860/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200861 * Write job output "msg[len]" to the vterm.
862 */
863 static void
864term_write_job_output(term_T *term, char_u *msg, size_t len)
865{
866 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100867 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200868
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100869 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200870
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100871 /* flush vterm buffer when vterm responded to control sequence */
872 if (prevlen != vterm_output_get_buffer_current(vterm))
873 {
874 char buf[KEY_BUF_LEN];
875 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
876
877 if (curlen > 0)
878 channel_send(term->tl_job->jv_channel, get_tty_part(term),
879 (char_u *)buf, (int)curlen, NULL);
880 }
881
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200882 /* this invokes the damage callbacks */
883 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
884}
885
886 static void
887update_cursor(term_T *term, int redraw)
888{
889 if (term->tl_normal_mode)
890 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100891#ifdef FEAT_GUI
892 if (term->tl_system)
893 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
894 term->tl_cursor_pos.col);
895 else
896#endif
897 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200898 if (redraw)
899 {
900 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
901 cursor_on();
902 out_flush();
903#ifdef FEAT_GUI
904 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100905 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200906 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100907 gui_mch_flush();
908 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200909#endif
910 }
911}
912
913/*
914 * Invoked when "msg" output from a job was received. Write it to the terminal
915 * of "buffer".
916 */
917 void
918write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
919{
920 size_t len = STRLEN(msg);
921 term_T *term = buffer->b_term;
922
923 if (term->tl_vterm == NULL)
924 {
925 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
926 return;
927 }
928 ch_log(channel, "writing %d bytes to terminal", (int)len);
929 term_write_job_output(term, msg, len);
930
Bram Moolenaar13568252018-03-16 20:46:58 +0100931#ifdef FEAT_GUI
932 if (term->tl_system)
933 {
934 /* show system output, scrolling up the screen as needed */
935 update_system_term(term);
936 update_cursor(term, TRUE);
937 }
938 else
939#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200940 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
941 * contents, thus no screen update is needed. */
942 if (!term->tl_normal_mode)
943 {
944 /* TODO: only update once in a while. */
945 ch_log(term->tl_job->jv_channel, "updating screen");
946 if (buffer == curbuf)
947 {
948 update_screen(0);
949 update_cursor(term, TRUE);
950 }
951 else
952 redraw_after_callback(TRUE);
953 }
954}
955
956/*
957 * Send a mouse position and click to the vterm
958 */
959 static int
960term_send_mouse(VTerm *vterm, int button, int pressed)
961{
962 VTermModifier mod = VTERM_MOD_NONE;
963
964 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200965 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100966 if (button != 0)
967 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200968 return TRUE;
969}
970
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100971static int enter_mouse_col = -1;
972static int enter_mouse_row = -1;
973
974/*
975 * Handle a mouse click, drag or release.
976 * Return TRUE when a mouse event is sent to the terminal.
977 */
978 static int
979term_mouse_click(VTerm *vterm, int key)
980{
981#if defined(FEAT_CLIPBOARD)
982 /* For modeless selection mouse drag and release events are ignored, unless
983 * they are preceded with a mouse down event */
984 static int ignore_drag_release = TRUE;
985 VTermMouseState mouse_state;
986
987 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
988 if (mouse_state.flags == 0)
989 {
990 /* Terminal is not using the mouse, use modeless selection. */
991 switch (key)
992 {
993 case K_LEFTDRAG:
994 case K_LEFTRELEASE:
995 case K_RIGHTDRAG:
996 case K_RIGHTRELEASE:
997 /* Ignore drag and release events when the button-down wasn't
998 * seen before. */
999 if (ignore_drag_release)
1000 {
1001 int save_mouse_col, save_mouse_row;
1002
1003 if (enter_mouse_col < 0)
1004 break;
1005
1006 /* mouse click in the window gave us focus, handle that
1007 * click now */
1008 save_mouse_col = mouse_col;
1009 save_mouse_row = mouse_row;
1010 mouse_col = enter_mouse_col;
1011 mouse_row = enter_mouse_row;
1012 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1013 mouse_col = save_mouse_col;
1014 mouse_row = save_mouse_row;
1015 }
1016 /* FALLTHROUGH */
1017 case K_LEFTMOUSE:
1018 case K_RIGHTMOUSE:
1019 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1020 ignore_drag_release = TRUE;
1021 else
1022 ignore_drag_release = FALSE;
1023 /* Should we call mouse_has() here? */
1024 if (clip_star.available)
1025 {
1026 int button, is_click, is_drag;
1027
1028 button = get_mouse_button(KEY2TERMCAP1(key),
1029 &is_click, &is_drag);
1030 if (mouse_model_popup() && button == MOUSE_LEFT
1031 && (mod_mask & MOD_MASK_SHIFT))
1032 {
1033 /* Translate shift-left to right button. */
1034 button = MOUSE_RIGHT;
1035 mod_mask &= ~MOD_MASK_SHIFT;
1036 }
1037 clip_modeless(button, is_click, is_drag);
1038 }
1039 break;
1040
1041 case K_MIDDLEMOUSE:
1042 if (clip_star.available)
1043 insert_reg('*', TRUE);
1044 break;
1045 }
1046 enter_mouse_col = -1;
1047 return FALSE;
1048 }
1049#endif
1050 enter_mouse_col = -1;
1051
1052 switch (key)
1053 {
1054 case K_LEFTMOUSE:
1055 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1056 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1057 case K_LEFTRELEASE:
1058 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1059 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1060 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1061 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1062 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1063 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1064 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1065 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1066 }
1067 return TRUE;
1068}
1069
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001070/*
1071 * Convert typed key "c" into bytes to send to the job.
1072 * Return the number of bytes in "buf".
1073 */
1074 static int
1075term_convert_key(term_T *term, int c, char *buf)
1076{
1077 VTerm *vterm = term->tl_vterm;
1078 VTermKey key = VTERM_KEY_NONE;
1079 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001080 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001081
1082 switch (c)
1083 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001084 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1085
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001086 /* don't use VTERM_KEY_BACKSPACE, it always
1087 * becomes 0x7f DEL */
1088 case K_BS: c = term_backspace_char; break;
1089
1090 case ESC: key = VTERM_KEY_ESCAPE; break;
1091 case K_DEL: key = VTERM_KEY_DEL; break;
1092 case K_DOWN: key = VTERM_KEY_DOWN; break;
1093 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1094 key = VTERM_KEY_DOWN; break;
1095 case K_END: key = VTERM_KEY_END; break;
1096 case K_S_END: mod = VTERM_MOD_SHIFT;
1097 key = VTERM_KEY_END; break;
1098 case K_C_END: mod = VTERM_MOD_CTRL;
1099 key = VTERM_KEY_END; break;
1100 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1101 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1102 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1103 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1104 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1105 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1106 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1107 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1108 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1109 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1110 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1111 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1112 case K_HOME: key = VTERM_KEY_HOME; break;
1113 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1114 key = VTERM_KEY_HOME; break;
1115 case K_C_HOME: mod = VTERM_MOD_CTRL;
1116 key = VTERM_KEY_HOME; break;
1117 case K_INS: key = VTERM_KEY_INS; break;
1118 case K_K0: key = VTERM_KEY_KP_0; break;
1119 case K_K1: key = VTERM_KEY_KP_1; break;
1120 case K_K2: key = VTERM_KEY_KP_2; break;
1121 case K_K3: key = VTERM_KEY_KP_3; break;
1122 case K_K4: key = VTERM_KEY_KP_4; break;
1123 case K_K5: key = VTERM_KEY_KP_5; break;
1124 case K_K6: key = VTERM_KEY_KP_6; break;
1125 case K_K7: key = VTERM_KEY_KP_7; break;
1126 case K_K8: key = VTERM_KEY_KP_8; break;
1127 case K_K9: key = VTERM_KEY_KP_9; break;
1128 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1129 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1130 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1131 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1132 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1133 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1134 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1135 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1136 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1137 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1138 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1139 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1140 case K_LEFT: key = VTERM_KEY_LEFT; break;
1141 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1142 key = VTERM_KEY_LEFT; break;
1143 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1144 key = VTERM_KEY_LEFT; break;
1145 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1146 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1147 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1148 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1149 key = VTERM_KEY_RIGHT; break;
1150 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1151 key = VTERM_KEY_RIGHT; break;
1152 case K_UP: key = VTERM_KEY_UP; break;
1153 case K_S_UP: mod = VTERM_MOD_SHIFT;
1154 key = VTERM_KEY_UP; break;
1155 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001156 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1157 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001158
Bram Moolenaara42ad572017-11-16 13:08:04 +01001159 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1160 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001161 case K_MOUSELEFT: /* TODO */ return 0;
1162 case K_MOUSERIGHT: /* TODO */ return 0;
1163
1164 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001165 case K_LEFTMOUSE_NM:
1166 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001167 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001168 case K_LEFTRELEASE_NM:
1169 case K_MOUSEMOVE:
1170 case K_MIDDLEMOUSE:
1171 case K_MIDDLEDRAG:
1172 case K_MIDDLERELEASE:
1173 case K_RIGHTMOUSE:
1174 case K_RIGHTDRAG:
1175 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1176 return 0;
1177 other = TRUE;
1178 break;
1179
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001180 case K_X1MOUSE: /* TODO */ return 0;
1181 case K_X1DRAG: /* TODO */ return 0;
1182 case K_X1RELEASE: /* TODO */ return 0;
1183 case K_X2MOUSE: /* TODO */ return 0;
1184 case K_X2DRAG: /* TODO */ return 0;
1185 case K_X2RELEASE: /* TODO */ return 0;
1186
1187 case K_IGNORE: return 0;
1188 case K_NOP: return 0;
1189 case K_UNDO: return 0;
1190 case K_HELP: return 0;
1191 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1192 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1193 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1194 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1195 case K_SELECT: return 0;
1196#ifdef FEAT_GUI
1197 case K_VER_SCROLLBAR: return 0;
1198 case K_HOR_SCROLLBAR: return 0;
1199#endif
1200#ifdef FEAT_GUI_TABLINE
1201 case K_TABLINE: return 0;
1202 case K_TABMENU: return 0;
1203#endif
1204#ifdef FEAT_NETBEANS_INTG
1205 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1206#endif
1207#ifdef FEAT_DND
1208 case K_DROP: return 0;
1209#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001210 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001211 case K_PS: vterm_keyboard_start_paste(vterm);
1212 other = TRUE;
1213 break;
1214 case K_PE: vterm_keyboard_end_paste(vterm);
1215 other = TRUE;
1216 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001217 }
1218
1219 /*
1220 * Convert special keys to vterm keys:
1221 * - Write keys to vterm: vterm_keyboard_key()
1222 * - Write output to channel.
1223 * TODO: use mod_mask
1224 */
1225 if (key != VTERM_KEY_NONE)
1226 /* Special key, let vterm convert it. */
1227 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001228 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001229 /* Normal character, let vterm convert it. */
1230 vterm_keyboard_unichar(vterm, c, mod);
1231
1232 /* Read back the converted escape sequence. */
1233 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1234}
1235
1236/*
1237 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001238 * If "check_job_status" is TRUE update the job status.
1239 */
1240 static int
1241term_job_running_check(term_T *term, int check_job_status)
1242{
1243 /* Also consider the job finished when the channel is closed, to avoid a
1244 * race condition when updating the title. */
1245 if (term != NULL
1246 && term->tl_job != NULL
1247 && channel_is_open(term->tl_job->jv_channel))
1248 {
1249 if (check_job_status)
1250 job_status(term->tl_job);
1251 return (term->tl_job->jv_status == JOB_STARTED
1252 || term->tl_job->jv_channel->ch_keep_open);
1253 }
1254 return FALSE;
1255}
1256
1257/*
1258 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001259 */
1260 int
1261term_job_running(term_T *term)
1262{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001263 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001264}
1265
1266/*
1267 * Return TRUE if "term" has an active channel and used ":term NONE".
1268 */
1269 int
1270term_none_open(term_T *term)
1271{
1272 /* Also consider the job finished when the channel is closed, to avoid a
1273 * race condition when updating the title. */
1274 return term != NULL
1275 && term->tl_job != NULL
1276 && channel_is_open(term->tl_job->jv_channel)
1277 && term->tl_job->jv_channel->ch_keep_open;
1278}
1279
1280/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001281 * Used when exiting: kill the job in "buf" if so desired.
1282 * Return OK when the job finished.
1283 * Return FAIL when the job is still running.
1284 */
1285 int
1286term_try_stop_job(buf_T *buf)
1287{
1288 int count;
1289 char *how = (char *)buf->b_term->tl_kill;
1290
1291#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1292 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1293 {
1294 char_u buff[DIALOG_MSG_SIZE];
1295 int ret;
1296
1297 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1298 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1299 if (ret == VIM_YES)
1300 how = "kill";
1301 else if (ret == VIM_CANCEL)
1302 return FAIL;
1303 }
1304#endif
1305 if (how == NULL || *how == NUL)
1306 return FAIL;
1307
1308 job_stop(buf->b_term->tl_job, NULL, how);
1309
1310 /* wait for up to a second for the job to die */
1311 for (count = 0; count < 100; ++count)
1312 {
1313 /* buffer, terminal and job may be cleaned up while waiting */
1314 if (!buf_valid(buf)
1315 || buf->b_term == NULL
1316 || buf->b_term->tl_job == NULL)
1317 return OK;
1318
1319 /* call job_status() to update jv_status */
1320 job_status(buf->b_term->tl_job);
1321 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1322 return OK;
1323 ui_delay(10L, FALSE);
1324 mch_check_messages();
1325 parse_queued_messages();
1326 }
1327 return FAIL;
1328}
1329
1330/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001331 * Add the last line of the scrollback buffer to the buffer in the window.
1332 */
1333 static void
1334add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1335{
1336 buf_T *buf = term->tl_buffer;
1337 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1338 linenr_T lnum = buf->b_ml.ml_line_count;
1339
1340#ifdef WIN3264
1341 if (!enc_utf8 && enc_codepage > 0)
1342 {
1343 WCHAR *ret = NULL;
1344 int length = 0;
1345
1346 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1347 &ret, &length);
1348 if (ret != NULL)
1349 {
1350 WideCharToMultiByte_alloc(enc_codepage, 0,
1351 ret, length, (char **)&text, &len, 0, 0);
1352 vim_free(ret);
1353 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1354 vim_free(text);
1355 }
1356 }
1357 else
1358#endif
1359 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1360 if (empty)
1361 {
1362 /* Delete the empty line that was in the empty buffer. */
1363 curbuf = buf;
1364 ml_delete(1, FALSE);
1365 curbuf = curwin->w_buffer;
1366 }
1367}
1368
1369 static void
1370cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1371{
1372 attr->width = cell->width;
1373 attr->attrs = cell->attrs;
1374 attr->fg = cell->fg;
1375 attr->bg = cell->bg;
1376}
1377
1378 static int
1379equal_celattr(cellattr_T *a, cellattr_T *b)
1380{
1381 /* Comparing the colors should be sufficient. */
1382 return a->fg.red == b->fg.red
1383 && a->fg.green == b->fg.green
1384 && a->fg.blue == b->fg.blue
1385 && a->bg.red == b->bg.red
1386 && a->bg.green == b->bg.green
1387 && a->bg.blue == b->bg.blue;
1388}
1389
Bram Moolenaard96ff162018-02-18 22:13:29 +01001390/*
1391 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1392 * line at this position. Otherwise at the end.
1393 */
1394 static int
1395add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1396{
1397 if (ga_grow(&term->tl_scrollback, 1) == OK)
1398 {
1399 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1400 + term->tl_scrollback.ga_len;
1401
1402 if (lnum > 0)
1403 {
1404 int i;
1405
1406 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1407 {
1408 *line = *(line - 1);
1409 --line;
1410 }
1411 }
1412 line->sb_cols = 0;
1413 line->sb_cells = NULL;
1414 line->sb_fill_attr = *fill_attr;
1415 ++term->tl_scrollback.ga_len;
1416 return OK;
1417 }
1418 return FALSE;
1419}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001420
1421/*
1422 * Add the current lines of the terminal to scrollback and to the buffer.
1423 * Called after the job has ended and when switching to Terminal-Normal mode.
1424 */
1425 static void
1426move_terminal_to_buffer(term_T *term)
1427{
1428 win_T *wp;
1429 int len;
1430 int lines_skipped = 0;
1431 VTermPos pos;
1432 VTermScreenCell cell;
1433 cellattr_T fill_attr, new_fill_attr;
1434 cellattr_T *p;
1435 VTermScreen *screen;
1436
1437 if (term->tl_vterm == NULL)
1438 return;
1439 screen = vterm_obtain_screen(term->tl_vterm);
1440 fill_attr = new_fill_attr = term->tl_default_color;
1441
1442 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1443 {
1444 len = 0;
1445 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1446 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1447 && cell.chars[0] != NUL)
1448 {
1449 len = pos.col + 1;
1450 new_fill_attr = term->tl_default_color;
1451 }
1452 else
1453 /* Assume the last attr is the filler attr. */
1454 cell2cellattr(&cell, &new_fill_attr);
1455
1456 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1457 ++lines_skipped;
1458 else
1459 {
1460 while (lines_skipped > 0)
1461 {
1462 /* Line was skipped, add an empty line. */
1463 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001464 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001465 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001466 }
1467
1468 if (len == 0)
1469 p = NULL;
1470 else
1471 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1472 if ((p != NULL || len == 0)
1473 && ga_grow(&term->tl_scrollback, 1) == OK)
1474 {
1475 garray_T ga;
1476 int width;
1477 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1478 + term->tl_scrollback.ga_len;
1479
1480 ga_init2(&ga, 1, 100);
1481 for (pos.col = 0; pos.col < len; pos.col += width)
1482 {
1483 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1484 {
1485 width = 1;
1486 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1487 if (ga_grow(&ga, 1) == OK)
1488 ga.ga_len += utf_char2bytes(' ',
1489 (char_u *)ga.ga_data + ga.ga_len);
1490 }
1491 else
1492 {
1493 width = cell.width;
1494
1495 cell2cellattr(&cell, &p[pos.col]);
1496
1497 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1498 {
1499 int i;
1500 int c;
1501
1502 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1503 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1504 (char_u *)ga.ga_data + ga.ga_len);
1505 }
1506 }
1507 }
1508 line->sb_cols = len;
1509 line->sb_cells = p;
1510 line->sb_fill_attr = new_fill_attr;
1511 fill_attr = new_fill_attr;
1512 ++term->tl_scrollback.ga_len;
1513
1514 if (ga_grow(&ga, 1) == FAIL)
1515 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1516 else
1517 {
1518 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1519 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1520 }
1521 ga_clear(&ga);
1522 }
1523 else
1524 vim_free(p);
1525 }
1526 }
1527
1528 /* Obtain the current background color. */
1529 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1530 &term->tl_default_color.fg, &term->tl_default_color.bg);
1531
1532 FOR_ALL_WINDOWS(wp)
1533 {
1534 if (wp->w_buffer == term->tl_buffer)
1535 {
1536 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1537 wp->w_cursor.col = 0;
1538 wp->w_valid = 0;
1539 if (wp->w_cursor.lnum >= wp->w_height)
1540 {
1541 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1542
1543 if (wp->w_topline < min_topline)
1544 wp->w_topline = min_topline;
1545 }
1546 redraw_win_later(wp, NOT_VALID);
1547 }
1548 }
1549}
1550
1551 static void
1552set_terminal_mode(term_T *term, int normal_mode)
1553{
1554 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001555 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001556 if (term->tl_buffer == curbuf)
1557 maketitle();
1558}
1559
1560/*
1561 * Called after the job if finished and Terminal mode is not active:
1562 * Move the vterm contents into the scrollback buffer and free the vterm.
1563 */
1564 static void
1565cleanup_vterm(term_T *term)
1566{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001567 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001568 move_terminal_to_buffer(term);
1569 term_free_vterm(term);
1570 set_terminal_mode(term, FALSE);
1571}
1572
1573/*
1574 * Switch from Terminal-Job mode to Terminal-Normal mode.
1575 * Suspends updating the terminal window.
1576 */
1577 static void
1578term_enter_normal_mode(void)
1579{
1580 term_T *term = curbuf->b_term;
1581
1582 /* Append the current terminal contents to the buffer. */
1583 move_terminal_to_buffer(term);
1584
1585 set_terminal_mode(term, TRUE);
1586
1587 /* Move the window cursor to the position of the cursor in the
1588 * terminal. */
1589 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1590 + term->tl_cursor_pos.row + 1;
1591 check_cursor();
1592 coladvance(term->tl_cursor_pos.col);
1593
1594 /* Display the same lines as in the terminal. */
1595 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1596}
1597
1598/*
1599 * Returns TRUE if the current window contains a terminal and we are in
1600 * Terminal-Normal mode.
1601 */
1602 int
1603term_in_normal_mode(void)
1604{
1605 term_T *term = curbuf->b_term;
1606
1607 return term != NULL && term->tl_normal_mode;
1608}
1609
1610/*
1611 * Switch from Terminal-Normal mode to Terminal-Job mode.
1612 * Restores updating the terminal window.
1613 */
1614 void
1615term_enter_job_mode()
1616{
1617 term_T *term = curbuf->b_term;
1618 sb_line_T *line;
1619 garray_T *gap;
1620
1621 /* Remove the terminal contents from the scrollback and the buffer. */
1622 gap = &term->tl_scrollback;
1623 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1624 && gap->ga_len > 0)
1625 {
1626 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1627 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1628 vim_free(line->sb_cells);
1629 --gap->ga_len;
1630 }
1631 check_cursor();
1632
1633 set_terminal_mode(term, FALSE);
1634
1635 if (term->tl_channel_closed)
1636 cleanup_vterm(term);
1637 redraw_buf_and_status_later(curbuf, NOT_VALID);
1638}
1639
1640/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001641 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001642 * Note: while waiting a terminal may be closed and freed if the channel is
1643 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001644 */
1645 static int
1646term_vgetc()
1647{
1648 int c;
1649 int save_State = State;
1650
1651 State = TERMINAL;
1652 got_int = FALSE;
1653#ifdef WIN3264
1654 ctrl_break_was_pressed = FALSE;
1655#endif
1656 c = vgetc();
1657 got_int = FALSE;
1658 State = save_State;
1659 return c;
1660}
1661
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001662static int mouse_was_outside = FALSE;
1663
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001664/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001665 * Send keys to terminal.
1666 * Return FAIL when the key needs to be handled in Normal mode.
1667 * Return OK when the key was dropped or sent to the terminal.
1668 */
1669 int
1670send_keys_to_term(term_T *term, int c, int typed)
1671{
1672 char msg[KEY_BUF_LEN];
1673 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001674 int dragging_outside = FALSE;
1675
1676 /* Catch keys that need to be handled as in Normal mode. */
1677 switch (c)
1678 {
1679 case NUL:
1680 case K_ZERO:
1681 if (typed)
1682 stuffcharReadbuff(c);
1683 return FAIL;
1684
1685 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001686 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001687 return FAIL;
1688
1689 case K_LEFTDRAG:
1690 case K_MIDDLEDRAG:
1691 case K_RIGHTDRAG:
1692 case K_X1DRAG:
1693 case K_X2DRAG:
1694 dragging_outside = mouse_was_outside;
1695 /* FALLTHROUGH */
1696 case K_LEFTMOUSE:
1697 case K_LEFTMOUSE_NM:
1698 case K_LEFTRELEASE:
1699 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001700 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001701 case K_MIDDLEMOUSE:
1702 case K_MIDDLERELEASE:
1703 case K_RIGHTMOUSE:
1704 case K_RIGHTRELEASE:
1705 case K_X1MOUSE:
1706 case K_X1RELEASE:
1707 case K_X2MOUSE:
1708 case K_X2RELEASE:
1709
1710 case K_MOUSEUP:
1711 case K_MOUSEDOWN:
1712 case K_MOUSELEFT:
1713 case K_MOUSERIGHT:
1714 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001715 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001716 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001717 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001718 || dragging_outside)
1719 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001720 /* click or scroll outside the current window or on status line
1721 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001722 if (typed)
1723 {
1724 stuffcharReadbuff(c);
1725 mouse_was_outside = TRUE;
1726 }
1727 return FAIL;
1728 }
1729 }
1730 if (typed)
1731 mouse_was_outside = FALSE;
1732
1733 /* Convert the typed key to a sequence of bytes for the job. */
1734 len = term_convert_key(term, c, msg);
1735 if (len > 0)
1736 /* TODO: if FAIL is returned, stop? */
1737 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1738 (char_u *)msg, (int)len, NULL);
1739
1740 return OK;
1741}
1742
1743 static void
1744position_cursor(win_T *wp, VTermPos *pos)
1745{
1746 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1747 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1748 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1749}
1750
1751/*
1752 * Handle CTRL-W "": send register contents to the job.
1753 */
1754 static void
1755term_paste_register(int prev_c UNUSED)
1756{
1757 int c;
1758 list_T *l;
1759 listitem_T *item;
1760 long reglen = 0;
1761 int type;
1762
1763#ifdef FEAT_CMDL_INFO
1764 if (add_to_showcmd(prev_c))
1765 if (add_to_showcmd('"'))
1766 out_flush();
1767#endif
1768 c = term_vgetc();
1769#ifdef FEAT_CMDL_INFO
1770 clear_showcmd();
1771#endif
1772 if (!term_use_loop())
1773 /* job finished while waiting for a character */
1774 return;
1775
1776 /* CTRL-W "= prompt for expression to evaluate. */
1777 if (c == '=' && get_expr_register() != '=')
1778 return;
1779 if (!term_use_loop())
1780 /* job finished while waiting for a character */
1781 return;
1782
1783 l = (list_T *)get_reg_contents(c, GREG_LIST);
1784 if (l != NULL)
1785 {
1786 type = get_reg_type(c, &reglen);
1787 for (item = l->lv_first; item != NULL; item = item->li_next)
1788 {
1789 char_u *s = get_tv_string(&item->li_tv);
1790#ifdef WIN3264
1791 char_u *tmp = s;
1792
1793 if (!enc_utf8 && enc_codepage > 0)
1794 {
1795 WCHAR *ret = NULL;
1796 int length = 0;
1797
1798 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1799 (int)STRLEN(s), &ret, &length);
1800 if (ret != NULL)
1801 {
1802 WideCharToMultiByte_alloc(CP_UTF8, 0,
1803 ret, length, (char **)&s, &length, 0, 0);
1804 vim_free(ret);
1805 }
1806 }
1807#endif
1808 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1809 s, (int)STRLEN(s), NULL);
1810#ifdef WIN3264
1811 if (tmp != s)
1812 vim_free(s);
1813#endif
1814
1815 if (item->li_next != NULL || type == MLINE)
1816 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1817 (char_u *)"\r", 1, NULL);
1818 }
1819 list_free(l);
1820 }
1821}
1822
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001823/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001824 * Return TRUE when waiting for a character in the terminal, the cursor of the
1825 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001826 */
1827 int
1828terminal_is_active()
1829{
1830 return in_terminal_loop != NULL;
1831}
1832
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001833#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001834 cursorentry_T *
1835term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1836{
1837 term_T *term = in_terminal_loop;
1838 static cursorentry_T entry;
1839
1840 vim_memset(&entry, 0, sizeof(entry));
1841 entry.shape = entry.mshape =
1842 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1843 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1844 SHAPE_BLOCK;
1845 entry.percentage = 20;
1846 if (term->tl_cursor_blink)
1847 {
1848 entry.blinkwait = 700;
1849 entry.blinkon = 400;
1850 entry.blinkoff = 250;
1851 }
1852 *fg = gui.back_pixel;
1853 if (term->tl_cursor_color == NULL)
1854 *bg = gui.norm_pixel;
1855 else
1856 *bg = color_name2handle(term->tl_cursor_color);
1857 entry.name = "n";
1858 entry.used_for = SHAPE_CURSOR;
1859
1860 return &entry;
1861}
1862#endif
1863
Bram Moolenaard317b382018-02-08 22:33:31 +01001864 static void
1865may_output_cursor_props(void)
1866{
1867 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1868 || last_set_cursor_shape != desired_cursor_shape
1869 || last_set_cursor_blink != desired_cursor_blink)
1870 {
1871 last_set_cursor_color = desired_cursor_color;
1872 last_set_cursor_shape = desired_cursor_shape;
1873 last_set_cursor_blink = desired_cursor_blink;
1874 term_cursor_color(desired_cursor_color);
1875 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1876 /* this will restore the initial cursor style, if possible */
1877 ui_cursor_shape_forced(TRUE);
1878 else
1879 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1880 }
1881}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001882
Bram Moolenaard317b382018-02-08 22:33:31 +01001883/*
1884 * Set the cursor color and shape, if not last set to these.
1885 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001886 static void
1887may_set_cursor_props(term_T *term)
1888{
1889#ifdef FEAT_GUI
1890 /* For the GUI the cursor properties are obtained with
1891 * term_get_cursor_shape(). */
1892 if (gui.in_use)
1893 return;
1894#endif
1895 if (in_terminal_loop == term)
1896 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001897 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001898 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001899 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001900 desired_cursor_color = (char_u *)"";
1901 desired_cursor_shape = term->tl_cursor_shape;
1902 desired_cursor_blink = term->tl_cursor_blink;
1903 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001904 }
1905}
1906
Bram Moolenaard317b382018-02-08 22:33:31 +01001907/*
1908 * Reset the desired cursor properties and restore them when needed.
1909 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001910 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001911prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001912{
1913#ifdef FEAT_GUI
1914 if (gui.in_use)
1915 return;
1916#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001917 desired_cursor_color = (char_u *)"";
1918 desired_cursor_shape = -1;
1919 desired_cursor_blink = -1;
1920 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001921}
1922
1923/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001924 * Returns TRUE if the current window contains a terminal and we are sending
1925 * keys to the job.
1926 * If "check_job_status" is TRUE update the job status.
1927 */
1928 static int
1929term_use_loop_check(int check_job_status)
1930{
1931 term_T *term = curbuf->b_term;
1932
1933 return term != NULL
1934 && !term->tl_normal_mode
1935 && term->tl_vterm != NULL
1936 && term_job_running_check(term, check_job_status);
1937}
1938
1939/*
1940 * Returns TRUE if the current window contains a terminal and we are sending
1941 * keys to the job.
1942 */
1943 int
1944term_use_loop(void)
1945{
1946 return term_use_loop_check(FALSE);
1947}
1948
1949/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001950 * Called when entering a window with the mouse. If this is a terminal window
1951 * we may want to change state.
1952 */
1953 void
1954term_win_entered()
1955{
1956 term_T *term = curbuf->b_term;
1957
1958 if (term != NULL)
1959 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001960 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001961 {
1962 reset_VIsual_and_resel();
1963 if (State & INSERT)
1964 stop_insert_mode = TRUE;
1965 }
1966 mouse_was_outside = FALSE;
1967 enter_mouse_col = mouse_col;
1968 enter_mouse_row = mouse_row;
1969 }
1970}
1971
1972/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001973 * Wait for input and send it to the job.
1974 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1975 * when there is no more typahead.
1976 * Return when the start of a CTRL-W command is typed or anything else that
1977 * should be handled as a Normal mode command.
1978 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1979 * the terminal was closed.
1980 */
1981 int
1982terminal_loop(int blocking)
1983{
1984 int c;
1985 int termkey = 0;
1986 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001987#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001988 int tty_fd = curbuf->b_term->tl_job->jv_channel
1989 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001990#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001991 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001992
1993 /* Remember the terminal we are sending keys to. However, the terminal
1994 * might be closed while waiting for a character, e.g. typing "exit" in a
1995 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1996 * stored reference. */
1997 in_terminal_loop = curbuf->b_term;
1998
Bram Moolenaar6d150f72018-04-21 20:03:20 +02001999 if (*curwin->w_p_twk != NUL)
2000 termkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002001 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2002 may_set_cursor_props(curbuf->b_term);
2003
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002004 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002005 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002006#ifdef FEAT_GUI
2007 if (!curbuf->b_term->tl_system)
2008#endif
2009 /* TODO: skip screen update when handling a sequence of keys. */
2010 /* Repeat redrawing in case a message is received while redrawing.
2011 */
2012 while (must_redraw != 0)
2013 if (update_screen(0) == FAIL)
2014 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002015 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002016 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002017
2018 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002019 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002020 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002021 /* Job finished while waiting for a character. Push back the
2022 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002023 if (c != K_IGNORE)
2024 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002025 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002026 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002027 if (c == K_IGNORE)
2028 continue;
2029
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002030#ifdef UNIX
2031 /*
2032 * The shell or another program may change the tty settings. Getting
2033 * them for every typed character is a bit of overhead, but it's needed
2034 * for the first character typed, e.g. when Vim starts in a shell.
2035 */
2036 if (isatty(tty_fd))
2037 {
2038 ttyinfo_T info;
2039
2040 /* Get the current backspace character of the pty. */
2041 if (get_tty_info(tty_fd, &info) == OK)
2042 term_backspace_char = info.backspace;
2043 }
2044#endif
2045
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002046#ifdef WIN3264
2047 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2048 * Use CTRL-BREAK to kill the job. */
2049 if (ctrl_break_was_pressed)
2050 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2051#endif
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002052 /* Was either CTRL-W (termkey) or CTRL-\ pressed?
2053 * Not in a system terminal. */
2054 if ((c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2055#ifdef FEAT_GUI
2056 && !curbuf->b_term->tl_system
2057#endif
2058 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002059 {
2060 int prev_c = c;
2061
2062#ifdef FEAT_CMDL_INFO
2063 if (add_to_showcmd(c))
2064 out_flush();
2065#endif
2066 c = term_vgetc();
2067#ifdef FEAT_CMDL_INFO
2068 clear_showcmd();
2069#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002070 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002071 /* job finished while waiting for a character */
2072 break;
2073
2074 if (prev_c == Ctrl_BSL)
2075 {
2076 if (c == Ctrl_N)
2077 {
2078 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2079 term_enter_normal_mode();
2080 ret = FAIL;
2081 goto theend;
2082 }
2083 /* Send both keys to the terminal. */
2084 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2085 }
2086 else if (c == Ctrl_C)
2087 {
2088 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2089 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2090 }
2091 else if (termkey == 0 && c == '.')
2092 {
2093 /* "CTRL-W .": send CTRL-W to the job */
2094 c = Ctrl_W;
2095 }
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002096 else if (termkey == 0 && c == Ctrl_BSL)
2097 {
2098 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2099 c = Ctrl_BSL;
2100 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002101 else if (c == 'N')
2102 {
2103 /* CTRL-W N : go to Terminal-Normal mode. */
2104 term_enter_normal_mode();
2105 ret = FAIL;
2106 goto theend;
2107 }
2108 else if (c == '"')
2109 {
2110 term_paste_register(prev_c);
2111 continue;
2112 }
2113 else if (termkey == 0 || c != termkey)
2114 {
2115 stuffcharReadbuff(Ctrl_W);
2116 stuffcharReadbuff(c);
2117 ret = OK;
2118 goto theend;
2119 }
2120 }
2121# ifdef WIN3264
2122 if (!enc_utf8 && has_mbyte && c >= 0x80)
2123 {
2124 WCHAR wc;
2125 char_u mb[3];
2126
2127 mb[0] = (unsigned)c >> 8;
2128 mb[1] = c;
2129 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2130 c = wc;
2131 }
2132# endif
2133 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2134 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002135 if (c == K_MOUSEMOVE)
2136 /* We are sure to come back here, don't reset the cursor color
2137 * and shape to avoid flickering. */
2138 restore_cursor = FALSE;
2139
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002140 ret = OK;
2141 goto theend;
2142 }
2143 }
2144 ret = FAIL;
2145
2146theend:
2147 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002148 if (restore_cursor)
2149 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002150 return ret;
2151}
2152
2153/*
2154 * Called when a job has finished.
2155 * This updates the title and status, but does not close the vterm, because
2156 * there might still be pending output in the channel.
2157 */
2158 void
2159term_job_ended(job_T *job)
2160{
2161 term_T *term;
2162 int did_one = FALSE;
2163
2164 for (term = first_term; term != NULL; term = term->tl_next)
2165 if (term->tl_job == job)
2166 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002167 VIM_CLEAR(term->tl_title);
2168 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002169 redraw_buf_and_status_later(term->tl_buffer, VALID);
2170 did_one = TRUE;
2171 }
2172 if (did_one)
2173 redraw_statuslines();
2174 if (curbuf->b_term != NULL)
2175 {
2176 if (curbuf->b_term->tl_job == job)
2177 maketitle();
2178 update_cursor(curbuf->b_term, TRUE);
2179 }
2180}
2181
2182 static void
2183may_toggle_cursor(term_T *term)
2184{
2185 if (in_terminal_loop == term)
2186 {
2187 if (term->tl_cursor_visible)
2188 cursor_on();
2189 else
2190 cursor_off();
2191 }
2192}
2193
2194/*
2195 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002196 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002197 */
2198 static int
2199color2index(VTermColor *color, int fg, int *boldp)
2200{
2201 int red = color->red;
2202 int blue = color->blue;
2203 int green = color->green;
2204
Bram Moolenaar46359e12017-11-29 22:33:38 +01002205 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002206 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002207 /* First 16 colors and default: use the ANSI index, because these
2208 * colors can be redefined. */
2209 if (t_colors >= 16)
2210 return color->ansi_index;
2211 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002212 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002213 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002214 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002215 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2216 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2217 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002218 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002219 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2220 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2221 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2222 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2223 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2224 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2225 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2226 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2227 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2228 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2229 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002230 }
2231 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002232
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002233 if (t_colors >= 256)
2234 {
2235 if (red == blue && red == green)
2236 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002237 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002238 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002239 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2240 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2241 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002242 int i;
2243
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002244 if (red < 5)
2245 return 17; /* 00/00/00 */
2246 if (red > 245) /* ff/ff/ff */
2247 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002248 for (i = 0; i < 23; ++i)
2249 if (red < cutoff[i])
2250 return i + 233;
2251 return 256;
2252 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002253 {
2254 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2255 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002256
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002257 /* 216-color cube */
2258 for (ri = 0; ri < 5; ++ri)
2259 if (red < cutoff[ri])
2260 break;
2261 for (gi = 0; gi < 5; ++gi)
2262 if (green < cutoff[gi])
2263 break;
2264 for (bi = 0; bi < 5; ++bi)
2265 if (blue < cutoff[bi])
2266 break;
2267 return 17 + ri * 36 + gi * 6 + bi;
2268 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002269 }
2270 return 0;
2271}
2272
2273/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002274 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002275 */
2276 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002277vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002278{
2279 int attr = 0;
2280
2281 if (cellattrs.bold)
2282 attr |= HL_BOLD;
2283 if (cellattrs.underline)
2284 attr |= HL_UNDERLINE;
2285 if (cellattrs.italic)
2286 attr |= HL_ITALIC;
2287 if (cellattrs.strike)
2288 attr |= HL_STRIKETHROUGH;
2289 if (cellattrs.reverse)
2290 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002291 return attr;
2292}
2293
2294/*
2295 * Store Vterm attributes in "cell" from highlight flags.
2296 */
2297 static void
2298hl2vtermAttr(int attr, cellattr_T *cell)
2299{
2300 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2301 if (attr & HL_BOLD)
2302 cell->attrs.bold = 1;
2303 if (attr & HL_UNDERLINE)
2304 cell->attrs.underline = 1;
2305 if (attr & HL_ITALIC)
2306 cell->attrs.italic = 1;
2307 if (attr & HL_STRIKETHROUGH)
2308 cell->attrs.strike = 1;
2309 if (attr & HL_INVERSE)
2310 cell->attrs.reverse = 1;
2311}
2312
2313/*
2314 * Convert the attributes of a vterm cell into an attribute index.
2315 */
2316 static int
2317cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2318{
2319 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002320
2321#ifdef FEAT_GUI
2322 if (gui.in_use)
2323 {
2324 guicolor_T fg, bg;
2325
2326 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2327 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2328 return get_gui_attr_idx(attr, fg, bg);
2329 }
2330 else
2331#endif
2332#ifdef FEAT_TERMGUICOLORS
2333 if (p_tgc)
2334 {
2335 guicolor_T fg, bg;
2336
2337 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2338 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2339
2340 return get_tgc_attr_idx(attr, fg, bg);
2341 }
2342 else
2343#endif
2344 {
2345 int bold = MAYBE;
2346 int fg = color2index(&cellfg, TRUE, &bold);
2347 int bg = color2index(&cellbg, FALSE, &bold);
2348
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002349 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002350 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002351 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002352 if (fg == 0 && term_default_cterm_fg >= 0)
2353 fg = term_default_cterm_fg + 1;
2354 if (bg == 0 && term_default_cterm_bg >= 0)
2355 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002356 }
2357
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002358 /* with 8 colors set the bold attribute to get a bright foreground */
2359 if (bold == TRUE)
2360 attr |= HL_BOLD;
2361 return get_cterm_attr_idx(attr, fg, bg);
2362 }
2363 return 0;
2364}
2365
2366 static int
2367handle_damage(VTermRect rect, void *user)
2368{
2369 term_T *term = (term_T *)user;
2370
2371 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2372 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2373 redraw_buf_later(term->tl_buffer, NOT_VALID);
2374 return 1;
2375}
2376
2377 static int
2378handle_moverect(VTermRect dest, VTermRect src, void *user)
2379{
2380 term_T *term = (term_T *)user;
2381
2382 /* Scrolling up is done much more efficiently by deleting lines instead of
2383 * redrawing the text. */
2384 if (dest.start_col == src.start_col
2385 && dest.end_col == src.end_col
2386 && dest.start_row < src.start_row)
2387 {
2388 win_T *wp;
2389 VTermColor fg, bg;
2390 VTermScreenCellAttrs attr;
2391 int clear_attr;
2392
2393 /* Set the color to clear lines with. */
2394 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2395 &fg, &bg);
2396 vim_memset(&attr, 0, sizeof(attr));
2397 clear_attr = cell2attr(attr, fg, bg);
2398
2399 FOR_ALL_WINDOWS(wp)
2400 {
2401 if (wp->w_buffer == term->tl_buffer)
2402 win_del_lines(wp, dest.start_row,
2403 src.start_row - dest.start_row, FALSE, FALSE,
2404 clear_attr);
2405 }
2406 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002407
2408 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2409 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2410
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002411 redraw_buf_later(term->tl_buffer, NOT_VALID);
2412 return 1;
2413}
2414
2415 static int
2416handle_movecursor(
2417 VTermPos pos,
2418 VTermPos oldpos UNUSED,
2419 int visible,
2420 void *user)
2421{
2422 term_T *term = (term_T *)user;
2423 win_T *wp;
2424
2425 term->tl_cursor_pos = pos;
2426 term->tl_cursor_visible = visible;
2427
2428 FOR_ALL_WINDOWS(wp)
2429 {
2430 if (wp->w_buffer == term->tl_buffer)
2431 position_cursor(wp, &pos);
2432 }
2433 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2434 {
2435 may_toggle_cursor(term);
2436 update_cursor(term, term->tl_cursor_visible);
2437 }
2438
2439 return 1;
2440}
2441
2442 static int
2443handle_settermprop(
2444 VTermProp prop,
2445 VTermValue *value,
2446 void *user)
2447{
2448 term_T *term = (term_T *)user;
2449
2450 switch (prop)
2451 {
2452 case VTERM_PROP_TITLE:
2453 vim_free(term->tl_title);
2454 /* a blank title isn't useful, make it empty, so that "running" is
2455 * displayed */
2456 if (*skipwhite((char_u *)value->string) == NUL)
2457 term->tl_title = NULL;
2458#ifdef WIN3264
2459 else if (!enc_utf8 && enc_codepage > 0)
2460 {
2461 WCHAR *ret = NULL;
2462 int length = 0;
2463
2464 MultiByteToWideChar_alloc(CP_UTF8, 0,
2465 (char*)value->string, (int)STRLEN(value->string),
2466 &ret, &length);
2467 if (ret != NULL)
2468 {
2469 WideCharToMultiByte_alloc(enc_codepage, 0,
2470 ret, length, (char**)&term->tl_title,
2471 &length, 0, 0);
2472 vim_free(ret);
2473 }
2474 }
2475#endif
2476 else
2477 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002478 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002479 if (term == curbuf->b_term)
2480 maketitle();
2481 break;
2482
2483 case VTERM_PROP_CURSORVISIBLE:
2484 term->tl_cursor_visible = value->boolean;
2485 may_toggle_cursor(term);
2486 out_flush();
2487 break;
2488
2489 case VTERM_PROP_CURSORBLINK:
2490 term->tl_cursor_blink = value->boolean;
2491 may_set_cursor_props(term);
2492 break;
2493
2494 case VTERM_PROP_CURSORSHAPE:
2495 term->tl_cursor_shape = value->number;
2496 may_set_cursor_props(term);
2497 break;
2498
2499 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002500 if (desired_cursor_color == term->tl_cursor_color)
2501 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002502 vim_free(term->tl_cursor_color);
2503 if (*value->string == NUL)
2504 term->tl_cursor_color = NULL;
2505 else
2506 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2507 may_set_cursor_props(term);
2508 break;
2509
2510 case VTERM_PROP_ALTSCREEN:
2511 /* TODO: do anything else? */
2512 term->tl_using_altscreen = value->boolean;
2513 break;
2514
2515 default:
2516 break;
2517 }
2518 /* Always return 1, otherwise vterm doesn't store the value internally. */
2519 return 1;
2520}
2521
2522/*
2523 * The job running in the terminal resized the terminal.
2524 */
2525 static int
2526handle_resize(int rows, int cols, void *user)
2527{
2528 term_T *term = (term_T *)user;
2529 win_T *wp;
2530
2531 term->tl_rows = rows;
2532 term->tl_cols = cols;
2533 if (term->tl_vterm_size_changed)
2534 /* Size was set by vterm_set_size(), don't set the window size. */
2535 term->tl_vterm_size_changed = FALSE;
2536 else
2537 {
2538 FOR_ALL_WINDOWS(wp)
2539 {
2540 if (wp->w_buffer == term->tl_buffer)
2541 {
2542 win_setheight_win(rows, wp);
2543 win_setwidth_win(cols, wp);
2544 }
2545 }
2546 redraw_buf_later(term->tl_buffer, NOT_VALID);
2547 }
2548 return 1;
2549}
2550
2551/*
2552 * Handle a line that is pushed off the top of the screen.
2553 */
2554 static int
2555handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2556{
2557 term_T *term = (term_T *)user;
2558
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002559 /* If the number of lines that are stored goes over 'termscrollback' then
2560 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002561 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002562 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002563 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002564 int i;
2565
2566 curbuf = term->tl_buffer;
2567 for (i = 0; i < todo; ++i)
2568 {
2569 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2570 ml_delete(1, FALSE);
2571 }
2572 curbuf = curwin->w_buffer;
2573
2574 term->tl_scrollback.ga_len -= todo;
2575 mch_memmove(term->tl_scrollback.ga_data,
2576 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2577 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2578 }
2579
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002580 if (ga_grow(&term->tl_scrollback, 1) == OK)
2581 {
2582 cellattr_T *p = NULL;
2583 int len = 0;
2584 int i;
2585 int c;
2586 int col;
2587 sb_line_T *line;
2588 garray_T ga;
2589 cellattr_T fill_attr = term->tl_default_color;
2590
2591 /* do not store empty cells at the end */
2592 for (i = 0; i < cols; ++i)
2593 if (cells[i].chars[0] != 0)
2594 len = i + 1;
2595 else
2596 cell2cellattr(&cells[i], &fill_attr);
2597
2598 ga_init2(&ga, 1, 100);
2599 if (len > 0)
2600 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2601 if (p != NULL)
2602 {
2603 for (col = 0; col < len; col += cells[col].width)
2604 {
2605 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2606 {
2607 ga.ga_len = 0;
2608 break;
2609 }
2610 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2611 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2612 (char_u *)ga.ga_data + ga.ga_len);
2613 cell2cellattr(&cells[col], &p[col]);
2614 }
2615 }
2616 if (ga_grow(&ga, 1) == FAIL)
2617 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2618 else
2619 {
2620 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2621 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2622 }
2623 ga_clear(&ga);
2624
2625 line = (sb_line_T *)term->tl_scrollback.ga_data
2626 + term->tl_scrollback.ga_len;
2627 line->sb_cols = len;
2628 line->sb_cells = p;
2629 line->sb_fill_attr = fill_attr;
2630 ++term->tl_scrollback.ga_len;
2631 ++term->tl_scrollback_scrolled;
2632 }
2633 return 0; /* ignored */
2634}
2635
2636static VTermScreenCallbacks screen_callbacks = {
2637 handle_damage, /* damage */
2638 handle_moverect, /* moverect */
2639 handle_movecursor, /* movecursor */
2640 handle_settermprop, /* settermprop */
2641 NULL, /* bell */
2642 handle_resize, /* resize */
2643 handle_pushline, /* sb_pushline */
2644 NULL /* sb_popline */
2645};
2646
2647/*
2648 * Called when a channel has been closed.
2649 * If this was a channel for a terminal window then finish it up.
2650 */
2651 void
2652term_channel_closed(channel_T *ch)
2653{
2654 term_T *term;
2655 int did_one = FALSE;
2656
2657 for (term = first_term; term != NULL; term = term->tl_next)
2658 if (term->tl_job == ch->ch_job)
2659 {
2660 term->tl_channel_closed = TRUE;
2661 did_one = TRUE;
2662
Bram Moolenaard23a8232018-02-10 18:45:26 +01002663 VIM_CLEAR(term->tl_title);
2664 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002665
2666 /* Unless in Terminal-Normal mode: clear the vterm. */
2667 if (!term->tl_normal_mode)
2668 {
2669 int fnum = term->tl_buffer->b_fnum;
2670
2671 cleanup_vterm(term);
2672
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002673 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002674 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002675 aco_save_T aco;
2676
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002677 /* ++close or term_finish == "close" */
2678 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002679 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002680 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002681 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002682 break;
2683 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002684 if (term->tl_finish == TL_FINISH_OPEN
2685 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002686 {
2687 char buf[50];
2688
2689 /* TODO: use term_opencmd */
2690 ch_log(NULL, "terminal job finished, opening window");
2691 vim_snprintf(buf, sizeof(buf),
2692 term->tl_opencmd == NULL
2693 ? "botright sbuf %d"
2694 : (char *)term->tl_opencmd, fnum);
2695 do_cmdline_cmd((char_u *)buf);
2696 }
2697 else
2698 ch_log(NULL, "terminal job finished");
2699 }
2700
2701 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2702 }
2703 if (did_one)
2704 {
2705 redraw_statuslines();
2706
2707 /* Need to break out of vgetc(). */
2708 ins_char_typebuf(K_IGNORE);
2709 typebuf_was_filled = TRUE;
2710
2711 term = curbuf->b_term;
2712 if (term != NULL)
2713 {
2714 if (term->tl_job == ch->ch_job)
2715 maketitle();
2716 update_cursor(term, term->tl_cursor_visible);
2717 }
2718 }
2719}
2720
2721/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002722 * Fill one screen line from a line of the terminal.
2723 * Advances "pos" to past the last column.
2724 */
2725 static void
2726term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2727{
2728 int off = screen_get_current_line_off();
2729
2730 for (pos->col = 0; pos->col < max_col; )
2731 {
2732 VTermScreenCell cell;
2733 int c;
2734
2735 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2736 vim_memset(&cell, 0, sizeof(cell));
2737
2738 c = cell.chars[0];
2739 if (c == NUL)
2740 {
2741 ScreenLines[off] = ' ';
2742 if (enc_utf8)
2743 ScreenLinesUC[off] = NUL;
2744 }
2745 else
2746 {
2747 if (enc_utf8)
2748 {
2749 int i;
2750
2751 /* composing chars */
2752 for (i = 0; i < Screen_mco
2753 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2754 {
2755 ScreenLinesC[i][off] = cell.chars[i + 1];
2756 if (cell.chars[i + 1] == 0)
2757 break;
2758 }
2759 if (c >= 0x80 || (Screen_mco > 0
2760 && ScreenLinesC[0][off] != 0))
2761 {
2762 ScreenLines[off] = ' ';
2763 ScreenLinesUC[off] = c;
2764 }
2765 else
2766 {
2767 ScreenLines[off] = c;
2768 ScreenLinesUC[off] = NUL;
2769 }
2770 }
2771#ifdef WIN3264
2772 else if (has_mbyte && c >= 0x80)
2773 {
2774 char_u mb[MB_MAXBYTES+1];
2775 WCHAR wc = c;
2776
2777 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2778 (char*)mb, 2, 0, 0) > 1)
2779 {
2780 ScreenLines[off] = mb[0];
2781 ScreenLines[off + 1] = mb[1];
2782 cell.width = mb_ptr2cells(mb);
2783 }
2784 else
2785 ScreenLines[off] = c;
2786 }
2787#endif
2788 else
2789 ScreenLines[off] = c;
2790 }
2791 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2792
2793 ++pos->col;
2794 ++off;
2795 if (cell.width == 2)
2796 {
2797 if (enc_utf8)
2798 ScreenLinesUC[off] = NUL;
2799
2800 /* don't set the second byte to NUL for a DBCS encoding, it
2801 * has been set above */
2802 if (enc_utf8 || !has_mbyte)
2803 ScreenLines[off] = NUL;
2804
2805 ++pos->col;
2806 ++off;
2807 }
2808 }
2809}
2810
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002811#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002812 static void
2813update_system_term(term_T *term)
2814{
2815 VTermPos pos;
2816 VTermScreen *screen;
2817
2818 if (term->tl_vterm == NULL)
2819 return;
2820 screen = vterm_obtain_screen(term->tl_vterm);
2821
2822 /* Scroll up to make more room for terminal lines if needed. */
2823 while (term->tl_toprow > 0
2824 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2825 {
2826 int save_p_more = p_more;
2827
2828 p_more = FALSE;
2829 msg_row = Rows - 1;
2830 msg_puts((char_u *)"\n");
2831 p_more = save_p_more;
2832 --term->tl_toprow;
2833 }
2834
2835 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2836 && pos.row < Rows; ++pos.row)
2837 {
2838 if (pos.row < term->tl_rows)
2839 {
2840 int max_col = MIN(Columns, term->tl_cols);
2841
2842 term_line2screenline(screen, &pos, max_col);
2843 }
2844 else
2845 pos.col = 0;
2846
2847 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2848 }
2849
2850 term->tl_dirty_row_start = MAX_ROW;
2851 term->tl_dirty_row_end = 0;
2852 update_cursor(term, TRUE);
2853}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002854#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002855
2856/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002857 * Called to update a window that contains an active terminal.
2858 * Returns FAIL when there is no terminal running in this window or in
2859 * Terminal-Normal mode.
2860 */
2861 int
2862term_update_window(win_T *wp)
2863{
2864 term_T *term = wp->w_buffer->b_term;
2865 VTerm *vterm;
2866 VTermScreen *screen;
2867 VTermState *state;
2868 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002869 int rows, cols;
2870 int newrows, newcols;
2871 int minsize;
2872 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002873
2874 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2875 return FAIL;
2876
2877 vterm = term->tl_vterm;
2878 screen = vterm_obtain_screen(vterm);
2879 state = vterm_obtain_state(vterm);
2880
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002881 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002882 {
2883 term->tl_dirty_row_start = 0;
2884 term->tl_dirty_row_end = MAX_ROW;
2885 }
2886
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002887 /*
2888 * If the window was resized a redraw will be triggered and we get here.
2889 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2890 */
Bram Moolenaar498c2562018-04-15 23:45:15 +02002891 minsize = parse_termsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002892
Bram Moolenaar498c2562018-04-15 23:45:15 +02002893 newrows = 99999;
2894 newcols = 99999;
2895 FOR_ALL_WINDOWS(twp)
2896 {
2897 /* When more than one window shows the same terminal, use the
2898 * smallest size. */
2899 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002900 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02002901 newrows = MIN(newrows, twp->w_height);
2902 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002903 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02002904 }
2905 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
2906 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
2907
2908 if (term->tl_rows != newrows || term->tl_cols != newcols)
2909 {
2910
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002911
2912 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002913 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002914 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02002915 newrows);
2916 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002917 }
2918
2919 /* The cursor may have been moved when resizing. */
2920 vterm_state_get_cursorpos(state, &pos);
2921 position_cursor(wp, &pos);
2922
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002923 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2924 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002925 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002926 if (pos.row < term->tl_rows)
2927 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002928 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002929
Bram Moolenaar13568252018-03-16 20:46:58 +01002930 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002931 }
2932 else
2933 pos.col = 0;
2934
Bram Moolenaarf118d482018-03-13 13:14:00 +01002935 screen_line(wp->w_winrow + pos.row
2936#ifdef FEAT_MENU
2937 + winbar_height(wp)
2938#endif
2939 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002940 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002941 term->tl_dirty_row_start = MAX_ROW;
2942 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002943
2944 return OK;
2945}
2946
2947/*
2948 * Return TRUE if "wp" is a terminal window where the job has finished.
2949 */
2950 int
2951term_is_finished(buf_T *buf)
2952{
2953 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2954}
2955
2956/*
2957 * Return TRUE if "wp" is a terminal window where the job has finished or we
2958 * are in Terminal-Normal mode, thus we show the buffer contents.
2959 */
2960 int
2961term_show_buffer(buf_T *buf)
2962{
2963 term_T *term = buf->b_term;
2964
2965 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2966}
2967
2968/*
2969 * The current buffer is going to be changed. If there is terminal
2970 * highlighting remove it now.
2971 */
2972 void
2973term_change_in_curbuf(void)
2974{
2975 term_T *term = curbuf->b_term;
2976
2977 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2978 {
2979 free_scrollback(term);
2980 redraw_buf_later(term->tl_buffer, NOT_VALID);
2981
2982 /* The buffer is now like a normal buffer, it cannot be easily
2983 * abandoned when changed. */
2984 set_string_option_direct((char_u *)"buftype", -1,
2985 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2986 }
2987}
2988
2989/*
2990 * Get the screen attribute for a position in the buffer.
2991 * Use a negative "col" to get the filler background color.
2992 */
2993 int
2994term_get_attr(buf_T *buf, linenr_T lnum, int col)
2995{
2996 term_T *term = buf->b_term;
2997 sb_line_T *line;
2998 cellattr_T *cellattr;
2999
3000 if (lnum > term->tl_scrollback.ga_len)
3001 cellattr = &term->tl_default_color;
3002 else
3003 {
3004 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3005 if (col < 0 || col >= line->sb_cols)
3006 cellattr = &line->sb_fill_attr;
3007 else
3008 cellattr = line->sb_cells + col;
3009 }
3010 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3011}
3012
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003013/*
3014 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003015 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003016 */
3017 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003018cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003019{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003020 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003021}
3022
3023/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003024 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003025 */
3026 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003027init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003028{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003029 VTermColor *fg, *bg;
3030 int fgval, bgval;
3031 int id;
3032
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003033 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3034 term->tl_default_color.width = 1;
3035 fg = &term->tl_default_color.fg;
3036 bg = &term->tl_default_color.bg;
3037
3038 /* Vterm uses a default black background. Set it to white when
3039 * 'background' is "light". */
3040 if (*p_bg == 'l')
3041 {
3042 fgval = 0;
3043 bgval = 255;
3044 }
3045 else
3046 {
3047 fgval = 255;
3048 bgval = 0;
3049 }
3050 fg->red = fg->green = fg->blue = fgval;
3051 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003052 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003053
3054 /* The "Terminal" highlight group overrules the defaults. */
3055 id = syn_name2id((char_u *)"Terminal");
3056
Bram Moolenaar46359e12017-11-29 22:33:38 +01003057 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003058#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3059 if (0
3060# ifdef FEAT_GUI
3061 || gui.in_use
3062# endif
3063# ifdef FEAT_TERMGUICOLORS
3064 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003065# ifdef FEAT_VTP
3066 /* Finally get INVALCOLOR on this execution path */
3067 || (!p_tgc && t_colors >= 256)
3068# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003069# endif
3070 )
3071 {
3072 guicolor_T fg_rgb = INVALCOLOR;
3073 guicolor_T bg_rgb = INVALCOLOR;
3074
3075 if (id != 0)
3076 syn_id2colors(id, &fg_rgb, &bg_rgb);
3077
3078# ifdef FEAT_GUI
3079 if (gui.in_use)
3080 {
3081 if (fg_rgb == INVALCOLOR)
3082 fg_rgb = gui.norm_pixel;
3083 if (bg_rgb == INVALCOLOR)
3084 bg_rgb = gui.back_pixel;
3085 }
3086# ifdef FEAT_TERMGUICOLORS
3087 else
3088# endif
3089# endif
3090# ifdef FEAT_TERMGUICOLORS
3091 {
3092 if (fg_rgb == INVALCOLOR)
3093 fg_rgb = cterm_normal_fg_gui_color;
3094 if (bg_rgb == INVALCOLOR)
3095 bg_rgb = cterm_normal_bg_gui_color;
3096 }
3097# endif
3098 if (fg_rgb != INVALCOLOR)
3099 {
3100 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3101
3102 fg->red = (unsigned)(rgb >> 16);
3103 fg->green = (unsigned)(rgb >> 8) & 255;
3104 fg->blue = (unsigned)rgb & 255;
3105 }
3106 if (bg_rgb != INVALCOLOR)
3107 {
3108 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3109
3110 bg->red = (unsigned)(rgb >> 16);
3111 bg->green = (unsigned)(rgb >> 8) & 255;
3112 bg->blue = (unsigned)rgb & 255;
3113 }
3114 }
3115 else
3116#endif
3117 if (id != 0 && t_colors >= 16)
3118 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003119 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003120 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003121 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003122 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003123 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003124 else
3125 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003126#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003127 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003128#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003129
3130 /* In an MS-Windows console we know the normal colors. */
3131 if (cterm_normal_fg_color > 0)
3132 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003133 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003134# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003135 tmp = fg->red;
3136 fg->red = fg->blue;
3137 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003138# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003139 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003140# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003141 else
3142 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003143# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003144
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003145 if (cterm_normal_bg_color > 0)
3146 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003147 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003148# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003149 tmp = bg->red;
3150 bg->red = bg->blue;
3151 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003152# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003153 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003154# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003155 else
3156 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003157# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003158 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003159}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003160
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003161#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3162/*
3163 * Set the 16 ANSI colors from array of RGB values
3164 */
3165 static void
3166set_vterm_palette(VTerm *vterm, long_u *rgb)
3167{
3168 int index = 0;
3169 VTermState *state = vterm_obtain_state(vterm);
3170 for (; index < 16; index++)
3171 {
3172 VTermColor color;
3173 color.red = (unsigned)(rgb[index] >> 16);
3174 color.green = (unsigned)(rgb[index] >> 8) & 255;
3175 color.blue = (unsigned)rgb[index] & 255;
3176 vterm_state_set_palette_color(state, index, &color);
3177 }
3178}
3179
3180/*
3181 * Set the ANSI color palette from a list of colors
3182 */
3183 static int
3184set_ansi_colors_list(VTerm *vterm, list_T *list)
3185{
3186 int n = 0;
3187 long_u rgb[16];
3188 listitem_T *li = list->lv_first;
3189
3190 for (; li != NULL && n < 16; li = li->li_next, n++)
3191 {
3192 char_u *color_name;
3193 guicolor_T guicolor;
3194
3195 color_name = get_tv_string_chk(&li->li_tv);
3196 if (color_name == NULL)
3197 return FAIL;
3198
3199 guicolor = GUI_GET_COLOR(color_name);
3200 if (guicolor == INVALCOLOR)
3201 return FAIL;
3202
3203 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3204 }
3205
3206 if (n != 16 || li != NULL)
3207 return FAIL;
3208
3209 set_vterm_palette(vterm, rgb);
3210
3211 return OK;
3212}
3213
3214/*
3215 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3216 */
3217 static void
3218init_vterm_ansi_colors(VTerm *vterm)
3219{
3220 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3221
3222 if (var != NULL
3223 && (var->di_tv.v_type != VAR_LIST
3224 || var->di_tv.vval.v_list == NULL
3225 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3226 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3227}
3228#endif
3229
Bram Moolenaar52acb112018-03-18 19:20:22 +01003230/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003231 * Handles a "drop" command from the job in the terminal.
3232 * "item" is the file name, "item->li_next" may have options.
3233 */
3234 static void
3235handle_drop_command(listitem_T *item)
3236{
3237 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003238 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003239 int bufnr;
3240 win_T *wp;
3241 tabpage_T *tp;
3242 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003243 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003244
3245 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3246 FOR_ALL_TAB_WINDOWS(tp, wp)
3247 {
3248 if (wp->w_buffer->b_fnum == bufnr)
3249 {
3250 /* buffer is in a window already, go there */
3251 goto_tabpage_win(tp, wp);
3252 return;
3253 }
3254 }
3255
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003256 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003257
3258 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3259 && opt_item->li_tv.vval.v_dict != NULL)
3260 {
3261 dict_T *dict = opt_item->li_tv.vval.v_dict;
3262 char_u *p;
3263
3264 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3265 if (p == NULL)
3266 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3267 if (p != NULL)
3268 {
3269 if (check_ff_value(p) == FAIL)
3270 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3271 else
3272 ea.force_ff = *p;
3273 }
3274 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3275 if (p == NULL)
3276 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3277 if (p != NULL)
3278 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003279 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003280 if (ea.cmd != NULL)
3281 {
3282 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3283 ea.force_enc = 11;
3284 tofree = ea.cmd;
3285 }
3286 }
3287
3288 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3289 if (p != NULL)
3290 get_bad_opt(p, &ea);
3291
3292 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3293 ea.force_bin = FORCE_BIN;
3294 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3295 ea.force_bin = FORCE_BIN;
3296 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3297 ea.force_bin = FORCE_NOBIN;
3298 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3299 ea.force_bin = FORCE_NOBIN;
3300 }
3301
3302 /* open in new window, like ":split fname" */
3303 if (ea.cmd == NULL)
3304 ea.cmd = (char_u *)"split";
3305 ea.arg = fname;
3306 ea.cmdidx = CMD_split;
3307 ex_splitview(&ea);
3308
3309 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003310}
3311
3312/*
3313 * Handles a function call from the job running in a terminal.
3314 * "item" is the function name, "item->li_next" has the arguments.
3315 */
3316 static void
3317handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3318{
3319 char_u *func;
3320 typval_T argvars[2];
3321 typval_T rettv;
3322 int doesrange;
3323
3324 if (item->li_next == NULL)
3325 {
3326 ch_log(channel, "Missing function arguments for call");
3327 return;
3328 }
3329 func = get_tv_string(&item->li_tv);
3330
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003331 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003332 {
3333 ch_log(channel, "Invalid function name: %s", func);
3334 return;
3335 }
3336
3337 argvars[0].v_type = VAR_NUMBER;
3338 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3339 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003340 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003341 2, argvars, /* argv_func */ NULL,
3342 /* firstline */ 1, /* lastline */ 1,
3343 &doesrange, /* evaluate */ TRUE,
3344 /* partial */ NULL, /* selfdict */ NULL) == OK)
3345 {
3346 clear_tv(&rettv);
3347 ch_log(channel, "Function %s called", func);
3348 }
3349 else
3350 ch_log(channel, "Calling function %s failed", func);
3351}
3352
3353/*
3354 * Called by libvterm when it cannot recognize an OSC sequence.
3355 * We recognize a terminal API command.
3356 */
3357 static int
3358parse_osc(const char *command, size_t cmdlen, void *user)
3359{
3360 term_T *term = (term_T *)user;
3361 js_read_T reader;
3362 typval_T tv;
3363 channel_T *channel = term->tl_job == NULL ? NULL
3364 : term->tl_job->jv_channel;
3365
3366 /* We recognize only OSC 5 1 ; {command} */
3367 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3368 return 0; /* not handled */
3369
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003370 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003371 if (reader.js_buf == NULL)
3372 return 1;
3373 reader.js_fill = NULL;
3374 reader.js_used = 0;
3375 if (json_decode(&reader, &tv, 0) == OK
3376 && tv.v_type == VAR_LIST
3377 && tv.vval.v_list != NULL)
3378 {
3379 listitem_T *item = tv.vval.v_list->lv_first;
3380
3381 if (item == NULL)
3382 ch_log(channel, "Missing command");
3383 else
3384 {
3385 char_u *cmd = get_tv_string(&item->li_tv);
3386
Bram Moolenaara997b452018-04-17 23:24:06 +02003387 /* Make sure an invoked command doesn't delete the buffer (and the
3388 * terminal) under our fingers. */
3389 ++term->tl_buffer->b_locked;
3390
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003391 item = item->li_next;
3392 if (item == NULL)
3393 ch_log(channel, "Missing argument for %s", cmd);
3394 else if (STRCMP(cmd, "drop") == 0)
3395 handle_drop_command(item);
3396 else if (STRCMP(cmd, "call") == 0)
3397 handle_call_command(term, channel, item);
3398 else
3399 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003400 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003401 }
3402 }
3403 else
3404 ch_log(channel, "Invalid JSON received");
3405
3406 vim_free(reader.js_buf);
3407 clear_tv(&tv);
3408 return 1;
3409}
3410
3411static VTermParserCallbacks parser_fallbacks = {
3412 NULL, /* text */
3413 NULL, /* control */
3414 NULL, /* escape */
3415 NULL, /* csi */
3416 parse_osc, /* osc */
3417 NULL, /* dcs */
3418 NULL /* resize */
3419};
3420
3421/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003422 * Use Vim's allocation functions for vterm so profiling works.
3423 */
3424 static void *
3425vterm_malloc(size_t size, void *data UNUSED)
3426{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003427 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003428}
3429
3430 static void
3431vterm_memfree(void *ptr, void *data UNUSED)
3432{
3433 vim_free(ptr);
3434}
3435
3436static VTermAllocatorFunctions vterm_allocator = {
3437 &vterm_malloc,
3438 &vterm_memfree
3439};
3440
3441/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003442 * Create a new vterm and initialize it.
3443 */
3444 static void
3445create_vterm(term_T *term, int rows, int cols)
3446{
3447 VTerm *vterm;
3448 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003449 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003450 VTermValue value;
3451
Bram Moolenaar756ef112018-04-10 12:04:27 +02003452 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003453 term->tl_vterm = vterm;
3454 screen = vterm_obtain_screen(vterm);
3455 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3456 /* TODO: depends on 'encoding'. */
3457 vterm_set_utf8(vterm, 1);
3458
3459 init_default_colors(term);
3460
3461 vterm_state_set_default_colors(
3462 vterm_obtain_state(vterm),
3463 &term->tl_default_color.fg,
3464 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003465
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003466 if (t_colors >= 16)
3467 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3468
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003469 /* Required to initialize most things. */
3470 vterm_screen_reset(screen, 1 /* hard */);
3471
3472 /* Allow using alternate screen. */
3473 vterm_screen_enable_altscreen(screen, 1);
3474
3475 /* For unix do not use a blinking cursor. In an xterm this causes the
3476 * cursor to blink if it's blinking in the xterm.
3477 * For Windows we respect the system wide setting. */
3478#ifdef WIN3264
3479 if (GetCaretBlinkTime() == INFINITE)
3480 value.boolean = 0;
3481 else
3482 value.boolean = 1;
3483#else
3484 value.boolean = 0;
3485#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003486 state = vterm_obtain_state(vterm);
3487 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3488 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003489}
3490
3491/*
3492 * Return the text to show for the buffer name and status.
3493 */
3494 char_u *
3495term_get_status_text(term_T *term)
3496{
3497 if (term->tl_status_text == NULL)
3498 {
3499 char_u *txt;
3500 size_t len;
3501
3502 if (term->tl_normal_mode)
3503 {
3504 if (term_job_running(term))
3505 txt = (char_u *)_("Terminal");
3506 else
3507 txt = (char_u *)_("Terminal-finished");
3508 }
3509 else if (term->tl_title != NULL)
3510 txt = term->tl_title;
3511 else if (term_none_open(term))
3512 txt = (char_u *)_("active");
3513 else if (term_job_running(term))
3514 txt = (char_u *)_("running");
3515 else
3516 txt = (char_u *)_("finished");
3517 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3518 term->tl_status_text = alloc((int)len);
3519 if (term->tl_status_text != NULL)
3520 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3521 term->tl_buffer->b_fname, txt);
3522 }
3523 return term->tl_status_text;
3524}
3525
3526/*
3527 * Mark references in jobs of terminals.
3528 */
3529 int
3530set_ref_in_term(int copyID)
3531{
3532 int abort = FALSE;
3533 term_T *term;
3534 typval_T tv;
3535
3536 for (term = first_term; term != NULL; term = term->tl_next)
3537 if (term->tl_job != NULL)
3538 {
3539 tv.v_type = VAR_JOB;
3540 tv.vval.v_job = term->tl_job;
3541 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3542 }
3543 return abort;
3544}
3545
3546/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003547 * Cache "Terminal" highlight group colors.
3548 */
3549 void
3550set_terminal_default_colors(int cterm_fg, int cterm_bg)
3551{
3552 term_default_cterm_fg = cterm_fg - 1;
3553 term_default_cterm_bg = cterm_bg - 1;
3554}
3555
3556/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003557 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003558 * Returns NULL when the buffer is not for a terminal window and logs a message
3559 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003560 */
3561 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003562term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003563{
3564 buf_T *buf;
3565
3566 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3567 ++emsg_off;
3568 buf = get_buf_tv(&argvars[0], FALSE);
3569 --emsg_off;
3570 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003571 {
3572 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003573 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003574 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003575 return buf;
3576}
3577
Bram Moolenaard96ff162018-02-18 22:13:29 +01003578 static int
3579same_color(VTermColor *a, VTermColor *b)
3580{
3581 return a->red == b->red
3582 && a->green == b->green
3583 && a->blue == b->blue
3584 && a->ansi_index == b->ansi_index;
3585}
3586
3587 static void
3588dump_term_color(FILE *fd, VTermColor *color)
3589{
3590 fprintf(fd, "%02x%02x%02x%d",
3591 (int)color->red, (int)color->green, (int)color->blue,
3592 (int)color->ansi_index);
3593}
3594
3595/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003596 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003597 *
3598 * Each screen cell in full is:
3599 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3600 * {characters} is a space for an empty cell
3601 * For a double-width character "+" is changed to "*" and the next cell is
3602 * skipped.
3603 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3604 * when "&" use the same as the previous cell.
3605 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3606 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3607 * {color-idx} is a number from 0 to 255
3608 *
3609 * Screen cell with same width, attributes and color as the previous one:
3610 * |{characters}
3611 *
3612 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3613 *
3614 * Repeating the previous screen cell:
3615 * @{count}
3616 */
3617 void
3618f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3619{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003620 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003621 term_T *term;
3622 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003623 int max_height = 0;
3624 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003625 stat_T st;
3626 FILE *fd;
3627 VTermPos pos;
3628 VTermScreen *screen;
3629 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003630 VTermState *state;
3631 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003632
3633 if (check_restricted() || check_secure())
3634 return;
3635 if (buf == NULL)
3636 return;
3637 term = buf->b_term;
3638
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003639 if (argvars[2].v_type != VAR_UNKNOWN)
3640 {
3641 dict_T *d;
3642
3643 if (argvars[2].v_type != VAR_DICT)
3644 {
3645 EMSG(_(e_dictreq));
3646 return;
3647 }
3648 d = argvars[2].vval.v_dict;
3649 if (d != NULL)
3650 {
3651 max_height = get_dict_number(d, (char_u *)"rows");
3652 max_width = get_dict_number(d, (char_u *)"columns");
3653 }
3654 }
3655
Bram Moolenaard96ff162018-02-18 22:13:29 +01003656 fname = get_tv_string_chk(&argvars[1]);
3657 if (fname == NULL)
3658 return;
3659 if (mch_stat((char *)fname, &st) >= 0)
3660 {
3661 EMSG2(_("E953: File exists: %s"), fname);
3662 return;
3663 }
3664
Bram Moolenaard96ff162018-02-18 22:13:29 +01003665 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3666 {
3667 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3668 return;
3669 }
3670
3671 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3672
3673 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003674 state = vterm_obtain_state(term->tl_vterm);
3675 vterm_state_get_cursorpos(state, &cursor_pos);
3676
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003677 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3678 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003679 {
3680 int repeat = 0;
3681
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003682 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3683 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003684 {
3685 VTermScreenCell cell;
3686 int same_attr;
3687 int same_chars = TRUE;
3688 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003689 int is_cursor_pos = (pos.col == cursor_pos.col
3690 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003691
3692 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3693 vim_memset(&cell, 0, sizeof(cell));
3694
3695 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3696 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003697 int c = cell.chars[i];
3698 int pc = prev_cell.chars[i];
3699
3700 /* For the first character NUL is the same as space. */
3701 if (i == 0)
3702 {
3703 c = (c == NUL) ? ' ' : c;
3704 pc = (pc == NUL) ? ' ' : pc;
3705 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003706 if (cell.chars[i] != prev_cell.chars[i])
3707 same_chars = FALSE;
3708 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3709 break;
3710 }
3711 same_attr = vtermAttr2hl(cell.attrs)
3712 == vtermAttr2hl(prev_cell.attrs)
3713 && same_color(&cell.fg, &prev_cell.fg)
3714 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003715 if (same_chars && cell.width == prev_cell.width && same_attr
3716 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003717 {
3718 ++repeat;
3719 }
3720 else
3721 {
3722 if (repeat > 0)
3723 {
3724 fprintf(fd, "@%d", repeat);
3725 repeat = 0;
3726 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003727 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003728
3729 if (cell.chars[0] == NUL)
3730 fputs(" ", fd);
3731 else
3732 {
3733 char_u charbuf[10];
3734 int len;
3735
3736 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3737 && cell.chars[i] != NUL; ++i)
3738 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003739 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003740 fwrite(charbuf, len, 1, fd);
3741 }
3742 }
3743
3744 /* When only the characters differ we don't write anything, the
3745 * following "|", "@" or NL will indicate using the same
3746 * attributes. */
3747 if (cell.width != prev_cell.width || !same_attr)
3748 {
3749 if (cell.width == 2)
3750 {
3751 fputs("*", fd);
3752 ++pos.col;
3753 }
3754 else
3755 fputs("+", fd);
3756
3757 if (same_attr)
3758 {
3759 fputs("&", fd);
3760 }
3761 else
3762 {
3763 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3764 if (same_color(&cell.fg, &prev_cell.fg))
3765 fputs("&", fd);
3766 else
3767 {
3768 fputs("#", fd);
3769 dump_term_color(fd, &cell.fg);
3770 }
3771 if (same_color(&cell.bg, &prev_cell.bg))
3772 fputs("&", fd);
3773 else
3774 {
3775 fputs("#", fd);
3776 dump_term_color(fd, &cell.bg);
3777 }
3778 }
3779 }
3780
3781 prev_cell = cell;
3782 }
3783 }
3784 if (repeat > 0)
3785 fprintf(fd, "@%d", repeat);
3786 fputs("\n", fd);
3787 }
3788
3789 fclose(fd);
3790}
3791
3792/*
3793 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3794 */
3795 static void
3796dump_is_corrupt(garray_T *gap)
3797{
3798 ga_concat(gap, (char_u *)"CORRUPT");
3799}
3800
3801 static void
3802append_cell(garray_T *gap, cellattr_T *cell)
3803{
3804 if (ga_grow(gap, 1) == OK)
3805 {
3806 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3807 ++gap->ga_len;
3808 }
3809}
3810
3811/*
3812 * Read the dump file from "fd" and append lines to the current buffer.
3813 * Return the cell width of the longest line.
3814 */
3815 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003816read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003817{
3818 int c;
3819 garray_T ga_text;
3820 garray_T ga_cell;
3821 char_u *prev_char = NULL;
3822 int attr = 0;
3823 cellattr_T cell;
3824 term_T *term = curbuf->b_term;
3825 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003826 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003827
3828 ga_init2(&ga_text, 1, 90);
3829 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3830 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003831 cursor_pos->row = -1;
3832 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003833
3834 c = fgetc(fd);
3835 for (;;)
3836 {
3837 if (c == EOF)
3838 break;
3839 if (c == '\n')
3840 {
3841 /* End of a line: append it to the buffer. */
3842 if (ga_text.ga_data == NULL)
3843 dump_is_corrupt(&ga_text);
3844 if (ga_grow(&term->tl_scrollback, 1) == OK)
3845 {
3846 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3847 + term->tl_scrollback.ga_len;
3848
3849 if (max_cells < ga_cell.ga_len)
3850 max_cells = ga_cell.ga_len;
3851 line->sb_cols = ga_cell.ga_len;
3852 line->sb_cells = ga_cell.ga_data;
3853 line->sb_fill_attr = term->tl_default_color;
3854 ++term->tl_scrollback.ga_len;
3855 ga_init(&ga_cell);
3856
3857 ga_append(&ga_text, NUL);
3858 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3859 ga_text.ga_len, FALSE);
3860 }
3861 else
3862 ga_clear(&ga_cell);
3863 ga_text.ga_len = 0;
3864
3865 c = fgetc(fd);
3866 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003867 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003868 {
3869 int prev_len = ga_text.ga_len;
3870
Bram Moolenaar9271d052018-02-25 21:39:46 +01003871 if (c == '>')
3872 {
3873 if (cursor_pos->row != -1)
3874 dump_is_corrupt(&ga_text); /* duplicate cursor */
3875 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3876 cursor_pos->col = ga_cell.ga_len;
3877 }
3878
Bram Moolenaard96ff162018-02-18 22:13:29 +01003879 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3880 c = fgetc(fd);
3881 if (c != EOF)
3882 ga_append(&ga_text, c);
3883 for (;;)
3884 {
3885 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003886 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003887 || c == EOF || c == '\n')
3888 break;
3889 ga_append(&ga_text, c);
3890 }
3891
3892 /* save the character for repeating it */
3893 vim_free(prev_char);
3894 if (ga_text.ga_data != NULL)
3895 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3896 ga_text.ga_len - prev_len);
3897
Bram Moolenaar9271d052018-02-25 21:39:46 +01003898 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003899 {
3900 /* use all attributes from previous cell */
3901 }
3902 else if (c == '+' || c == '*')
3903 {
3904 int is_bg;
3905
3906 cell.width = c == '+' ? 1 : 2;
3907
3908 c = fgetc(fd);
3909 if (c == '&')
3910 {
3911 /* use same attr as previous cell */
3912 c = fgetc(fd);
3913 }
3914 else if (isdigit(c))
3915 {
3916 /* get the decimal attribute */
3917 attr = 0;
3918 while (isdigit(c))
3919 {
3920 attr = attr * 10 + (c - '0');
3921 c = fgetc(fd);
3922 }
3923 hl2vtermAttr(attr, &cell);
3924 }
3925 else
3926 dump_is_corrupt(&ga_text);
3927
3928 /* is_bg == 0: fg, is_bg == 1: bg */
3929 for (is_bg = 0; is_bg <= 1; ++is_bg)
3930 {
3931 if (c == '&')
3932 {
3933 /* use same color as previous cell */
3934 c = fgetc(fd);
3935 }
3936 else if (c == '#')
3937 {
3938 int red, green, blue, index = 0;
3939
3940 c = fgetc(fd);
3941 red = hex2nr(c);
3942 c = fgetc(fd);
3943 red = (red << 4) + hex2nr(c);
3944 c = fgetc(fd);
3945 green = hex2nr(c);
3946 c = fgetc(fd);
3947 green = (green << 4) + hex2nr(c);
3948 c = fgetc(fd);
3949 blue = hex2nr(c);
3950 c = fgetc(fd);
3951 blue = (blue << 4) + hex2nr(c);
3952 c = fgetc(fd);
3953 if (!isdigit(c))
3954 dump_is_corrupt(&ga_text);
3955 while (isdigit(c))
3956 {
3957 index = index * 10 + (c - '0');
3958 c = fgetc(fd);
3959 }
3960
3961 if (is_bg)
3962 {
3963 cell.bg.red = red;
3964 cell.bg.green = green;
3965 cell.bg.blue = blue;
3966 cell.bg.ansi_index = index;
3967 }
3968 else
3969 {
3970 cell.fg.red = red;
3971 cell.fg.green = green;
3972 cell.fg.blue = blue;
3973 cell.fg.ansi_index = index;
3974 }
3975 }
3976 else
3977 dump_is_corrupt(&ga_text);
3978 }
3979 }
3980 else
3981 dump_is_corrupt(&ga_text);
3982
3983 append_cell(&ga_cell, &cell);
3984 }
3985 else if (c == '@')
3986 {
3987 if (prev_char == NULL)
3988 dump_is_corrupt(&ga_text);
3989 else
3990 {
3991 int count = 0;
3992
3993 /* repeat previous character, get the count */
3994 for (;;)
3995 {
3996 c = fgetc(fd);
3997 if (!isdigit(c))
3998 break;
3999 count = count * 10 + (c - '0');
4000 }
4001
4002 while (count-- > 0)
4003 {
4004 ga_concat(&ga_text, prev_char);
4005 append_cell(&ga_cell, &cell);
4006 }
4007 }
4008 }
4009 else
4010 {
4011 dump_is_corrupt(&ga_text);
4012 c = fgetc(fd);
4013 }
4014 }
4015
4016 if (ga_text.ga_len > 0)
4017 {
4018 /* trailing characters after last NL */
4019 dump_is_corrupt(&ga_text);
4020 ga_append(&ga_text, NUL);
4021 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4022 ga_text.ga_len, FALSE);
4023 }
4024
4025 ga_clear(&ga_text);
4026 vim_free(prev_char);
4027
4028 return max_cells;
4029}
4030
4031/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004032 * Return an allocated string with at least "text_width" "=" characters and
4033 * "fname" inserted in the middle.
4034 */
4035 static char_u *
4036get_separator(int text_width, char_u *fname)
4037{
4038 int width = MAX(text_width, curwin->w_width);
4039 char_u *textline;
4040 int fname_size;
4041 char_u *p = fname;
4042 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004043 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004044
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004045 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004046 if (textline == NULL)
4047 return NULL;
4048
4049 fname_size = vim_strsize(fname);
4050 if (fname_size < width - 8)
4051 {
4052 /* enough room, don't use the full window width */
4053 width = MAX(text_width, fname_size + 8);
4054 }
4055 else if (fname_size > width - 8)
4056 {
4057 /* full name doesn't fit, use only the tail */
4058 p = gettail(fname);
4059 fname_size = vim_strsize(p);
4060 }
4061 /* skip characters until the name fits */
4062 while (fname_size > width - 8)
4063 {
4064 p += (*mb_ptr2len)(p);
4065 fname_size = vim_strsize(p);
4066 }
4067
4068 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4069 textline[i] = '=';
4070 textline[i++] = ' ';
4071
4072 STRCPY(textline + i, p);
4073 off = STRLEN(textline);
4074 textline[off] = ' ';
4075 for (i = 1; i < (width - fname_size) / 2; ++i)
4076 textline[off + i] = '=';
4077 textline[off + i] = NUL;
4078
4079 return textline;
4080}
4081
4082/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004083 * Common for "term_dumpdiff()" and "term_dumpload()".
4084 */
4085 static void
4086term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4087{
4088 jobopt_T opt;
4089 buf_T *buf;
4090 char_u buf1[NUMBUFLEN];
4091 char_u buf2[NUMBUFLEN];
4092 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004093 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004094 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004095 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004096 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004097 char_u *textline = NULL;
4098
4099 /* First open the files. If this fails bail out. */
4100 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4101 if (do_diff)
4102 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4103 if (fname1 == NULL || (do_diff && fname2 == NULL))
4104 {
4105 EMSG(_(e_invarg));
4106 return;
4107 }
4108 fd1 = mch_fopen((char *)fname1, READBIN);
4109 if (fd1 == NULL)
4110 {
4111 EMSG2(_(e_notread), fname1);
4112 return;
4113 }
4114 if (do_diff)
4115 {
4116 fd2 = mch_fopen((char *)fname2, READBIN);
4117 if (fd2 == NULL)
4118 {
4119 fclose(fd1);
4120 EMSG2(_(e_notread), fname2);
4121 return;
4122 }
4123 }
4124
4125 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004126 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4127 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4128 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4129 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4130 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004131
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004132 if (opt.jo_term_name == NULL)
4133 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004134 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004135
Bram Moolenaarb571c632018-03-21 22:27:59 +01004136 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004137 if (fname_tofree != NULL)
4138 {
4139 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4140 opt.jo_term_name = fname_tofree;
4141 }
4142 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004143
Bram Moolenaar13568252018-03-16 20:46:58 +01004144 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004145 if (buf != NULL && buf->b_term != NULL)
4146 {
4147 int i;
4148 linenr_T bot_lnum;
4149 linenr_T lnum;
4150 term_T *term = buf->b_term;
4151 int width;
4152 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004153 VTermPos cursor_pos1;
4154 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004155
Bram Moolenaar52acb112018-03-18 19:20:22 +01004156 init_default_colors(term);
4157
Bram Moolenaard96ff162018-02-18 22:13:29 +01004158 rettv->vval.v_number = buf->b_fnum;
4159
4160 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004161 width = read_dump_file(fd1, &cursor_pos1);
4162
4163 /* position the cursor */
4164 if (cursor_pos1.row >= 0)
4165 {
4166 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4167 coladvance(cursor_pos1.col);
4168 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004169
4170 /* Delete the empty line that was in the empty buffer. */
4171 ml_delete(1, FALSE);
4172
4173 /* For term_dumpload() we are done here. */
4174 if (!do_diff)
4175 goto theend;
4176
4177 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4178
Bram Moolenaar4a696342018-04-05 18:45:26 +02004179 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004180 if (textline == NULL)
4181 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004182 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4183 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4184 vim_free(textline);
4185
4186 textline = get_separator(width, fname2);
4187 if (textline == NULL)
4188 goto theend;
4189 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4190 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004191 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004192
4193 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004194 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004195 if (width2 > width)
4196 {
4197 vim_free(textline);
4198 textline = alloc(width2 + 1);
4199 if (textline == NULL)
4200 goto theend;
4201 width = width2;
4202 textline[width] = NUL;
4203 }
4204 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4205
4206 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4207 {
4208 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4209 {
4210 /* bottom part has fewer rows, fill with "-" */
4211 for (i = 0; i < width; ++i)
4212 textline[i] = '-';
4213 }
4214 else
4215 {
4216 char_u *line1;
4217 char_u *line2;
4218 char_u *p1;
4219 char_u *p2;
4220 int col;
4221 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4222 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4223 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4224 ->sb_cells;
4225
4226 /* Make a copy, getting the second line will invalidate it. */
4227 line1 = vim_strsave(ml_get(lnum));
4228 if (line1 == NULL)
4229 break;
4230 p1 = line1;
4231
4232 line2 = ml_get(lnum + bot_lnum);
4233 p2 = line2;
4234 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4235 {
4236 int len1 = utfc_ptr2len(p1);
4237 int len2 = utfc_ptr2len(p2);
4238
4239 textline[col] = ' ';
4240 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004241 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004242 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004243 else if (lnum == cursor_pos1.row + 1
4244 && col == cursor_pos1.col
4245 && (cursor_pos1.row != cursor_pos2.row
4246 || cursor_pos1.col != cursor_pos2.col))
4247 /* cursor in first but not in second */
4248 textline[col] = '>';
4249 else if (lnum == cursor_pos2.row + 1
4250 && col == cursor_pos2.col
4251 && (cursor_pos1.row != cursor_pos2.row
4252 || cursor_pos1.col != cursor_pos2.col))
4253 /* cursor in second but not in first */
4254 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004255 else if (cellattr1 != NULL && cellattr2 != NULL)
4256 {
4257 if ((cellattr1 + col)->width
4258 != (cellattr2 + col)->width)
4259 textline[col] = 'w';
4260 else if (!same_color(&(cellattr1 + col)->fg,
4261 &(cellattr2 + col)->fg))
4262 textline[col] = 'f';
4263 else if (!same_color(&(cellattr1 + col)->bg,
4264 &(cellattr2 + col)->bg))
4265 textline[col] = 'b';
4266 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4267 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4268 textline[col] = 'a';
4269 }
4270 p1 += len1;
4271 p2 += len2;
4272 /* TODO: handle different width */
4273 }
4274 vim_free(line1);
4275
4276 while (col < width)
4277 {
4278 if (*p1 == NUL && *p2 == NUL)
4279 textline[col] = '?';
4280 else if (*p1 == NUL)
4281 {
4282 textline[col] = '+';
4283 p2 += utfc_ptr2len(p2);
4284 }
4285 else
4286 {
4287 textline[col] = '-';
4288 p1 += utfc_ptr2len(p1);
4289 }
4290 ++col;
4291 }
4292 }
4293 if (add_empty_scrollback(term, &term->tl_default_color,
4294 term->tl_top_diff_rows) == OK)
4295 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4296 ++bot_lnum;
4297 }
4298
4299 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4300 {
4301 /* bottom part has more rows, fill with "+" */
4302 for (i = 0; i < width; ++i)
4303 textline[i] = '+';
4304 if (add_empty_scrollback(term, &term->tl_default_color,
4305 term->tl_top_diff_rows) == OK)
4306 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4307 ++lnum;
4308 ++bot_lnum;
4309 }
4310
4311 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004312
4313 /* looks better without wrapping */
4314 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004315 }
4316
4317theend:
4318 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004319 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004320 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004321 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004322 fclose(fd2);
4323}
4324
4325/*
4326 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4327 * bottom files.
4328 * Return FAIL when this is not possible.
4329 */
4330 int
4331term_swap_diff()
4332{
4333 term_T *term = curbuf->b_term;
4334 linenr_T line_count;
4335 linenr_T top_rows;
4336 linenr_T bot_rows;
4337 linenr_T bot_start;
4338 linenr_T lnum;
4339 char_u *p;
4340 sb_line_T *sb_line;
4341
4342 if (term == NULL
4343 || !term_is_finished(curbuf)
4344 || term->tl_top_diff_rows == 0
4345 || term->tl_scrollback.ga_len == 0)
4346 return FAIL;
4347
4348 line_count = curbuf->b_ml.ml_line_count;
4349 top_rows = term->tl_top_diff_rows;
4350 bot_rows = term->tl_bot_diff_rows;
4351 bot_start = line_count - bot_rows;
4352 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4353
4354 /* move lines from top to above the bottom part */
4355 for (lnum = 1; lnum <= top_rows; ++lnum)
4356 {
4357 p = vim_strsave(ml_get(1));
4358 if (p == NULL)
4359 return OK;
4360 ml_append(bot_start, p, 0, FALSE);
4361 ml_delete(1, FALSE);
4362 vim_free(p);
4363 }
4364
4365 /* move lines from bottom to the top */
4366 for (lnum = 1; lnum <= bot_rows; ++lnum)
4367 {
4368 p = vim_strsave(ml_get(bot_start + lnum));
4369 if (p == NULL)
4370 return OK;
4371 ml_delete(bot_start + lnum, FALSE);
4372 ml_append(lnum - 1, p, 0, FALSE);
4373 vim_free(p);
4374 }
4375
4376 if (top_rows == bot_rows)
4377 {
4378 /* rows counts are equal, can swap cell properties */
4379 for (lnum = 0; lnum < top_rows; ++lnum)
4380 {
4381 sb_line_T temp;
4382
4383 temp = *(sb_line + lnum);
4384 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4385 *(sb_line + bot_start + lnum) = temp;
4386 }
4387 }
4388 else
4389 {
4390 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4391 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4392
4393 /* need to copy cell properties into temp memory */
4394 if (temp != NULL)
4395 {
4396 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4397 mch_memmove(term->tl_scrollback.ga_data,
4398 temp + bot_start,
4399 sizeof(sb_line_T) * bot_rows);
4400 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4401 temp + top_rows,
4402 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4403 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4404 + line_count - top_rows,
4405 temp,
4406 sizeof(sb_line_T) * top_rows);
4407 vim_free(temp);
4408 }
4409 }
4410
4411 term->tl_top_diff_rows = bot_rows;
4412 term->tl_bot_diff_rows = top_rows;
4413
4414 update_screen(NOT_VALID);
4415 return OK;
4416}
4417
4418/*
4419 * "term_dumpdiff(filename, filename, options)" function
4420 */
4421 void
4422f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4423{
4424 term_load_dump(argvars, rettv, TRUE);
4425}
4426
4427/*
4428 * "term_dumpload(filename, options)" function
4429 */
4430 void
4431f_term_dumpload(typval_T *argvars, typval_T *rettv)
4432{
4433 term_load_dump(argvars, rettv, FALSE);
4434}
4435
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004436/*
4437 * "term_getaltscreen(buf)" function
4438 */
4439 void
4440f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4441{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004442 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004443
4444 if (buf == NULL)
4445 return;
4446 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4447}
4448
4449/*
4450 * "term_getattr(attr, name)" function
4451 */
4452 void
4453f_term_getattr(typval_T *argvars, typval_T *rettv)
4454{
4455 int attr;
4456 size_t i;
4457 char_u *name;
4458
4459 static struct {
4460 char *name;
4461 int attr;
4462 } attrs[] = {
4463 {"bold", HL_BOLD},
4464 {"italic", HL_ITALIC},
4465 {"underline", HL_UNDERLINE},
4466 {"strike", HL_STRIKETHROUGH},
4467 {"reverse", HL_INVERSE},
4468 };
4469
4470 attr = get_tv_number(&argvars[0]);
4471 name = get_tv_string_chk(&argvars[1]);
4472 if (name == NULL)
4473 return;
4474
4475 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4476 if (STRCMP(name, attrs[i].name) == 0)
4477 {
4478 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4479 break;
4480 }
4481}
4482
4483/*
4484 * "term_getcursor(buf)" function
4485 */
4486 void
4487f_term_getcursor(typval_T *argvars, typval_T *rettv)
4488{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004489 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004490 term_T *term;
4491 list_T *l;
4492 dict_T *d;
4493
4494 if (rettv_list_alloc(rettv) == FAIL)
4495 return;
4496 if (buf == NULL)
4497 return;
4498 term = buf->b_term;
4499
4500 l = rettv->vval.v_list;
4501 list_append_number(l, term->tl_cursor_pos.row + 1);
4502 list_append_number(l, term->tl_cursor_pos.col + 1);
4503
4504 d = dict_alloc();
4505 if (d != NULL)
4506 {
4507 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4508 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4509 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4510 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4511 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4512 ? (char_u *)"" : term->tl_cursor_color);
4513 list_append_dict(l, d);
4514 }
4515}
4516
4517/*
4518 * "term_getjob(buf)" function
4519 */
4520 void
4521f_term_getjob(typval_T *argvars, typval_T *rettv)
4522{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004523 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004524
4525 rettv->v_type = VAR_JOB;
4526 rettv->vval.v_job = NULL;
4527 if (buf == NULL)
4528 return;
4529
4530 rettv->vval.v_job = buf->b_term->tl_job;
4531 if (rettv->vval.v_job != NULL)
4532 ++rettv->vval.v_job->jv_refcount;
4533}
4534
4535 static int
4536get_row_number(typval_T *tv, term_T *term)
4537{
4538 if (tv->v_type == VAR_STRING
4539 && tv->vval.v_string != NULL
4540 && STRCMP(tv->vval.v_string, ".") == 0)
4541 return term->tl_cursor_pos.row;
4542 return (int)get_tv_number(tv) - 1;
4543}
4544
4545/*
4546 * "term_getline(buf, row)" function
4547 */
4548 void
4549f_term_getline(typval_T *argvars, typval_T *rettv)
4550{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004551 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004552 term_T *term;
4553 int row;
4554
4555 rettv->v_type = VAR_STRING;
4556 if (buf == NULL)
4557 return;
4558 term = buf->b_term;
4559 row = get_row_number(&argvars[1], term);
4560
4561 if (term->tl_vterm == NULL)
4562 {
4563 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4564
4565 /* vterm is finished, get the text from the buffer */
4566 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4567 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4568 }
4569 else
4570 {
4571 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4572 VTermRect rect;
4573 int len;
4574 char_u *p;
4575
4576 if (row < 0 || row >= term->tl_rows)
4577 return;
4578 len = term->tl_cols * MB_MAXBYTES + 1;
4579 p = alloc(len);
4580 if (p == NULL)
4581 return;
4582 rettv->vval.v_string = p;
4583
4584 rect.start_col = 0;
4585 rect.end_col = term->tl_cols;
4586 rect.start_row = row;
4587 rect.end_row = row + 1;
4588 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4589 }
4590}
4591
4592/*
4593 * "term_getscrolled(buf)" function
4594 */
4595 void
4596f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4597{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004598 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004599
4600 if (buf == NULL)
4601 return;
4602 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4603}
4604
4605/*
4606 * "term_getsize(buf)" function
4607 */
4608 void
4609f_term_getsize(typval_T *argvars, typval_T *rettv)
4610{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004611 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004612 list_T *l;
4613
4614 if (rettv_list_alloc(rettv) == FAIL)
4615 return;
4616 if (buf == NULL)
4617 return;
4618
4619 l = rettv->vval.v_list;
4620 list_append_number(l, buf->b_term->tl_rows);
4621 list_append_number(l, buf->b_term->tl_cols);
4622}
4623
4624/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004625 * "term_setsize(buf, rows, cols)" function
4626 */
4627 void
4628f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4629{
4630 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4631 term_T *term;
4632 varnumber_T rows, cols;
4633
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004634 if (buf == NULL)
4635 {
4636 EMSG(_("E955: Not a terminal buffer"));
4637 return;
4638 }
4639 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004640 return;
4641 term = buf->b_term;
4642 rows = get_tv_number(&argvars[1]);
4643 rows = rows <= 0 ? term->tl_rows : rows;
4644 cols = get_tv_number(&argvars[2]);
4645 cols = cols <= 0 ? term->tl_cols : cols;
4646 vterm_set_size(term->tl_vterm, rows, cols);
4647 /* handle_resize() will resize the windows */
4648
4649 /* Get and remember the size we ended up with. Update the pty. */
4650 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4651 term_report_winsize(term, term->tl_rows, term->tl_cols);
4652}
4653
4654/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004655 * "term_getstatus(buf)" function
4656 */
4657 void
4658f_term_getstatus(typval_T *argvars, typval_T *rettv)
4659{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004660 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004661 term_T *term;
4662 char_u val[100];
4663
4664 rettv->v_type = VAR_STRING;
4665 if (buf == NULL)
4666 return;
4667 term = buf->b_term;
4668
4669 if (term_job_running(term))
4670 STRCPY(val, "running");
4671 else
4672 STRCPY(val, "finished");
4673 if (term->tl_normal_mode)
4674 STRCAT(val, ",normal");
4675 rettv->vval.v_string = vim_strsave(val);
4676}
4677
4678/*
4679 * "term_gettitle(buf)" function
4680 */
4681 void
4682f_term_gettitle(typval_T *argvars, typval_T *rettv)
4683{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004684 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004685
4686 rettv->v_type = VAR_STRING;
4687 if (buf == NULL)
4688 return;
4689
4690 if (buf->b_term->tl_title != NULL)
4691 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4692}
4693
4694/*
4695 * "term_gettty(buf)" function
4696 */
4697 void
4698f_term_gettty(typval_T *argvars, typval_T *rettv)
4699{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004700 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004701 char_u *p;
4702 int num = 0;
4703
4704 rettv->v_type = VAR_STRING;
4705 if (buf == NULL)
4706 return;
4707 if (argvars[1].v_type != VAR_UNKNOWN)
4708 num = get_tv_number(&argvars[1]);
4709
4710 switch (num)
4711 {
4712 case 0:
4713 if (buf->b_term->tl_job != NULL)
4714 p = buf->b_term->tl_job->jv_tty_out;
4715 else
4716 p = buf->b_term->tl_tty_out;
4717 break;
4718 case 1:
4719 if (buf->b_term->tl_job != NULL)
4720 p = buf->b_term->tl_job->jv_tty_in;
4721 else
4722 p = buf->b_term->tl_tty_in;
4723 break;
4724 default:
4725 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4726 return;
4727 }
4728 if (p != NULL)
4729 rettv->vval.v_string = vim_strsave(p);
4730}
4731
4732/*
4733 * "term_list()" function
4734 */
4735 void
4736f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4737{
4738 term_T *tp;
4739 list_T *l;
4740
4741 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4742 return;
4743
4744 l = rettv->vval.v_list;
4745 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4746 if (tp != NULL && tp->tl_buffer != NULL)
4747 if (list_append_number(l,
4748 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4749 return;
4750}
4751
4752/*
4753 * "term_scrape(buf, row)" function
4754 */
4755 void
4756f_term_scrape(typval_T *argvars, typval_T *rettv)
4757{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004758 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004759 VTermScreen *screen = NULL;
4760 VTermPos pos;
4761 list_T *l;
4762 term_T *term;
4763 char_u *p;
4764 sb_line_T *line;
4765
4766 if (rettv_list_alloc(rettv) == FAIL)
4767 return;
4768 if (buf == NULL)
4769 return;
4770 term = buf->b_term;
4771
4772 l = rettv->vval.v_list;
4773 pos.row = get_row_number(&argvars[1], term);
4774
4775 if (term->tl_vterm != NULL)
4776 {
4777 screen = vterm_obtain_screen(term->tl_vterm);
4778 p = NULL;
4779 line = NULL;
4780 }
4781 else
4782 {
4783 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4784
4785 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4786 return;
4787 p = ml_get_buf(buf, lnum + 1, FALSE);
4788 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4789 }
4790
4791 for (pos.col = 0; pos.col < term->tl_cols; )
4792 {
4793 dict_T *dcell;
4794 int width;
4795 VTermScreenCellAttrs attrs;
4796 VTermColor fg, bg;
4797 char_u rgb[8];
4798 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4799 int off = 0;
4800 int i;
4801
4802 if (screen == NULL)
4803 {
4804 cellattr_T *cellattr;
4805 int len;
4806
4807 /* vterm has finished, get the cell from scrollback */
4808 if (pos.col >= line->sb_cols)
4809 break;
4810 cellattr = line->sb_cells + pos.col;
4811 width = cellattr->width;
4812 attrs = cellattr->attrs;
4813 fg = cellattr->fg;
4814 bg = cellattr->bg;
4815 len = MB_PTR2LEN(p);
4816 mch_memmove(mbs, p, len);
4817 mbs[len] = NUL;
4818 p += len;
4819 }
4820 else
4821 {
4822 VTermScreenCell cell;
4823 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4824 break;
4825 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4826 {
4827 if (cell.chars[i] == 0)
4828 break;
4829 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4830 }
4831 mbs[off] = NUL;
4832 width = cell.width;
4833 attrs = cell.attrs;
4834 fg = cell.fg;
4835 bg = cell.bg;
4836 }
4837 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004838 if (dcell == NULL)
4839 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004840 list_append_dict(l, dcell);
4841
4842 dict_add_nr_str(dcell, "chars", 0, mbs);
4843
4844 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4845 fg.red, fg.green, fg.blue);
4846 dict_add_nr_str(dcell, "fg", 0, rgb);
4847 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4848 bg.red, bg.green, bg.blue);
4849 dict_add_nr_str(dcell, "bg", 0, rgb);
4850
4851 dict_add_nr_str(dcell, "attr",
4852 cell2attr(attrs, fg, bg), NULL);
4853 dict_add_nr_str(dcell, "width", width, NULL);
4854
4855 ++pos.col;
4856 if (width == 2)
4857 ++pos.col;
4858 }
4859}
4860
4861/*
4862 * "term_sendkeys(buf, keys)" function
4863 */
4864 void
4865f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4866{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004867 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004868 char_u *msg;
4869 term_T *term;
4870
4871 rettv->v_type = VAR_UNKNOWN;
4872 if (buf == NULL)
4873 return;
4874
4875 msg = get_tv_string_chk(&argvars[1]);
4876 if (msg == NULL)
4877 return;
4878 term = buf->b_term;
4879 if (term->tl_vterm == NULL)
4880 return;
4881
4882 while (*msg != NUL)
4883 {
4884 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004885 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004886 }
4887}
4888
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004889#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
4890/*
4891 * "term_getansicolors(buf)" function
4892 */
4893 void
4894f_term_getansicolors(typval_T *argvars, typval_T *rettv)
4895{
4896 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
4897 term_T *term;
4898 VTermState *state;
4899 VTermColor color;
4900 char_u hexbuf[10];
4901 int index;
4902 list_T *list;
4903
4904 if (rettv_list_alloc(rettv) == FAIL)
4905 return;
4906
4907 if (buf == NULL)
4908 return;
4909 term = buf->b_term;
4910 if (term->tl_vterm == NULL)
4911 return;
4912
4913 list = rettv->vval.v_list;
4914 state = vterm_obtain_state(term->tl_vterm);
4915 for (index = 0; index < 16; index++)
4916 {
4917 vterm_state_get_palette_color(state, index, &color);
4918 sprintf((char *)hexbuf, "#%02x%02x%02x",
4919 color.red, color.green, color.blue);
4920 if (list_append_string(list, hexbuf, 7) == FAIL)
4921 return;
4922 }
4923}
4924
4925/*
4926 * "term_setansicolors(buf, list)" function
4927 */
4928 void
4929f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
4930{
4931 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
4932 term_T *term;
4933
4934 if (buf == NULL)
4935 return;
4936 term = buf->b_term;
4937 if (term->tl_vterm == NULL)
4938 return;
4939
4940 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
4941 {
4942 EMSG(_(e_listreq));
4943 return;
4944 }
4945
4946 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
4947 EMSG(_(e_invarg));
4948}
4949#endif
4950
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004951/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004952 * "term_setrestore(buf, command)" function
4953 */
4954 void
4955f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4956{
4957#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004958 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004959 term_T *term;
4960 char_u *cmd;
4961
4962 if (buf == NULL)
4963 return;
4964 term = buf->b_term;
4965 vim_free(term->tl_command);
4966 cmd = get_tv_string_chk(&argvars[1]);
4967 if (cmd != NULL)
4968 term->tl_command = vim_strsave(cmd);
4969 else
4970 term->tl_command = NULL;
4971#endif
4972}
4973
4974/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004975 * "term_setkill(buf, how)" function
4976 */
4977 void
4978f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4979{
4980 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4981 term_T *term;
4982 char_u *how;
4983
4984 if (buf == NULL)
4985 return;
4986 term = buf->b_term;
4987 vim_free(term->tl_kill);
4988 how = get_tv_string_chk(&argvars[1]);
4989 if (how != NULL)
4990 term->tl_kill = vim_strsave(how);
4991 else
4992 term->tl_kill = NULL;
4993}
4994
4995/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004996 * "term_start(command, options)" function
4997 */
4998 void
4999f_term_start(typval_T *argvars, typval_T *rettv)
5000{
5001 jobopt_T opt;
5002 buf_T *buf;
5003
5004 init_job_options(&opt);
5005 if (argvars[1].v_type != VAR_UNKNOWN
5006 && get_job_options(&argvars[1], &opt,
5007 JO_TIMEOUT_ALL + JO_STOPONEXIT
5008 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5009 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5010 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5011 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005012 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005013 + JO2_NORESTORE + JO2_TERM_KILL
5014 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005015 return;
5016
Bram Moolenaar13568252018-03-16 20:46:58 +01005017 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005018
5019 if (buf != NULL && buf->b_term != NULL)
5020 rettv->vval.v_number = buf->b_fnum;
5021}
5022
5023/*
5024 * "term_wait" function
5025 */
5026 void
5027f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5028{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005029 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005030
5031 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005032 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005033 if (buf->b_term->tl_job == NULL)
5034 {
5035 ch_log(NULL, "term_wait(): no job to wait for");
5036 return;
5037 }
5038 if (buf->b_term->tl_job->jv_channel == NULL)
5039 /* channel is closed, nothing to do */
5040 return;
5041
5042 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005043 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005044 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5045 {
5046 /* The job is dead, keep reading channel I/O until the channel is
5047 * closed. buf->b_term may become NULL if the terminal was closed while
5048 * waiting. */
5049 ch_log(NULL, "term_wait(): waiting for channel to close");
5050 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5051 {
5052 mch_check_messages();
5053 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005054 if (!buf_valid(buf))
5055 /* If the terminal is closed when the channel is closed the
5056 * buffer disappears. */
5057 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005058 ui_delay(10L, FALSE);
5059 }
5060 mch_check_messages();
5061 parse_queued_messages();
5062 }
5063 else
5064 {
5065 long wait = 10L;
5066
5067 mch_check_messages();
5068 parse_queued_messages();
5069
5070 /* Wait for some time for any channel I/O. */
5071 if (argvars[1].v_type != VAR_UNKNOWN)
5072 wait = get_tv_number(&argvars[1]);
5073 ui_delay(wait, TRUE);
5074 mch_check_messages();
5075
5076 /* Flushing messages on channels is hopefully sufficient.
5077 * TODO: is there a better way? */
5078 parse_queued_messages();
5079 }
5080}
5081
5082/*
5083 * Called when a channel has sent all the lines to a terminal.
5084 * Send a CTRL-D to mark the end of the text.
5085 */
5086 void
5087term_send_eof(channel_T *ch)
5088{
5089 term_T *term;
5090
5091 for (term = first_term; term != NULL; term = term->tl_next)
5092 if (term->tl_job == ch->ch_job)
5093 {
5094 if (term->tl_eof_chars != NULL)
5095 {
5096 channel_send(ch, PART_IN, term->tl_eof_chars,
5097 (int)STRLEN(term->tl_eof_chars), NULL);
5098 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5099 }
5100# ifdef WIN3264
5101 else
5102 /* Default: CTRL-D */
5103 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5104# endif
5105 }
5106}
5107
5108# if defined(WIN3264) || defined(PROTO)
5109
5110/**************************************
5111 * 2. MS-Windows implementation.
5112 */
5113
5114# ifndef PROTO
5115
5116#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5117#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005118#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005119
5120void* (*winpty_config_new)(UINT64, void*);
5121void* (*winpty_open)(void*, void*);
5122void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5123BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5124void (*winpty_config_set_mouse_mode)(void*, int);
5125void (*winpty_config_set_initial_size)(void*, int, int);
5126LPCWSTR (*winpty_conin_name)(void*);
5127LPCWSTR (*winpty_conout_name)(void*);
5128LPCWSTR (*winpty_conerr_name)(void*);
5129void (*winpty_free)(void*);
5130void (*winpty_config_free)(void*);
5131void (*winpty_spawn_config_free)(void*);
5132void (*winpty_error_free)(void*);
5133LPCWSTR (*winpty_error_msg)(void*);
5134BOOL (*winpty_set_size)(void*, int, int, void*);
5135HANDLE (*winpty_agent_process)(void*);
5136
5137#define WINPTY_DLL "winpty.dll"
5138
5139static HINSTANCE hWinPtyDLL = NULL;
5140# endif
5141
5142 static int
5143dyn_winpty_init(int verbose)
5144{
5145 int i;
5146 static struct
5147 {
5148 char *name;
5149 FARPROC *ptr;
5150 } winpty_entry[] =
5151 {
5152 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5153 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5154 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5155 {"winpty_config_set_mouse_mode",
5156 (FARPROC*)&winpty_config_set_mouse_mode},
5157 {"winpty_config_set_initial_size",
5158 (FARPROC*)&winpty_config_set_initial_size},
5159 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5160 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5161 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5162 {"winpty_free", (FARPROC*)&winpty_free},
5163 {"winpty_open", (FARPROC*)&winpty_open},
5164 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5165 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5166 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5167 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5168 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5169 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5170 {NULL, NULL}
5171 };
5172
5173 /* No need to initialize twice. */
5174 if (hWinPtyDLL)
5175 return OK;
5176 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5177 * winpty.dll. */
5178 if (*p_winptydll != NUL)
5179 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5180 if (!hWinPtyDLL)
5181 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5182 if (!hWinPtyDLL)
5183 {
5184 if (verbose)
5185 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5186 : (char_u *)WINPTY_DLL);
5187 return FAIL;
5188 }
5189 for (i = 0; winpty_entry[i].name != NULL
5190 && winpty_entry[i].ptr != NULL; ++i)
5191 {
5192 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5193 winpty_entry[i].name)) == NULL)
5194 {
5195 if (verbose)
5196 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5197 return FAIL;
5198 }
5199 }
5200
5201 return OK;
5202}
5203
5204/*
5205 * Create a new terminal of "rows" by "cols" cells.
5206 * Store a reference in "term".
5207 * Return OK or FAIL.
5208 */
5209 static int
5210term_and_job_init(
5211 term_T *term,
5212 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005213 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005214 jobopt_T *opt)
5215{
5216 WCHAR *cmd_wchar = NULL;
5217 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005218 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005219 channel_T *channel = NULL;
5220 job_T *job = NULL;
5221 DWORD error;
5222 HANDLE jo = NULL;
5223 HANDLE child_process_handle;
5224 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005225 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005226 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005227 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005228 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005229
5230 if (dyn_winpty_init(TRUE) == FAIL)
5231 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005232 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5233 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005234
5235 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005236 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005237 cmd = argvar->vval.v_string;
5238 }
5239 else if (argvar->v_type == VAR_LIST)
5240 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005241 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005242 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005243 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005244 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005245 if (cmd == NULL || *cmd == NUL)
5246 {
5247 EMSG(_(e_invarg));
5248 goto failed;
5249 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005250
5251 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005252 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005253 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005254 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005255 if (opt->jo_cwd != NULL)
5256 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005257
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005258 win32_build_env(opt->jo_env, &ga_env, TRUE);
5259 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005260
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005261 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5262 if (term->tl_winpty_config == NULL)
5263 goto failed;
5264
5265 winpty_config_set_mouse_mode(term->tl_winpty_config,
5266 WINPTY_MOUSE_MODE_FORCE);
5267 winpty_config_set_initial_size(term->tl_winpty_config,
5268 term->tl_cols, term->tl_rows);
5269 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5270 if (term->tl_winpty == NULL)
5271 goto failed;
5272
5273 spawn_config = winpty_spawn_config_new(
5274 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5275 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5276 NULL,
5277 cmd_wchar,
5278 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005279 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005280 &winpty_err);
5281 if (spawn_config == NULL)
5282 goto failed;
5283
5284 channel = add_channel();
5285 if (channel == NULL)
5286 goto failed;
5287
5288 job = job_alloc();
5289 if (job == NULL)
5290 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005291 if (argvar->v_type == VAR_STRING)
5292 {
5293 int argc;
5294
5295 build_argv_from_string(cmd, &job->jv_argv, &argc);
5296 }
5297 else
5298 {
5299 int argc;
5300
5301 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5302 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005303
5304 if (opt->jo_set & JO_IN_BUF)
5305 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5306
5307 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5308 &child_thread_handle, &error, &winpty_err))
5309 goto failed;
5310
5311 channel_set_pipes(channel,
5312 (sock_T)CreateFileW(
5313 winpty_conin_name(term->tl_winpty),
5314 GENERIC_WRITE, 0, NULL,
5315 OPEN_EXISTING, 0, NULL),
5316 (sock_T)CreateFileW(
5317 winpty_conout_name(term->tl_winpty),
5318 GENERIC_READ, 0, NULL,
5319 OPEN_EXISTING, 0, NULL),
5320 (sock_T)CreateFileW(
5321 winpty_conerr_name(term->tl_winpty),
5322 GENERIC_READ, 0, NULL,
5323 OPEN_EXISTING, 0, NULL));
5324
5325 /* Write lines with CR instead of NL. */
5326 channel->ch_write_text_mode = TRUE;
5327
5328 jo = CreateJobObject(NULL, NULL);
5329 if (jo == NULL)
5330 goto failed;
5331
5332 if (!AssignProcessToJobObject(jo, child_process_handle))
5333 {
5334 /* Failed, switch the way to terminate process with TerminateProcess. */
5335 CloseHandle(jo);
5336 jo = NULL;
5337 }
5338
5339 winpty_spawn_config_free(spawn_config);
5340 vim_free(cmd_wchar);
5341 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005342 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005343
5344 create_vterm(term, term->tl_rows, term->tl_cols);
5345
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005346#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5347 if (opt->jo_set2 & JO2_ANSI_COLORS)
5348 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5349 else
5350 init_vterm_ansi_colors(term->tl_vterm);
5351#endif
5352
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005353 channel_set_job(channel, job, opt);
5354 job_set_options(job, opt);
5355
5356 job->jv_channel = channel;
5357 job->jv_proc_info.hProcess = child_process_handle;
5358 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5359 job->jv_job_object = jo;
5360 job->jv_status = JOB_STARTED;
5361 job->jv_tty_in = utf16_to_enc(
5362 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5363 job->jv_tty_out = utf16_to_enc(
5364 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5365 ++job->jv_refcount;
5366 term->tl_job = job;
5367
5368 return OK;
5369
5370failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005371 ga_clear(&ga_cmd);
5372 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005373 vim_free(cmd_wchar);
5374 vim_free(cwd_wchar);
5375 if (spawn_config != NULL)
5376 winpty_spawn_config_free(spawn_config);
5377 if (channel != NULL)
5378 channel_clear(channel);
5379 if (job != NULL)
5380 {
5381 job->jv_channel = NULL;
5382 job_cleanup(job);
5383 }
5384 term->tl_job = NULL;
5385 if (jo != NULL)
5386 CloseHandle(jo);
5387 if (term->tl_winpty != NULL)
5388 winpty_free(term->tl_winpty);
5389 term->tl_winpty = NULL;
5390 if (term->tl_winpty_config != NULL)
5391 winpty_config_free(term->tl_winpty_config);
5392 term->tl_winpty_config = NULL;
5393 if (winpty_err != NULL)
5394 {
5395 char_u *msg = utf16_to_enc(
5396 (short_u *)winpty_error_msg(winpty_err), NULL);
5397
5398 EMSG(msg);
5399 winpty_error_free(winpty_err);
5400 }
5401 return FAIL;
5402}
5403
5404 static int
5405create_pty_only(term_T *term, jobopt_T *options)
5406{
5407 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5408 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5409 char in_name[80], out_name[80];
5410 channel_T *channel = NULL;
5411
5412 create_vterm(term, term->tl_rows, term->tl_cols);
5413
5414 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5415 GetCurrentProcessId(),
5416 curbuf->b_fnum);
5417 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5418 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5419 PIPE_UNLIMITED_INSTANCES,
5420 0, 0, NMPWAIT_NOWAIT, NULL);
5421 if (hPipeIn == INVALID_HANDLE_VALUE)
5422 goto failed;
5423
5424 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5425 GetCurrentProcessId(),
5426 curbuf->b_fnum);
5427 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5428 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5429 PIPE_UNLIMITED_INSTANCES,
5430 0, 0, 0, NULL);
5431 if (hPipeOut == INVALID_HANDLE_VALUE)
5432 goto failed;
5433
5434 ConnectNamedPipe(hPipeIn, NULL);
5435 ConnectNamedPipe(hPipeOut, NULL);
5436
5437 term->tl_job = job_alloc();
5438 if (term->tl_job == NULL)
5439 goto failed;
5440 ++term->tl_job->jv_refcount;
5441
5442 /* behave like the job is already finished */
5443 term->tl_job->jv_status = JOB_FINISHED;
5444
5445 channel = add_channel();
5446 if (channel == NULL)
5447 goto failed;
5448 term->tl_job->jv_channel = channel;
5449 channel->ch_keep_open = TRUE;
5450 channel->ch_named_pipe = TRUE;
5451
5452 channel_set_pipes(channel,
5453 (sock_T)hPipeIn,
5454 (sock_T)hPipeOut,
5455 (sock_T)hPipeOut);
5456 channel_set_job(channel, term->tl_job, options);
5457 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5458 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5459
5460 return OK;
5461
5462failed:
5463 if (hPipeIn != NULL)
5464 CloseHandle(hPipeIn);
5465 if (hPipeOut != NULL)
5466 CloseHandle(hPipeOut);
5467 return FAIL;
5468}
5469
5470/*
5471 * Free the terminal emulator part of "term".
5472 */
5473 static void
5474term_free_vterm(term_T *term)
5475{
5476 if (term->tl_winpty != NULL)
5477 winpty_free(term->tl_winpty);
5478 term->tl_winpty = NULL;
5479 if (term->tl_winpty_config != NULL)
5480 winpty_config_free(term->tl_winpty_config);
5481 term->tl_winpty_config = NULL;
5482 if (term->tl_vterm != NULL)
5483 vterm_free(term->tl_vterm);
5484 term->tl_vterm = NULL;
5485}
5486
5487/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005488 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005489 */
5490 static void
5491term_report_winsize(term_T *term, int rows, int cols)
5492{
5493 if (term->tl_winpty)
5494 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5495}
5496
5497 int
5498terminal_enabled(void)
5499{
5500 return dyn_winpty_init(FALSE) == OK;
5501}
5502
5503# else
5504
5505/**************************************
5506 * 3. Unix-like implementation.
5507 */
5508
5509/*
5510 * Create a new terminal of "rows" by "cols" cells.
5511 * Start job for "cmd".
5512 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005513 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005514 * Return OK or FAIL.
5515 */
5516 static int
5517term_and_job_init(
5518 term_T *term,
5519 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005520 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005521 jobopt_T *opt)
5522{
5523 create_vterm(term, term->tl_rows, term->tl_cols);
5524
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005525#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5526 if (opt->jo_set2 & JO2_ANSI_COLORS)
5527 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5528 else
5529 init_vterm_ansi_colors(term->tl_vterm);
5530#endif
5531
Bram Moolenaar13568252018-03-16 20:46:58 +01005532 /* This may change a string in "argvar". */
5533 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005534 if (term->tl_job != NULL)
5535 ++term->tl_job->jv_refcount;
5536
5537 return term->tl_job != NULL
5538 && term->tl_job->jv_channel != NULL
5539 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5540}
5541
5542 static int
5543create_pty_only(term_T *term, jobopt_T *opt)
5544{
5545 create_vterm(term, term->tl_rows, term->tl_cols);
5546
5547 term->tl_job = job_alloc();
5548 if (term->tl_job == NULL)
5549 return FAIL;
5550 ++term->tl_job->jv_refcount;
5551
5552 /* behave like the job is already finished */
5553 term->tl_job->jv_status = JOB_FINISHED;
5554
5555 return mch_create_pty_channel(term->tl_job, opt);
5556}
5557
5558/*
5559 * Free the terminal emulator part of "term".
5560 */
5561 static void
5562term_free_vterm(term_T *term)
5563{
5564 if (term->tl_vterm != NULL)
5565 vterm_free(term->tl_vterm);
5566 term->tl_vterm = NULL;
5567}
5568
5569/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005570 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005571 */
5572 static void
5573term_report_winsize(term_T *term, int rows, int cols)
5574{
5575 /* Use an ioctl() to report the new window size to the job. */
5576 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5577 {
5578 int fd = -1;
5579 int part;
5580
5581 for (part = PART_OUT; part < PART_COUNT; ++part)
5582 {
5583 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5584 if (isatty(fd))
5585 break;
5586 }
5587 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5588 mch_signal_job(term->tl_job, (char_u *)"winch");
5589 }
5590}
5591
5592# endif
5593
5594#endif /* FEAT_TERMINAL */