blob: 1fd9ed2abcc93b14bfff3ed38e0718b52eb4771a [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 Moolenaar13568252018-03-16 20:46:58 +0100608 if (old_curbuf == NULL)
Bram Moolenaarab5e7c32018-02-13 14:07:18 +0100609 {
610 ++curbuf->b_locked;
611 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
612 --curbuf->b_locked;
613 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100614 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200615 {
616 --curbuf->b_nwindows;
617 curbuf = old_curbuf;
618 curwin->w_buffer = curbuf;
619 ++curbuf->b_nwindows;
620 }
621 }
622 else
623 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100624 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200625 return NULL;
626 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100627
Bram Moolenaar13568252018-03-16 20:46:58 +0100628 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200629 return newbuf;
630}
631
632/*
633 * ":terminal": open a terminal window and execute a job in it.
634 */
635 void
636ex_terminal(exarg_T *eap)
637{
638 typval_T argvar[2];
639 jobopt_T opt;
640 char_u *cmd;
641 char_u *tofree = NULL;
642
643 init_job_options(&opt);
644
645 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100646 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200647 {
648 char_u *p, *ep;
649
650 cmd += 2;
651 p = skiptowhite(cmd);
652 ep = vim_strchr(cmd, '=');
653 if (ep != NULL && ep < p)
654 p = ep;
655
656 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
657 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100658 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
659 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200660 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
661 opt.jo_term_finish = 'o';
662 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
663 opt.jo_curwin = 1;
664 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
665 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100666 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
667 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100668 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
669 && ep != NULL)
670 {
671 opt.jo_set2 |= JO2_TERM_KILL;
672 opt.jo_term_kill = ep + 1;
673 p = skiptowhite(cmd);
674 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200675 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
676 && ep != NULL && isdigit(ep[1]))
677 {
678 opt.jo_set2 |= JO2_TERM_ROWS;
679 opt.jo_term_rows = atoi((char *)ep + 1);
680 p = skiptowhite(cmd);
681 }
682 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
683 && ep != NULL && isdigit(ep[1]))
684 {
685 opt.jo_set2 |= JO2_TERM_COLS;
686 opt.jo_term_cols = atoi((char *)ep + 1);
687 p = skiptowhite(cmd);
688 }
689 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
690 && ep != NULL)
691 {
692 char_u *buf = NULL;
693 char_u *keys;
694
695 p = skiptowhite(cmd);
696 *p = NUL;
697 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
698 opt.jo_set2 |= JO2_EOF_CHARS;
699 opt.jo_eof_chars = vim_strsave(keys);
700 vim_free(buf);
701 *p = ' ';
702 }
703 else
704 {
705 if (*p)
706 *p = NUL;
707 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100708 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200709 }
710 cmd = skipwhite(p);
711 }
712 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100713 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200714 /* Make a copy of 'shell', an autocommand may change the option. */
715 tofree = cmd = vim_strsave(p_sh);
716
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100717 /* default to close when the shell exits */
718 if (opt.jo_term_finish == NUL)
719 opt.jo_term_finish = 'c';
720 }
721
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200722 if (eap->addr_count > 0)
723 {
724 /* Write lines from current buffer to the job. */
725 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
726 opt.jo_io[PART_IN] = JIO_BUFFER;
727 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
728 opt.jo_in_top = eap->line1;
729 opt.jo_in_bot = eap->line2;
730 }
731
732 argvar[0].v_type = VAR_STRING;
733 argvar[0].vval.v_string = cmd;
734 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100735 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200736 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100737
738theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200739 vim_free(opt.jo_eof_chars);
740}
741
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100742#if defined(FEAT_SESSION) || defined(PROTO)
743/*
744 * Write a :terminal command to the session file to restore the terminal in
745 * window "wp".
746 * Return FAIL if writing fails.
747 */
748 int
749term_write_session(FILE *fd, win_T *wp)
750{
751 term_T *term = wp->w_buffer->b_term;
752
753 /* Create the terminal and run the command. This is not without
754 * risk, but let's assume the user only creates a session when this
755 * will be OK. */
756 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
757 term->tl_cols, term->tl_rows) < 0)
758 return FAIL;
759 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
760 return FAIL;
761
762 return put_eol(fd);
763}
764
765/*
766 * Return TRUE if "buf" has a terminal that should be restored.
767 */
768 int
769term_should_restore(buf_T *buf)
770{
771 term_T *term = buf->b_term;
772
773 return term != NULL && (term->tl_command == NULL
774 || STRCMP(term->tl_command, "NONE") != 0);
775}
776#endif
777
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200778/*
779 * Free the scrollback buffer for "term".
780 */
781 static void
782free_scrollback(term_T *term)
783{
784 int i;
785
786 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
787 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
788 ga_clear(&term->tl_scrollback);
789}
790
791/*
792 * Free a terminal and everything it refers to.
793 * Kills the job if there is one.
794 * Called when wiping out a buffer.
795 */
796 void
797free_terminal(buf_T *buf)
798{
799 term_T *term = buf->b_term;
800 term_T *tp;
801
802 if (term == NULL)
803 return;
804 if (first_term == term)
805 first_term = term->tl_next;
806 else
807 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
808 if (tp->tl_next == term)
809 {
810 tp->tl_next = term->tl_next;
811 break;
812 }
813
814 if (term->tl_job != NULL)
815 {
816 if (term->tl_job->jv_status != JOB_ENDED
817 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100818 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200819 job_stop(term->tl_job, NULL, "kill");
820 job_unref(term->tl_job);
821 }
822
823 free_scrollback(term);
824
825 term_free_vterm(term);
826 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100827#ifdef FEAT_SESSION
828 vim_free(term->tl_command);
829#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100830 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200831 vim_free(term->tl_status_text);
832 vim_free(term->tl_opencmd);
833 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100834 if (desired_cursor_color == term->tl_cursor_color)
835 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200836 vim_free(term->tl_cursor_color);
837 vim_free(term);
838 buf->b_term = NULL;
839 if (in_terminal_loop == term)
840 in_terminal_loop = NULL;
841}
842
843/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100844 * Get the part that is connected to the tty. Normally this is PART_IN, but
845 * when writing buffer lines to the job it can be another. This makes it
846 * possible to do "1,5term vim -".
847 */
848 static ch_part_T
849get_tty_part(term_T *term)
850{
851#ifdef UNIX
852 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
853 int i;
854
855 for (i = 0; i < 3; ++i)
856 {
857 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
858
859 if (isatty(fd))
860 return parts[i];
861 }
862#endif
863 return PART_IN;
864}
865
866/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200867 * Write job output "msg[len]" to the vterm.
868 */
869 static void
870term_write_job_output(term_T *term, char_u *msg, size_t len)
871{
872 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100873 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200874
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100875 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200876
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100877 /* flush vterm buffer when vterm responded to control sequence */
878 if (prevlen != vterm_output_get_buffer_current(vterm))
879 {
880 char buf[KEY_BUF_LEN];
881 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
882
883 if (curlen > 0)
884 channel_send(term->tl_job->jv_channel, get_tty_part(term),
885 (char_u *)buf, (int)curlen, NULL);
886 }
887
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200888 /* this invokes the damage callbacks */
889 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
890}
891
892 static void
893update_cursor(term_T *term, int redraw)
894{
895 if (term->tl_normal_mode)
896 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100897#ifdef FEAT_GUI
898 if (term->tl_system)
899 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
900 term->tl_cursor_pos.col);
901 else
902#endif
903 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200904 if (redraw)
905 {
906 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
907 cursor_on();
908 out_flush();
909#ifdef FEAT_GUI
910 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100911 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200912 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100913 gui_mch_flush();
914 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200915#endif
916 }
917}
918
919/*
920 * Invoked when "msg" output from a job was received. Write it to the terminal
921 * of "buffer".
922 */
923 void
924write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
925{
926 size_t len = STRLEN(msg);
927 term_T *term = buffer->b_term;
928
929 if (term->tl_vterm == NULL)
930 {
931 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
932 return;
933 }
934 ch_log(channel, "writing %d bytes to terminal", (int)len);
935 term_write_job_output(term, msg, len);
936
Bram Moolenaar13568252018-03-16 20:46:58 +0100937#ifdef FEAT_GUI
938 if (term->tl_system)
939 {
940 /* show system output, scrolling up the screen as needed */
941 update_system_term(term);
942 update_cursor(term, TRUE);
943 }
944 else
945#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200946 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
947 * contents, thus no screen update is needed. */
948 if (!term->tl_normal_mode)
949 {
950 /* TODO: only update once in a while. */
951 ch_log(term->tl_job->jv_channel, "updating screen");
952 if (buffer == curbuf)
953 {
954 update_screen(0);
955 update_cursor(term, TRUE);
956 }
957 else
958 redraw_after_callback(TRUE);
959 }
960}
961
962/*
963 * Send a mouse position and click to the vterm
964 */
965 static int
966term_send_mouse(VTerm *vterm, int button, int pressed)
967{
968 VTermModifier mod = VTERM_MOD_NONE;
969
970 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200971 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100972 if (button != 0)
973 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200974 return TRUE;
975}
976
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100977static int enter_mouse_col = -1;
978static int enter_mouse_row = -1;
979
980/*
981 * Handle a mouse click, drag or release.
982 * Return TRUE when a mouse event is sent to the terminal.
983 */
984 static int
985term_mouse_click(VTerm *vterm, int key)
986{
987#if defined(FEAT_CLIPBOARD)
988 /* For modeless selection mouse drag and release events are ignored, unless
989 * they are preceded with a mouse down event */
990 static int ignore_drag_release = TRUE;
991 VTermMouseState mouse_state;
992
993 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
994 if (mouse_state.flags == 0)
995 {
996 /* Terminal is not using the mouse, use modeless selection. */
997 switch (key)
998 {
999 case K_LEFTDRAG:
1000 case K_LEFTRELEASE:
1001 case K_RIGHTDRAG:
1002 case K_RIGHTRELEASE:
1003 /* Ignore drag and release events when the button-down wasn't
1004 * seen before. */
1005 if (ignore_drag_release)
1006 {
1007 int save_mouse_col, save_mouse_row;
1008
1009 if (enter_mouse_col < 0)
1010 break;
1011
1012 /* mouse click in the window gave us focus, handle that
1013 * click now */
1014 save_mouse_col = mouse_col;
1015 save_mouse_row = mouse_row;
1016 mouse_col = enter_mouse_col;
1017 mouse_row = enter_mouse_row;
1018 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1019 mouse_col = save_mouse_col;
1020 mouse_row = save_mouse_row;
1021 }
1022 /* FALLTHROUGH */
1023 case K_LEFTMOUSE:
1024 case K_RIGHTMOUSE:
1025 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1026 ignore_drag_release = TRUE;
1027 else
1028 ignore_drag_release = FALSE;
1029 /* Should we call mouse_has() here? */
1030 if (clip_star.available)
1031 {
1032 int button, is_click, is_drag;
1033
1034 button = get_mouse_button(KEY2TERMCAP1(key),
1035 &is_click, &is_drag);
1036 if (mouse_model_popup() && button == MOUSE_LEFT
1037 && (mod_mask & MOD_MASK_SHIFT))
1038 {
1039 /* Translate shift-left to right button. */
1040 button = MOUSE_RIGHT;
1041 mod_mask &= ~MOD_MASK_SHIFT;
1042 }
1043 clip_modeless(button, is_click, is_drag);
1044 }
1045 break;
1046
1047 case K_MIDDLEMOUSE:
1048 if (clip_star.available)
1049 insert_reg('*', TRUE);
1050 break;
1051 }
1052 enter_mouse_col = -1;
1053 return FALSE;
1054 }
1055#endif
1056 enter_mouse_col = -1;
1057
1058 switch (key)
1059 {
1060 case K_LEFTMOUSE:
1061 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1062 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1063 case K_LEFTRELEASE:
1064 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1065 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1066 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1067 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1068 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1069 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1070 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1071 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1072 }
1073 return TRUE;
1074}
1075
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001076/*
1077 * Convert typed key "c" into bytes to send to the job.
1078 * Return the number of bytes in "buf".
1079 */
1080 static int
1081term_convert_key(term_T *term, int c, char *buf)
1082{
1083 VTerm *vterm = term->tl_vterm;
1084 VTermKey key = VTERM_KEY_NONE;
1085 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001086 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001087
1088 switch (c)
1089 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001090 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1091
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001092 /* don't use VTERM_KEY_BACKSPACE, it always
1093 * becomes 0x7f DEL */
1094 case K_BS: c = term_backspace_char; break;
1095
1096 case ESC: key = VTERM_KEY_ESCAPE; break;
1097 case K_DEL: key = VTERM_KEY_DEL; break;
1098 case K_DOWN: key = VTERM_KEY_DOWN; break;
1099 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1100 key = VTERM_KEY_DOWN; break;
1101 case K_END: key = VTERM_KEY_END; break;
1102 case K_S_END: mod = VTERM_MOD_SHIFT;
1103 key = VTERM_KEY_END; break;
1104 case K_C_END: mod = VTERM_MOD_CTRL;
1105 key = VTERM_KEY_END; break;
1106 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1107 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1108 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1109 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1110 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1111 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1112 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1113 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1114 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1115 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1116 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1117 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1118 case K_HOME: key = VTERM_KEY_HOME; break;
1119 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1120 key = VTERM_KEY_HOME; break;
1121 case K_C_HOME: mod = VTERM_MOD_CTRL;
1122 key = VTERM_KEY_HOME; break;
1123 case K_INS: key = VTERM_KEY_INS; break;
1124 case K_K0: key = VTERM_KEY_KP_0; break;
1125 case K_K1: key = VTERM_KEY_KP_1; break;
1126 case K_K2: key = VTERM_KEY_KP_2; break;
1127 case K_K3: key = VTERM_KEY_KP_3; break;
1128 case K_K4: key = VTERM_KEY_KP_4; break;
1129 case K_K5: key = VTERM_KEY_KP_5; break;
1130 case K_K6: key = VTERM_KEY_KP_6; break;
1131 case K_K7: key = VTERM_KEY_KP_7; break;
1132 case K_K8: key = VTERM_KEY_KP_8; break;
1133 case K_K9: key = VTERM_KEY_KP_9; break;
1134 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1135 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1136 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1137 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1138 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1139 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1140 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1141 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1142 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1143 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1144 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1145 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1146 case K_LEFT: key = VTERM_KEY_LEFT; break;
1147 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1148 key = VTERM_KEY_LEFT; break;
1149 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1150 key = VTERM_KEY_LEFT; break;
1151 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1152 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1153 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1154 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1155 key = VTERM_KEY_RIGHT; break;
1156 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1157 key = VTERM_KEY_RIGHT; break;
1158 case K_UP: key = VTERM_KEY_UP; break;
1159 case K_S_UP: mod = VTERM_MOD_SHIFT;
1160 key = VTERM_KEY_UP; break;
1161 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001162 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1163 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001164
Bram Moolenaara42ad572017-11-16 13:08:04 +01001165 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1166 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001167 case K_MOUSELEFT: /* TODO */ return 0;
1168 case K_MOUSERIGHT: /* TODO */ return 0;
1169
1170 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001171 case K_LEFTMOUSE_NM:
1172 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001173 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001174 case K_LEFTRELEASE_NM:
1175 case K_MOUSEMOVE:
1176 case K_MIDDLEMOUSE:
1177 case K_MIDDLEDRAG:
1178 case K_MIDDLERELEASE:
1179 case K_RIGHTMOUSE:
1180 case K_RIGHTDRAG:
1181 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1182 return 0;
1183 other = TRUE;
1184 break;
1185
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001186 case K_X1MOUSE: /* TODO */ return 0;
1187 case K_X1DRAG: /* TODO */ return 0;
1188 case K_X1RELEASE: /* TODO */ return 0;
1189 case K_X2MOUSE: /* TODO */ return 0;
1190 case K_X2DRAG: /* TODO */ return 0;
1191 case K_X2RELEASE: /* TODO */ return 0;
1192
1193 case K_IGNORE: return 0;
1194 case K_NOP: return 0;
1195 case K_UNDO: return 0;
1196 case K_HELP: return 0;
1197 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1198 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1199 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1200 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1201 case K_SELECT: return 0;
1202#ifdef FEAT_GUI
1203 case K_VER_SCROLLBAR: return 0;
1204 case K_HOR_SCROLLBAR: return 0;
1205#endif
1206#ifdef FEAT_GUI_TABLINE
1207 case K_TABLINE: return 0;
1208 case K_TABMENU: return 0;
1209#endif
1210#ifdef FEAT_NETBEANS_INTG
1211 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1212#endif
1213#ifdef FEAT_DND
1214 case K_DROP: return 0;
1215#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001216 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001217 case K_PS: vterm_keyboard_start_paste(vterm);
1218 other = TRUE;
1219 break;
1220 case K_PE: vterm_keyboard_end_paste(vterm);
1221 other = TRUE;
1222 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001223 }
1224
1225 /*
1226 * Convert special keys to vterm keys:
1227 * - Write keys to vterm: vterm_keyboard_key()
1228 * - Write output to channel.
1229 * TODO: use mod_mask
1230 */
1231 if (key != VTERM_KEY_NONE)
1232 /* Special key, let vterm convert it. */
1233 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001234 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001235 /* Normal character, let vterm convert it. */
1236 vterm_keyboard_unichar(vterm, c, mod);
1237
1238 /* Read back the converted escape sequence. */
1239 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1240}
1241
1242/*
1243 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001244 * If "check_job_status" is TRUE update the job status.
1245 */
1246 static int
1247term_job_running_check(term_T *term, int check_job_status)
1248{
1249 /* Also consider the job finished when the channel is closed, to avoid a
1250 * race condition when updating the title. */
1251 if (term != NULL
1252 && term->tl_job != NULL
1253 && channel_is_open(term->tl_job->jv_channel))
1254 {
1255 if (check_job_status)
1256 job_status(term->tl_job);
1257 return (term->tl_job->jv_status == JOB_STARTED
1258 || term->tl_job->jv_channel->ch_keep_open);
1259 }
1260 return FALSE;
1261}
1262
1263/*
1264 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001265 */
1266 int
1267term_job_running(term_T *term)
1268{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001269 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001270}
1271
1272/*
1273 * Return TRUE if "term" has an active channel and used ":term NONE".
1274 */
1275 int
1276term_none_open(term_T *term)
1277{
1278 /* Also consider the job finished when the channel is closed, to avoid a
1279 * race condition when updating the title. */
1280 return term != NULL
1281 && term->tl_job != NULL
1282 && channel_is_open(term->tl_job->jv_channel)
1283 && term->tl_job->jv_channel->ch_keep_open;
1284}
1285
1286/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001287 * Used when exiting: kill the job in "buf" if so desired.
1288 * Return OK when the job finished.
1289 * Return FAIL when the job is still running.
1290 */
1291 int
1292term_try_stop_job(buf_T *buf)
1293{
1294 int count;
1295 char *how = (char *)buf->b_term->tl_kill;
1296
1297#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1298 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1299 {
1300 char_u buff[DIALOG_MSG_SIZE];
1301 int ret;
1302
1303 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1304 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1305 if (ret == VIM_YES)
1306 how = "kill";
1307 else if (ret == VIM_CANCEL)
1308 return FAIL;
1309 }
1310#endif
1311 if (how == NULL || *how == NUL)
1312 return FAIL;
1313
1314 job_stop(buf->b_term->tl_job, NULL, how);
1315
1316 /* wait for up to a second for the job to die */
1317 for (count = 0; count < 100; ++count)
1318 {
1319 /* buffer, terminal and job may be cleaned up while waiting */
1320 if (!buf_valid(buf)
1321 || buf->b_term == NULL
1322 || buf->b_term->tl_job == NULL)
1323 return OK;
1324
1325 /* call job_status() to update jv_status */
1326 job_status(buf->b_term->tl_job);
1327 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1328 return OK;
1329 ui_delay(10L, FALSE);
1330 mch_check_messages();
1331 parse_queued_messages();
1332 }
1333 return FAIL;
1334}
1335
1336/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001337 * Add the last line of the scrollback buffer to the buffer in the window.
1338 */
1339 static void
1340add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1341{
1342 buf_T *buf = term->tl_buffer;
1343 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1344 linenr_T lnum = buf->b_ml.ml_line_count;
1345
1346#ifdef WIN3264
1347 if (!enc_utf8 && enc_codepage > 0)
1348 {
1349 WCHAR *ret = NULL;
1350 int length = 0;
1351
1352 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1353 &ret, &length);
1354 if (ret != NULL)
1355 {
1356 WideCharToMultiByte_alloc(enc_codepage, 0,
1357 ret, length, (char **)&text, &len, 0, 0);
1358 vim_free(ret);
1359 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1360 vim_free(text);
1361 }
1362 }
1363 else
1364#endif
1365 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1366 if (empty)
1367 {
1368 /* Delete the empty line that was in the empty buffer. */
1369 curbuf = buf;
1370 ml_delete(1, FALSE);
1371 curbuf = curwin->w_buffer;
1372 }
1373}
1374
1375 static void
1376cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1377{
1378 attr->width = cell->width;
1379 attr->attrs = cell->attrs;
1380 attr->fg = cell->fg;
1381 attr->bg = cell->bg;
1382}
1383
1384 static int
1385equal_celattr(cellattr_T *a, cellattr_T *b)
1386{
1387 /* Comparing the colors should be sufficient. */
1388 return a->fg.red == b->fg.red
1389 && a->fg.green == b->fg.green
1390 && a->fg.blue == b->fg.blue
1391 && a->bg.red == b->bg.red
1392 && a->bg.green == b->bg.green
1393 && a->bg.blue == b->bg.blue;
1394}
1395
Bram Moolenaard96ff162018-02-18 22:13:29 +01001396/*
1397 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1398 * line at this position. Otherwise at the end.
1399 */
1400 static int
1401add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1402{
1403 if (ga_grow(&term->tl_scrollback, 1) == OK)
1404 {
1405 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1406 + term->tl_scrollback.ga_len;
1407
1408 if (lnum > 0)
1409 {
1410 int i;
1411
1412 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1413 {
1414 *line = *(line - 1);
1415 --line;
1416 }
1417 }
1418 line->sb_cols = 0;
1419 line->sb_cells = NULL;
1420 line->sb_fill_attr = *fill_attr;
1421 ++term->tl_scrollback.ga_len;
1422 return OK;
1423 }
1424 return FALSE;
1425}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001426
1427/*
1428 * Add the current lines of the terminal to scrollback and to the buffer.
1429 * Called after the job has ended and when switching to Terminal-Normal mode.
1430 */
1431 static void
1432move_terminal_to_buffer(term_T *term)
1433{
1434 win_T *wp;
1435 int len;
1436 int lines_skipped = 0;
1437 VTermPos pos;
1438 VTermScreenCell cell;
1439 cellattr_T fill_attr, new_fill_attr;
1440 cellattr_T *p;
1441 VTermScreen *screen;
1442
1443 if (term->tl_vterm == NULL)
1444 return;
1445 screen = vterm_obtain_screen(term->tl_vterm);
1446 fill_attr = new_fill_attr = term->tl_default_color;
1447
1448 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1449 {
1450 len = 0;
1451 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1452 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1453 && cell.chars[0] != NUL)
1454 {
1455 len = pos.col + 1;
1456 new_fill_attr = term->tl_default_color;
1457 }
1458 else
1459 /* Assume the last attr is the filler attr. */
1460 cell2cellattr(&cell, &new_fill_attr);
1461
1462 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1463 ++lines_skipped;
1464 else
1465 {
1466 while (lines_skipped > 0)
1467 {
1468 /* Line was skipped, add an empty line. */
1469 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001470 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001471 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001472 }
1473
1474 if (len == 0)
1475 p = NULL;
1476 else
1477 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1478 if ((p != NULL || len == 0)
1479 && ga_grow(&term->tl_scrollback, 1) == OK)
1480 {
1481 garray_T ga;
1482 int width;
1483 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1484 + term->tl_scrollback.ga_len;
1485
1486 ga_init2(&ga, 1, 100);
1487 for (pos.col = 0; pos.col < len; pos.col += width)
1488 {
1489 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1490 {
1491 width = 1;
1492 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1493 if (ga_grow(&ga, 1) == OK)
1494 ga.ga_len += utf_char2bytes(' ',
1495 (char_u *)ga.ga_data + ga.ga_len);
1496 }
1497 else
1498 {
1499 width = cell.width;
1500
1501 cell2cellattr(&cell, &p[pos.col]);
1502
1503 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1504 {
1505 int i;
1506 int c;
1507
1508 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1509 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1510 (char_u *)ga.ga_data + ga.ga_len);
1511 }
1512 }
1513 }
1514 line->sb_cols = len;
1515 line->sb_cells = p;
1516 line->sb_fill_attr = new_fill_attr;
1517 fill_attr = new_fill_attr;
1518 ++term->tl_scrollback.ga_len;
1519
1520 if (ga_grow(&ga, 1) == FAIL)
1521 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1522 else
1523 {
1524 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1525 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1526 }
1527 ga_clear(&ga);
1528 }
1529 else
1530 vim_free(p);
1531 }
1532 }
1533
1534 /* Obtain the current background color. */
1535 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1536 &term->tl_default_color.fg, &term->tl_default_color.bg);
1537
1538 FOR_ALL_WINDOWS(wp)
1539 {
1540 if (wp->w_buffer == term->tl_buffer)
1541 {
1542 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1543 wp->w_cursor.col = 0;
1544 wp->w_valid = 0;
1545 if (wp->w_cursor.lnum >= wp->w_height)
1546 {
1547 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1548
1549 if (wp->w_topline < min_topline)
1550 wp->w_topline = min_topline;
1551 }
1552 redraw_win_later(wp, NOT_VALID);
1553 }
1554 }
1555}
1556
1557 static void
1558set_terminal_mode(term_T *term, int normal_mode)
1559{
1560 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001561 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001562 if (term->tl_buffer == curbuf)
1563 maketitle();
1564}
1565
1566/*
1567 * Called after the job if finished and Terminal mode is not active:
1568 * Move the vterm contents into the scrollback buffer and free the vterm.
1569 */
1570 static void
1571cleanup_vterm(term_T *term)
1572{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001573 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001574 move_terminal_to_buffer(term);
1575 term_free_vterm(term);
1576 set_terminal_mode(term, FALSE);
1577}
1578
1579/*
1580 * Switch from Terminal-Job mode to Terminal-Normal mode.
1581 * Suspends updating the terminal window.
1582 */
1583 static void
1584term_enter_normal_mode(void)
1585{
1586 term_T *term = curbuf->b_term;
1587
1588 /* Append the current terminal contents to the buffer. */
1589 move_terminal_to_buffer(term);
1590
1591 set_terminal_mode(term, TRUE);
1592
1593 /* Move the window cursor to the position of the cursor in the
1594 * terminal. */
1595 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1596 + term->tl_cursor_pos.row + 1;
1597 check_cursor();
1598 coladvance(term->tl_cursor_pos.col);
1599
1600 /* Display the same lines as in the terminal. */
1601 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1602}
1603
1604/*
1605 * Returns TRUE if the current window contains a terminal and we are in
1606 * Terminal-Normal mode.
1607 */
1608 int
1609term_in_normal_mode(void)
1610{
1611 term_T *term = curbuf->b_term;
1612
1613 return term != NULL && term->tl_normal_mode;
1614}
1615
1616/*
1617 * Switch from Terminal-Normal mode to Terminal-Job mode.
1618 * Restores updating the terminal window.
1619 */
1620 void
1621term_enter_job_mode()
1622{
1623 term_T *term = curbuf->b_term;
1624 sb_line_T *line;
1625 garray_T *gap;
1626
1627 /* Remove the terminal contents from the scrollback and the buffer. */
1628 gap = &term->tl_scrollback;
1629 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1630 && gap->ga_len > 0)
1631 {
1632 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1633 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1634 vim_free(line->sb_cells);
1635 --gap->ga_len;
1636 }
1637 check_cursor();
1638
1639 set_terminal_mode(term, FALSE);
1640
1641 if (term->tl_channel_closed)
1642 cleanup_vterm(term);
1643 redraw_buf_and_status_later(curbuf, NOT_VALID);
1644}
1645
1646/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001647 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001648 * Note: while waiting a terminal may be closed and freed if the channel is
1649 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001650 */
1651 static int
1652term_vgetc()
1653{
1654 int c;
1655 int save_State = State;
1656
1657 State = TERMINAL;
1658 got_int = FALSE;
1659#ifdef WIN3264
1660 ctrl_break_was_pressed = FALSE;
1661#endif
1662 c = vgetc();
1663 got_int = FALSE;
1664 State = save_State;
1665 return c;
1666}
1667
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001668static int mouse_was_outside = FALSE;
1669
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001670/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001671 * Send keys to terminal.
1672 * Return FAIL when the key needs to be handled in Normal mode.
1673 * Return OK when the key was dropped or sent to the terminal.
1674 */
1675 int
1676send_keys_to_term(term_T *term, int c, int typed)
1677{
1678 char msg[KEY_BUF_LEN];
1679 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001680 int dragging_outside = FALSE;
1681
1682 /* Catch keys that need to be handled as in Normal mode. */
1683 switch (c)
1684 {
1685 case NUL:
1686 case K_ZERO:
1687 if (typed)
1688 stuffcharReadbuff(c);
1689 return FAIL;
1690
1691 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001692 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001693 return FAIL;
1694
1695 case K_LEFTDRAG:
1696 case K_MIDDLEDRAG:
1697 case K_RIGHTDRAG:
1698 case K_X1DRAG:
1699 case K_X2DRAG:
1700 dragging_outside = mouse_was_outside;
1701 /* FALLTHROUGH */
1702 case K_LEFTMOUSE:
1703 case K_LEFTMOUSE_NM:
1704 case K_LEFTRELEASE:
1705 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001706 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001707 case K_MIDDLEMOUSE:
1708 case K_MIDDLERELEASE:
1709 case K_RIGHTMOUSE:
1710 case K_RIGHTRELEASE:
1711 case K_X1MOUSE:
1712 case K_X1RELEASE:
1713 case K_X2MOUSE:
1714 case K_X2RELEASE:
1715
1716 case K_MOUSEUP:
1717 case K_MOUSEDOWN:
1718 case K_MOUSELEFT:
1719 case K_MOUSERIGHT:
1720 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001721 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001722 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001723 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001724 || dragging_outside)
1725 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001726 /* click or scroll outside the current window or on status line
1727 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001728 if (typed)
1729 {
1730 stuffcharReadbuff(c);
1731 mouse_was_outside = TRUE;
1732 }
1733 return FAIL;
1734 }
1735 }
1736 if (typed)
1737 mouse_was_outside = FALSE;
1738
1739 /* Convert the typed key to a sequence of bytes for the job. */
1740 len = term_convert_key(term, c, msg);
1741 if (len > 0)
1742 /* TODO: if FAIL is returned, stop? */
1743 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1744 (char_u *)msg, (int)len, NULL);
1745
1746 return OK;
1747}
1748
1749 static void
1750position_cursor(win_T *wp, VTermPos *pos)
1751{
1752 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1753 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1754 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1755}
1756
1757/*
1758 * Handle CTRL-W "": send register contents to the job.
1759 */
1760 static void
1761term_paste_register(int prev_c UNUSED)
1762{
1763 int c;
1764 list_T *l;
1765 listitem_T *item;
1766 long reglen = 0;
1767 int type;
1768
1769#ifdef FEAT_CMDL_INFO
1770 if (add_to_showcmd(prev_c))
1771 if (add_to_showcmd('"'))
1772 out_flush();
1773#endif
1774 c = term_vgetc();
1775#ifdef FEAT_CMDL_INFO
1776 clear_showcmd();
1777#endif
1778 if (!term_use_loop())
1779 /* job finished while waiting for a character */
1780 return;
1781
1782 /* CTRL-W "= prompt for expression to evaluate. */
1783 if (c == '=' && get_expr_register() != '=')
1784 return;
1785 if (!term_use_loop())
1786 /* job finished while waiting for a character */
1787 return;
1788
1789 l = (list_T *)get_reg_contents(c, GREG_LIST);
1790 if (l != NULL)
1791 {
1792 type = get_reg_type(c, &reglen);
1793 for (item = l->lv_first; item != NULL; item = item->li_next)
1794 {
1795 char_u *s = get_tv_string(&item->li_tv);
1796#ifdef WIN3264
1797 char_u *tmp = s;
1798
1799 if (!enc_utf8 && enc_codepage > 0)
1800 {
1801 WCHAR *ret = NULL;
1802 int length = 0;
1803
1804 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1805 (int)STRLEN(s), &ret, &length);
1806 if (ret != NULL)
1807 {
1808 WideCharToMultiByte_alloc(CP_UTF8, 0,
1809 ret, length, (char **)&s, &length, 0, 0);
1810 vim_free(ret);
1811 }
1812 }
1813#endif
1814 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1815 s, (int)STRLEN(s), NULL);
1816#ifdef WIN3264
1817 if (tmp != s)
1818 vim_free(s);
1819#endif
1820
1821 if (item->li_next != NULL || type == MLINE)
1822 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1823 (char_u *)"\r", 1, NULL);
1824 }
1825 list_free(l);
1826 }
1827}
1828
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001829/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001830 * Return TRUE when waiting for a character in the terminal, the cursor of the
1831 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001832 */
1833 int
1834terminal_is_active()
1835{
1836 return in_terminal_loop != NULL;
1837}
1838
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001839#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001840 cursorentry_T *
1841term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1842{
1843 term_T *term = in_terminal_loop;
1844 static cursorentry_T entry;
1845
1846 vim_memset(&entry, 0, sizeof(entry));
1847 entry.shape = entry.mshape =
1848 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1849 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1850 SHAPE_BLOCK;
1851 entry.percentage = 20;
1852 if (term->tl_cursor_blink)
1853 {
1854 entry.blinkwait = 700;
1855 entry.blinkon = 400;
1856 entry.blinkoff = 250;
1857 }
1858 *fg = gui.back_pixel;
1859 if (term->tl_cursor_color == NULL)
1860 *bg = gui.norm_pixel;
1861 else
1862 *bg = color_name2handle(term->tl_cursor_color);
1863 entry.name = "n";
1864 entry.used_for = SHAPE_CURSOR;
1865
1866 return &entry;
1867}
1868#endif
1869
Bram Moolenaard317b382018-02-08 22:33:31 +01001870 static void
1871may_output_cursor_props(void)
1872{
1873 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1874 || last_set_cursor_shape != desired_cursor_shape
1875 || last_set_cursor_blink != desired_cursor_blink)
1876 {
1877 last_set_cursor_color = desired_cursor_color;
1878 last_set_cursor_shape = desired_cursor_shape;
1879 last_set_cursor_blink = desired_cursor_blink;
1880 term_cursor_color(desired_cursor_color);
1881 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1882 /* this will restore the initial cursor style, if possible */
1883 ui_cursor_shape_forced(TRUE);
1884 else
1885 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1886 }
1887}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001888
Bram Moolenaard317b382018-02-08 22:33:31 +01001889/*
1890 * Set the cursor color and shape, if not last set to these.
1891 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001892 static void
1893may_set_cursor_props(term_T *term)
1894{
1895#ifdef FEAT_GUI
1896 /* For the GUI the cursor properties are obtained with
1897 * term_get_cursor_shape(). */
1898 if (gui.in_use)
1899 return;
1900#endif
1901 if (in_terminal_loop == term)
1902 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001903 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001904 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001905 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001906 desired_cursor_color = (char_u *)"";
1907 desired_cursor_shape = term->tl_cursor_shape;
1908 desired_cursor_blink = term->tl_cursor_blink;
1909 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001910 }
1911}
1912
Bram Moolenaard317b382018-02-08 22:33:31 +01001913/*
1914 * Reset the desired cursor properties and restore them when needed.
1915 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001916 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001917prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001918{
1919#ifdef FEAT_GUI
1920 if (gui.in_use)
1921 return;
1922#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001923 desired_cursor_color = (char_u *)"";
1924 desired_cursor_shape = -1;
1925 desired_cursor_blink = -1;
1926 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001927}
1928
1929/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001930 * Returns TRUE if the current window contains a terminal and we are sending
1931 * keys to the job.
1932 * If "check_job_status" is TRUE update the job status.
1933 */
1934 static int
1935term_use_loop_check(int check_job_status)
1936{
1937 term_T *term = curbuf->b_term;
1938
1939 return term != NULL
1940 && !term->tl_normal_mode
1941 && term->tl_vterm != NULL
1942 && term_job_running_check(term, check_job_status);
1943}
1944
1945/*
1946 * Returns TRUE if the current window contains a terminal and we are sending
1947 * keys to the job.
1948 */
1949 int
1950term_use_loop(void)
1951{
1952 return term_use_loop_check(FALSE);
1953}
1954
1955/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001956 * Called when entering a window with the mouse. If this is a terminal window
1957 * we may want to change state.
1958 */
1959 void
1960term_win_entered()
1961{
1962 term_T *term = curbuf->b_term;
1963
1964 if (term != NULL)
1965 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001966 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001967 {
1968 reset_VIsual_and_resel();
1969 if (State & INSERT)
1970 stop_insert_mode = TRUE;
1971 }
1972 mouse_was_outside = FALSE;
1973 enter_mouse_col = mouse_col;
1974 enter_mouse_row = mouse_row;
1975 }
1976}
1977
1978/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001979 * Wait for input and send it to the job.
1980 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1981 * when there is no more typahead.
1982 * Return when the start of a CTRL-W command is typed or anything else that
1983 * should be handled as a Normal mode command.
1984 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1985 * the terminal was closed.
1986 */
1987 int
1988terminal_loop(int blocking)
1989{
1990 int c;
1991 int termkey = 0;
1992 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001993#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001994 int tty_fd = curbuf->b_term->tl_job->jv_channel
1995 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001996#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001997 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001998
1999 /* Remember the terminal we are sending keys to. However, the terminal
2000 * might be closed while waiting for a character, e.g. typing "exit" in a
2001 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2002 * stored reference. */
2003 in_terminal_loop = curbuf->b_term;
2004
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002005 if (*curwin->w_p_twk != NUL)
2006 termkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002007 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2008 may_set_cursor_props(curbuf->b_term);
2009
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002010 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002011 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002012#ifdef FEAT_GUI
2013 if (!curbuf->b_term->tl_system)
2014#endif
2015 /* TODO: skip screen update when handling a sequence of keys. */
2016 /* Repeat redrawing in case a message is received while redrawing.
2017 */
2018 while (must_redraw != 0)
2019 if (update_screen(0) == FAIL)
2020 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002021 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002022 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002023
2024 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002025 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002026 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002027 /* Job finished while waiting for a character. Push back the
2028 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002029 if (c != K_IGNORE)
2030 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002031 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002032 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002033 if (c == K_IGNORE)
2034 continue;
2035
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002036#ifdef UNIX
2037 /*
2038 * The shell or another program may change the tty settings. Getting
2039 * them for every typed character is a bit of overhead, but it's needed
2040 * for the first character typed, e.g. when Vim starts in a shell.
2041 */
2042 if (isatty(tty_fd))
2043 {
2044 ttyinfo_T info;
2045
2046 /* Get the current backspace character of the pty. */
2047 if (get_tty_info(tty_fd, &info) == OK)
2048 term_backspace_char = info.backspace;
2049 }
2050#endif
2051
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002052#ifdef WIN3264
2053 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2054 * Use CTRL-BREAK to kill the job. */
2055 if (ctrl_break_was_pressed)
2056 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2057#endif
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002058 /* Was either CTRL-W (termkey) or CTRL-\ pressed?
2059 * Not in a system terminal. */
2060 if ((c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2061#ifdef FEAT_GUI
2062 && !curbuf->b_term->tl_system
2063#endif
2064 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002065 {
2066 int prev_c = c;
2067
2068#ifdef FEAT_CMDL_INFO
2069 if (add_to_showcmd(c))
2070 out_flush();
2071#endif
2072 c = term_vgetc();
2073#ifdef FEAT_CMDL_INFO
2074 clear_showcmd();
2075#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002076 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002077 /* job finished while waiting for a character */
2078 break;
2079
2080 if (prev_c == Ctrl_BSL)
2081 {
2082 if (c == Ctrl_N)
2083 {
2084 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2085 term_enter_normal_mode();
2086 ret = FAIL;
2087 goto theend;
2088 }
2089 /* Send both keys to the terminal. */
2090 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2091 }
2092 else if (c == Ctrl_C)
2093 {
2094 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2095 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2096 }
2097 else if (termkey == 0 && c == '.')
2098 {
2099 /* "CTRL-W .": send CTRL-W to the job */
2100 c = Ctrl_W;
2101 }
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002102 else if (termkey == 0 && c == Ctrl_BSL)
2103 {
2104 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2105 c = Ctrl_BSL;
2106 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002107 else if (c == 'N')
2108 {
2109 /* CTRL-W N : go to Terminal-Normal mode. */
2110 term_enter_normal_mode();
2111 ret = FAIL;
2112 goto theend;
2113 }
2114 else if (c == '"')
2115 {
2116 term_paste_register(prev_c);
2117 continue;
2118 }
2119 else if (termkey == 0 || c != termkey)
2120 {
2121 stuffcharReadbuff(Ctrl_W);
2122 stuffcharReadbuff(c);
2123 ret = OK;
2124 goto theend;
2125 }
2126 }
2127# ifdef WIN3264
2128 if (!enc_utf8 && has_mbyte && c >= 0x80)
2129 {
2130 WCHAR wc;
2131 char_u mb[3];
2132
2133 mb[0] = (unsigned)c >> 8;
2134 mb[1] = c;
2135 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2136 c = wc;
2137 }
2138# endif
2139 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2140 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002141 if (c == K_MOUSEMOVE)
2142 /* We are sure to come back here, don't reset the cursor color
2143 * and shape to avoid flickering. */
2144 restore_cursor = FALSE;
2145
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002146 ret = OK;
2147 goto theend;
2148 }
2149 }
2150 ret = FAIL;
2151
2152theend:
2153 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002154 if (restore_cursor)
2155 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002156 return ret;
2157}
2158
2159/*
2160 * Called when a job has finished.
2161 * This updates the title and status, but does not close the vterm, because
2162 * there might still be pending output in the channel.
2163 */
2164 void
2165term_job_ended(job_T *job)
2166{
2167 term_T *term;
2168 int did_one = FALSE;
2169
2170 for (term = first_term; term != NULL; term = term->tl_next)
2171 if (term->tl_job == job)
2172 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002173 VIM_CLEAR(term->tl_title);
2174 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002175 redraw_buf_and_status_later(term->tl_buffer, VALID);
2176 did_one = TRUE;
2177 }
2178 if (did_one)
2179 redraw_statuslines();
2180 if (curbuf->b_term != NULL)
2181 {
2182 if (curbuf->b_term->tl_job == job)
2183 maketitle();
2184 update_cursor(curbuf->b_term, TRUE);
2185 }
2186}
2187
2188 static void
2189may_toggle_cursor(term_T *term)
2190{
2191 if (in_terminal_loop == term)
2192 {
2193 if (term->tl_cursor_visible)
2194 cursor_on();
2195 else
2196 cursor_off();
2197 }
2198}
2199
2200/*
2201 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002202 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002203 */
2204 static int
2205color2index(VTermColor *color, int fg, int *boldp)
2206{
2207 int red = color->red;
2208 int blue = color->blue;
2209 int green = color->green;
2210
Bram Moolenaar46359e12017-11-29 22:33:38 +01002211 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002212 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002213 /* First 16 colors and default: use the ANSI index, because these
2214 * colors can be redefined. */
2215 if (t_colors >= 16)
2216 return color->ansi_index;
2217 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002218 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002219 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002220 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002221 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2222 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2223 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002224 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002225 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2226 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2227 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2228 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2229 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2230 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2231 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2232 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2233 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2234 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2235 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002236 }
2237 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002238
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002239 if (t_colors >= 256)
2240 {
2241 if (red == blue && red == green)
2242 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002243 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002244 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002245 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2246 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2247 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002248 int i;
2249
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002250 if (red < 5)
2251 return 17; /* 00/00/00 */
2252 if (red > 245) /* ff/ff/ff */
2253 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002254 for (i = 0; i < 23; ++i)
2255 if (red < cutoff[i])
2256 return i + 233;
2257 return 256;
2258 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002259 {
2260 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2261 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002262
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002263 /* 216-color cube */
2264 for (ri = 0; ri < 5; ++ri)
2265 if (red < cutoff[ri])
2266 break;
2267 for (gi = 0; gi < 5; ++gi)
2268 if (green < cutoff[gi])
2269 break;
2270 for (bi = 0; bi < 5; ++bi)
2271 if (blue < cutoff[bi])
2272 break;
2273 return 17 + ri * 36 + gi * 6 + bi;
2274 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002275 }
2276 return 0;
2277}
2278
2279/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002280 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002281 */
2282 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002283vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002284{
2285 int attr = 0;
2286
2287 if (cellattrs.bold)
2288 attr |= HL_BOLD;
2289 if (cellattrs.underline)
2290 attr |= HL_UNDERLINE;
2291 if (cellattrs.italic)
2292 attr |= HL_ITALIC;
2293 if (cellattrs.strike)
2294 attr |= HL_STRIKETHROUGH;
2295 if (cellattrs.reverse)
2296 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002297 return attr;
2298}
2299
2300/*
2301 * Store Vterm attributes in "cell" from highlight flags.
2302 */
2303 static void
2304hl2vtermAttr(int attr, cellattr_T *cell)
2305{
2306 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2307 if (attr & HL_BOLD)
2308 cell->attrs.bold = 1;
2309 if (attr & HL_UNDERLINE)
2310 cell->attrs.underline = 1;
2311 if (attr & HL_ITALIC)
2312 cell->attrs.italic = 1;
2313 if (attr & HL_STRIKETHROUGH)
2314 cell->attrs.strike = 1;
2315 if (attr & HL_INVERSE)
2316 cell->attrs.reverse = 1;
2317}
2318
2319/*
2320 * Convert the attributes of a vterm cell into an attribute index.
2321 */
2322 static int
2323cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2324{
2325 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002326
2327#ifdef FEAT_GUI
2328 if (gui.in_use)
2329 {
2330 guicolor_T fg, bg;
2331
2332 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2333 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2334 return get_gui_attr_idx(attr, fg, bg);
2335 }
2336 else
2337#endif
2338#ifdef FEAT_TERMGUICOLORS
2339 if (p_tgc)
2340 {
2341 guicolor_T fg, bg;
2342
2343 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2344 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2345
2346 return get_tgc_attr_idx(attr, fg, bg);
2347 }
2348 else
2349#endif
2350 {
2351 int bold = MAYBE;
2352 int fg = color2index(&cellfg, TRUE, &bold);
2353 int bg = color2index(&cellbg, FALSE, &bold);
2354
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002355 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002356 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002357 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002358 if (fg == 0 && term_default_cterm_fg >= 0)
2359 fg = term_default_cterm_fg + 1;
2360 if (bg == 0 && term_default_cterm_bg >= 0)
2361 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002362 }
2363
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002364 /* with 8 colors set the bold attribute to get a bright foreground */
2365 if (bold == TRUE)
2366 attr |= HL_BOLD;
2367 return get_cterm_attr_idx(attr, fg, bg);
2368 }
2369 return 0;
2370}
2371
2372 static int
2373handle_damage(VTermRect rect, void *user)
2374{
2375 term_T *term = (term_T *)user;
2376
2377 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2378 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2379 redraw_buf_later(term->tl_buffer, NOT_VALID);
2380 return 1;
2381}
2382
2383 static int
2384handle_moverect(VTermRect dest, VTermRect src, void *user)
2385{
2386 term_T *term = (term_T *)user;
2387
2388 /* Scrolling up is done much more efficiently by deleting lines instead of
2389 * redrawing the text. */
2390 if (dest.start_col == src.start_col
2391 && dest.end_col == src.end_col
2392 && dest.start_row < src.start_row)
2393 {
2394 win_T *wp;
2395 VTermColor fg, bg;
2396 VTermScreenCellAttrs attr;
2397 int clear_attr;
2398
2399 /* Set the color to clear lines with. */
2400 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2401 &fg, &bg);
2402 vim_memset(&attr, 0, sizeof(attr));
2403 clear_attr = cell2attr(attr, fg, bg);
2404
2405 FOR_ALL_WINDOWS(wp)
2406 {
2407 if (wp->w_buffer == term->tl_buffer)
2408 win_del_lines(wp, dest.start_row,
2409 src.start_row - dest.start_row, FALSE, FALSE,
2410 clear_attr);
2411 }
2412 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002413
2414 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2415 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2416
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002417 redraw_buf_later(term->tl_buffer, NOT_VALID);
2418 return 1;
2419}
2420
2421 static int
2422handle_movecursor(
2423 VTermPos pos,
2424 VTermPos oldpos UNUSED,
2425 int visible,
2426 void *user)
2427{
2428 term_T *term = (term_T *)user;
2429 win_T *wp;
2430
2431 term->tl_cursor_pos = pos;
2432 term->tl_cursor_visible = visible;
2433
2434 FOR_ALL_WINDOWS(wp)
2435 {
2436 if (wp->w_buffer == term->tl_buffer)
2437 position_cursor(wp, &pos);
2438 }
2439 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2440 {
2441 may_toggle_cursor(term);
2442 update_cursor(term, term->tl_cursor_visible);
2443 }
2444
2445 return 1;
2446}
2447
2448 static int
2449handle_settermprop(
2450 VTermProp prop,
2451 VTermValue *value,
2452 void *user)
2453{
2454 term_T *term = (term_T *)user;
2455
2456 switch (prop)
2457 {
2458 case VTERM_PROP_TITLE:
2459 vim_free(term->tl_title);
2460 /* a blank title isn't useful, make it empty, so that "running" is
2461 * displayed */
2462 if (*skipwhite((char_u *)value->string) == NUL)
2463 term->tl_title = NULL;
2464#ifdef WIN3264
2465 else if (!enc_utf8 && enc_codepage > 0)
2466 {
2467 WCHAR *ret = NULL;
2468 int length = 0;
2469
2470 MultiByteToWideChar_alloc(CP_UTF8, 0,
2471 (char*)value->string, (int)STRLEN(value->string),
2472 &ret, &length);
2473 if (ret != NULL)
2474 {
2475 WideCharToMultiByte_alloc(enc_codepage, 0,
2476 ret, length, (char**)&term->tl_title,
2477 &length, 0, 0);
2478 vim_free(ret);
2479 }
2480 }
2481#endif
2482 else
2483 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002484 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002485 if (term == curbuf->b_term)
2486 maketitle();
2487 break;
2488
2489 case VTERM_PROP_CURSORVISIBLE:
2490 term->tl_cursor_visible = value->boolean;
2491 may_toggle_cursor(term);
2492 out_flush();
2493 break;
2494
2495 case VTERM_PROP_CURSORBLINK:
2496 term->tl_cursor_blink = value->boolean;
2497 may_set_cursor_props(term);
2498 break;
2499
2500 case VTERM_PROP_CURSORSHAPE:
2501 term->tl_cursor_shape = value->number;
2502 may_set_cursor_props(term);
2503 break;
2504
2505 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002506 if (desired_cursor_color == term->tl_cursor_color)
2507 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002508 vim_free(term->tl_cursor_color);
2509 if (*value->string == NUL)
2510 term->tl_cursor_color = NULL;
2511 else
2512 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2513 may_set_cursor_props(term);
2514 break;
2515
2516 case VTERM_PROP_ALTSCREEN:
2517 /* TODO: do anything else? */
2518 term->tl_using_altscreen = value->boolean;
2519 break;
2520
2521 default:
2522 break;
2523 }
2524 /* Always return 1, otherwise vterm doesn't store the value internally. */
2525 return 1;
2526}
2527
2528/*
2529 * The job running in the terminal resized the terminal.
2530 */
2531 static int
2532handle_resize(int rows, int cols, void *user)
2533{
2534 term_T *term = (term_T *)user;
2535 win_T *wp;
2536
2537 term->tl_rows = rows;
2538 term->tl_cols = cols;
2539 if (term->tl_vterm_size_changed)
2540 /* Size was set by vterm_set_size(), don't set the window size. */
2541 term->tl_vterm_size_changed = FALSE;
2542 else
2543 {
2544 FOR_ALL_WINDOWS(wp)
2545 {
2546 if (wp->w_buffer == term->tl_buffer)
2547 {
2548 win_setheight_win(rows, wp);
2549 win_setwidth_win(cols, wp);
2550 }
2551 }
2552 redraw_buf_later(term->tl_buffer, NOT_VALID);
2553 }
2554 return 1;
2555}
2556
2557/*
2558 * Handle a line that is pushed off the top of the screen.
2559 */
2560 static int
2561handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2562{
2563 term_T *term = (term_T *)user;
2564
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002565 /* If the number of lines that are stored goes over 'termscrollback' then
2566 * delete the first 10%. */
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002567 if (term->tl_scrollback.ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002568 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002569 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002570 int i;
2571
2572 curbuf = term->tl_buffer;
2573 for (i = 0; i < todo; ++i)
2574 {
2575 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2576 ml_delete(1, FALSE);
2577 }
2578 curbuf = curwin->w_buffer;
2579
2580 term->tl_scrollback.ga_len -= todo;
2581 mch_memmove(term->tl_scrollback.ga_data,
2582 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2583 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2584 }
2585
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002586 if (ga_grow(&term->tl_scrollback, 1) == OK)
2587 {
2588 cellattr_T *p = NULL;
2589 int len = 0;
2590 int i;
2591 int c;
2592 int col;
2593 sb_line_T *line;
2594 garray_T ga;
2595 cellattr_T fill_attr = term->tl_default_color;
2596
2597 /* do not store empty cells at the end */
2598 for (i = 0; i < cols; ++i)
2599 if (cells[i].chars[0] != 0)
2600 len = i + 1;
2601 else
2602 cell2cellattr(&cells[i], &fill_attr);
2603
2604 ga_init2(&ga, 1, 100);
2605 if (len > 0)
2606 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2607 if (p != NULL)
2608 {
2609 for (col = 0; col < len; col += cells[col].width)
2610 {
2611 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2612 {
2613 ga.ga_len = 0;
2614 break;
2615 }
2616 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2617 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2618 (char_u *)ga.ga_data + ga.ga_len);
2619 cell2cellattr(&cells[col], &p[col]);
2620 }
2621 }
2622 if (ga_grow(&ga, 1) == FAIL)
2623 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2624 else
2625 {
2626 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2627 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2628 }
2629 ga_clear(&ga);
2630
2631 line = (sb_line_T *)term->tl_scrollback.ga_data
2632 + term->tl_scrollback.ga_len;
2633 line->sb_cols = len;
2634 line->sb_cells = p;
2635 line->sb_fill_attr = fill_attr;
2636 ++term->tl_scrollback.ga_len;
2637 ++term->tl_scrollback_scrolled;
2638 }
2639 return 0; /* ignored */
2640}
2641
2642static VTermScreenCallbacks screen_callbacks = {
2643 handle_damage, /* damage */
2644 handle_moverect, /* moverect */
2645 handle_movecursor, /* movecursor */
2646 handle_settermprop, /* settermprop */
2647 NULL, /* bell */
2648 handle_resize, /* resize */
2649 handle_pushline, /* sb_pushline */
2650 NULL /* sb_popline */
2651};
2652
2653/*
2654 * Called when a channel has been closed.
2655 * If this was a channel for a terminal window then finish it up.
2656 */
2657 void
2658term_channel_closed(channel_T *ch)
2659{
2660 term_T *term;
2661 int did_one = FALSE;
2662
2663 for (term = first_term; term != NULL; term = term->tl_next)
2664 if (term->tl_job == ch->ch_job)
2665 {
2666 term->tl_channel_closed = TRUE;
2667 did_one = TRUE;
2668
Bram Moolenaard23a8232018-02-10 18:45:26 +01002669 VIM_CLEAR(term->tl_title);
2670 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002671
2672 /* Unless in Terminal-Normal mode: clear the vterm. */
2673 if (!term->tl_normal_mode)
2674 {
2675 int fnum = term->tl_buffer->b_fnum;
2676
2677 cleanup_vterm(term);
2678
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002679 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002680 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002681 aco_save_T aco;
2682
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002683 /* ++close or term_finish == "close" */
2684 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002685 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002686 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002687 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002688 break;
2689 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002690 if (term->tl_finish == TL_FINISH_OPEN
2691 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002692 {
2693 char buf[50];
2694
2695 /* TODO: use term_opencmd */
2696 ch_log(NULL, "terminal job finished, opening window");
2697 vim_snprintf(buf, sizeof(buf),
2698 term->tl_opencmd == NULL
2699 ? "botright sbuf %d"
2700 : (char *)term->tl_opencmd, fnum);
2701 do_cmdline_cmd((char_u *)buf);
2702 }
2703 else
2704 ch_log(NULL, "terminal job finished");
2705 }
2706
2707 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2708 }
2709 if (did_one)
2710 {
2711 redraw_statuslines();
2712
2713 /* Need to break out of vgetc(). */
2714 ins_char_typebuf(K_IGNORE);
2715 typebuf_was_filled = TRUE;
2716
2717 term = curbuf->b_term;
2718 if (term != NULL)
2719 {
2720 if (term->tl_job == ch->ch_job)
2721 maketitle();
2722 update_cursor(term, term->tl_cursor_visible);
2723 }
2724 }
2725}
2726
2727/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002728 * Fill one screen line from a line of the terminal.
2729 * Advances "pos" to past the last column.
2730 */
2731 static void
2732term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2733{
2734 int off = screen_get_current_line_off();
2735
2736 for (pos->col = 0; pos->col < max_col; )
2737 {
2738 VTermScreenCell cell;
2739 int c;
2740
2741 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2742 vim_memset(&cell, 0, sizeof(cell));
2743
2744 c = cell.chars[0];
2745 if (c == NUL)
2746 {
2747 ScreenLines[off] = ' ';
2748 if (enc_utf8)
2749 ScreenLinesUC[off] = NUL;
2750 }
2751 else
2752 {
2753 if (enc_utf8)
2754 {
2755 int i;
2756
2757 /* composing chars */
2758 for (i = 0; i < Screen_mco
2759 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2760 {
2761 ScreenLinesC[i][off] = cell.chars[i + 1];
2762 if (cell.chars[i + 1] == 0)
2763 break;
2764 }
2765 if (c >= 0x80 || (Screen_mco > 0
2766 && ScreenLinesC[0][off] != 0))
2767 {
2768 ScreenLines[off] = ' ';
2769 ScreenLinesUC[off] = c;
2770 }
2771 else
2772 {
2773 ScreenLines[off] = c;
2774 ScreenLinesUC[off] = NUL;
2775 }
2776 }
2777#ifdef WIN3264
2778 else if (has_mbyte && c >= 0x80)
2779 {
2780 char_u mb[MB_MAXBYTES+1];
2781 WCHAR wc = c;
2782
2783 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2784 (char*)mb, 2, 0, 0) > 1)
2785 {
2786 ScreenLines[off] = mb[0];
2787 ScreenLines[off + 1] = mb[1];
2788 cell.width = mb_ptr2cells(mb);
2789 }
2790 else
2791 ScreenLines[off] = c;
2792 }
2793#endif
2794 else
2795 ScreenLines[off] = c;
2796 }
2797 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2798
2799 ++pos->col;
2800 ++off;
2801 if (cell.width == 2)
2802 {
2803 if (enc_utf8)
2804 ScreenLinesUC[off] = NUL;
2805
2806 /* don't set the second byte to NUL for a DBCS encoding, it
2807 * has been set above */
2808 if (enc_utf8 || !has_mbyte)
2809 ScreenLines[off] = NUL;
2810
2811 ++pos->col;
2812 ++off;
2813 }
2814 }
2815}
2816
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002817#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002818 static void
2819update_system_term(term_T *term)
2820{
2821 VTermPos pos;
2822 VTermScreen *screen;
2823
2824 if (term->tl_vterm == NULL)
2825 return;
2826 screen = vterm_obtain_screen(term->tl_vterm);
2827
2828 /* Scroll up to make more room for terminal lines if needed. */
2829 while (term->tl_toprow > 0
2830 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2831 {
2832 int save_p_more = p_more;
2833
2834 p_more = FALSE;
2835 msg_row = Rows - 1;
2836 msg_puts((char_u *)"\n");
2837 p_more = save_p_more;
2838 --term->tl_toprow;
2839 }
2840
2841 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2842 && pos.row < Rows; ++pos.row)
2843 {
2844 if (pos.row < term->tl_rows)
2845 {
2846 int max_col = MIN(Columns, term->tl_cols);
2847
2848 term_line2screenline(screen, &pos, max_col);
2849 }
2850 else
2851 pos.col = 0;
2852
2853 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2854 }
2855
2856 term->tl_dirty_row_start = MAX_ROW;
2857 term->tl_dirty_row_end = 0;
2858 update_cursor(term, TRUE);
2859}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002860#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002861
2862/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002863 * Called to update a window that contains an active terminal.
2864 * Returns FAIL when there is no terminal running in this window or in
2865 * Terminal-Normal mode.
2866 */
2867 int
2868term_update_window(win_T *wp)
2869{
2870 term_T *term = wp->w_buffer->b_term;
2871 VTerm *vterm;
2872 VTermScreen *screen;
2873 VTermState *state;
2874 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002875 int rows, cols;
2876 int newrows, newcols;
2877 int minsize;
2878 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002879
2880 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2881 return FAIL;
2882
2883 vterm = term->tl_vterm;
2884 screen = vterm_obtain_screen(vterm);
2885 state = vterm_obtain_state(vterm);
2886
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002887 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002888 {
2889 term->tl_dirty_row_start = 0;
2890 term->tl_dirty_row_end = MAX_ROW;
2891 }
2892
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002893 /*
2894 * If the window was resized a redraw will be triggered and we get here.
2895 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2896 */
Bram Moolenaar498c2562018-04-15 23:45:15 +02002897 minsize = parse_termsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002898
Bram Moolenaar498c2562018-04-15 23:45:15 +02002899 newrows = 99999;
2900 newcols = 99999;
2901 FOR_ALL_WINDOWS(twp)
2902 {
2903 /* When more than one window shows the same terminal, use the
2904 * smallest size. */
2905 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002906 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02002907 newrows = MIN(newrows, twp->w_height);
2908 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002909 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02002910 }
2911 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
2912 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
2913
2914 if (term->tl_rows != newrows || term->tl_cols != newcols)
2915 {
2916
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002917
2918 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002919 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002920 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02002921 newrows);
2922 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002923 }
2924
2925 /* The cursor may have been moved when resizing. */
2926 vterm_state_get_cursorpos(state, &pos);
2927 position_cursor(wp, &pos);
2928
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002929 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2930 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002931 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002932 if (pos.row < term->tl_rows)
2933 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002934 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002935
Bram Moolenaar13568252018-03-16 20:46:58 +01002936 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002937 }
2938 else
2939 pos.col = 0;
2940
Bram Moolenaarf118d482018-03-13 13:14:00 +01002941 screen_line(wp->w_winrow + pos.row
2942#ifdef FEAT_MENU
2943 + winbar_height(wp)
2944#endif
2945 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002946 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002947 term->tl_dirty_row_start = MAX_ROW;
2948 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002949
2950 return OK;
2951}
2952
2953/*
2954 * Return TRUE if "wp" is a terminal window where the job has finished.
2955 */
2956 int
2957term_is_finished(buf_T *buf)
2958{
2959 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2960}
2961
2962/*
2963 * Return TRUE if "wp" is a terminal window where the job has finished or we
2964 * are in Terminal-Normal mode, thus we show the buffer contents.
2965 */
2966 int
2967term_show_buffer(buf_T *buf)
2968{
2969 term_T *term = buf->b_term;
2970
2971 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2972}
2973
2974/*
2975 * The current buffer is going to be changed. If there is terminal
2976 * highlighting remove it now.
2977 */
2978 void
2979term_change_in_curbuf(void)
2980{
2981 term_T *term = curbuf->b_term;
2982
2983 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2984 {
2985 free_scrollback(term);
2986 redraw_buf_later(term->tl_buffer, NOT_VALID);
2987
2988 /* The buffer is now like a normal buffer, it cannot be easily
2989 * abandoned when changed. */
2990 set_string_option_direct((char_u *)"buftype", -1,
2991 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2992 }
2993}
2994
2995/*
2996 * Get the screen attribute for a position in the buffer.
2997 * Use a negative "col" to get the filler background color.
2998 */
2999 int
3000term_get_attr(buf_T *buf, linenr_T lnum, int col)
3001{
3002 term_T *term = buf->b_term;
3003 sb_line_T *line;
3004 cellattr_T *cellattr;
3005
3006 if (lnum > term->tl_scrollback.ga_len)
3007 cellattr = &term->tl_default_color;
3008 else
3009 {
3010 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3011 if (col < 0 || col >= line->sb_cols)
3012 cellattr = &line->sb_fill_attr;
3013 else
3014 cellattr = line->sb_cells + col;
3015 }
3016 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3017}
3018
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003019/*
3020 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003021 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003022 */
3023 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003024cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003025{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003026 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003027}
3028
3029/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003030 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003031 */
3032 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003033init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003034{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003035 VTermColor *fg, *bg;
3036 int fgval, bgval;
3037 int id;
3038
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003039 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3040 term->tl_default_color.width = 1;
3041 fg = &term->tl_default_color.fg;
3042 bg = &term->tl_default_color.bg;
3043
3044 /* Vterm uses a default black background. Set it to white when
3045 * 'background' is "light". */
3046 if (*p_bg == 'l')
3047 {
3048 fgval = 0;
3049 bgval = 255;
3050 }
3051 else
3052 {
3053 fgval = 255;
3054 bgval = 0;
3055 }
3056 fg->red = fg->green = fg->blue = fgval;
3057 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003058 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003059
3060 /* The "Terminal" highlight group overrules the defaults. */
3061 id = syn_name2id((char_u *)"Terminal");
3062
Bram Moolenaar46359e12017-11-29 22:33:38 +01003063 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003064#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3065 if (0
3066# ifdef FEAT_GUI
3067 || gui.in_use
3068# endif
3069# ifdef FEAT_TERMGUICOLORS
3070 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003071# ifdef FEAT_VTP
3072 /* Finally get INVALCOLOR on this execution path */
3073 || (!p_tgc && t_colors >= 256)
3074# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003075# endif
3076 )
3077 {
3078 guicolor_T fg_rgb = INVALCOLOR;
3079 guicolor_T bg_rgb = INVALCOLOR;
3080
3081 if (id != 0)
3082 syn_id2colors(id, &fg_rgb, &bg_rgb);
3083
3084# ifdef FEAT_GUI
3085 if (gui.in_use)
3086 {
3087 if (fg_rgb == INVALCOLOR)
3088 fg_rgb = gui.norm_pixel;
3089 if (bg_rgb == INVALCOLOR)
3090 bg_rgb = gui.back_pixel;
3091 }
3092# ifdef FEAT_TERMGUICOLORS
3093 else
3094# endif
3095# endif
3096# ifdef FEAT_TERMGUICOLORS
3097 {
3098 if (fg_rgb == INVALCOLOR)
3099 fg_rgb = cterm_normal_fg_gui_color;
3100 if (bg_rgb == INVALCOLOR)
3101 bg_rgb = cterm_normal_bg_gui_color;
3102 }
3103# endif
3104 if (fg_rgb != INVALCOLOR)
3105 {
3106 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3107
3108 fg->red = (unsigned)(rgb >> 16);
3109 fg->green = (unsigned)(rgb >> 8) & 255;
3110 fg->blue = (unsigned)rgb & 255;
3111 }
3112 if (bg_rgb != INVALCOLOR)
3113 {
3114 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3115
3116 bg->red = (unsigned)(rgb >> 16);
3117 bg->green = (unsigned)(rgb >> 8) & 255;
3118 bg->blue = (unsigned)rgb & 255;
3119 }
3120 }
3121 else
3122#endif
3123 if (id != 0 && t_colors >= 16)
3124 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003125 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003126 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003127 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003128 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003129 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003130 else
3131 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003132#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003133 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003134#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003135
3136 /* In an MS-Windows console we know the normal colors. */
3137 if (cterm_normal_fg_color > 0)
3138 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003139 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003140# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003141 tmp = fg->red;
3142 fg->red = fg->blue;
3143 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003144# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003145 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003146# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003147 else
3148 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003149# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003150
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003151 if (cterm_normal_bg_color > 0)
3152 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003153 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003154# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003155 tmp = bg->red;
3156 bg->red = bg->blue;
3157 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003158# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003159 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003160# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003161 else
3162 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003163# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003164 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003165}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003166
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003167#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3168/*
3169 * Set the 16 ANSI colors from array of RGB values
3170 */
3171 static void
3172set_vterm_palette(VTerm *vterm, long_u *rgb)
3173{
3174 int index = 0;
3175 VTermState *state = vterm_obtain_state(vterm);
3176 for (; index < 16; index++)
3177 {
3178 VTermColor color;
3179 color.red = (unsigned)(rgb[index] >> 16);
3180 color.green = (unsigned)(rgb[index] >> 8) & 255;
3181 color.blue = (unsigned)rgb[index] & 255;
3182 vterm_state_set_palette_color(state, index, &color);
3183 }
3184}
3185
3186/*
3187 * Set the ANSI color palette from a list of colors
3188 */
3189 static int
3190set_ansi_colors_list(VTerm *vterm, list_T *list)
3191{
3192 int n = 0;
3193 long_u rgb[16];
3194 listitem_T *li = list->lv_first;
3195
3196 for (; li != NULL && n < 16; li = li->li_next, n++)
3197 {
3198 char_u *color_name;
3199 guicolor_T guicolor;
3200
3201 color_name = get_tv_string_chk(&li->li_tv);
3202 if (color_name == NULL)
3203 return FAIL;
3204
3205 guicolor = GUI_GET_COLOR(color_name);
3206 if (guicolor == INVALCOLOR)
3207 return FAIL;
3208
3209 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3210 }
3211
3212 if (n != 16 || li != NULL)
3213 return FAIL;
3214
3215 set_vterm_palette(vterm, rgb);
3216
3217 return OK;
3218}
3219
3220/*
3221 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3222 */
3223 static void
3224init_vterm_ansi_colors(VTerm *vterm)
3225{
3226 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3227
3228 if (var != NULL
3229 && (var->di_tv.v_type != VAR_LIST
3230 || var->di_tv.vval.v_list == NULL
3231 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3232 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3233}
3234#endif
3235
Bram Moolenaar52acb112018-03-18 19:20:22 +01003236/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003237 * Handles a "drop" command from the job in the terminal.
3238 * "item" is the file name, "item->li_next" may have options.
3239 */
3240 static void
3241handle_drop_command(listitem_T *item)
3242{
3243 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003244 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003245 int bufnr;
3246 win_T *wp;
3247 tabpage_T *tp;
3248 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003249 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003250
3251 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3252 FOR_ALL_TAB_WINDOWS(tp, wp)
3253 {
3254 if (wp->w_buffer->b_fnum == bufnr)
3255 {
3256 /* buffer is in a window already, go there */
3257 goto_tabpage_win(tp, wp);
3258 return;
3259 }
3260 }
3261
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003262 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003263
3264 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3265 && opt_item->li_tv.vval.v_dict != NULL)
3266 {
3267 dict_T *dict = opt_item->li_tv.vval.v_dict;
3268 char_u *p;
3269
3270 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3271 if (p == NULL)
3272 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3273 if (p != NULL)
3274 {
3275 if (check_ff_value(p) == FAIL)
3276 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3277 else
3278 ea.force_ff = *p;
3279 }
3280 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3281 if (p == NULL)
3282 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3283 if (p != NULL)
3284 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003285 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003286 if (ea.cmd != NULL)
3287 {
3288 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3289 ea.force_enc = 11;
3290 tofree = ea.cmd;
3291 }
3292 }
3293
3294 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3295 if (p != NULL)
3296 get_bad_opt(p, &ea);
3297
3298 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3299 ea.force_bin = FORCE_BIN;
3300 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3301 ea.force_bin = FORCE_BIN;
3302 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3303 ea.force_bin = FORCE_NOBIN;
3304 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3305 ea.force_bin = FORCE_NOBIN;
3306 }
3307
3308 /* open in new window, like ":split fname" */
3309 if (ea.cmd == NULL)
3310 ea.cmd = (char_u *)"split";
3311 ea.arg = fname;
3312 ea.cmdidx = CMD_split;
3313 ex_splitview(&ea);
3314
3315 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003316}
3317
3318/*
3319 * Handles a function call from the job running in a terminal.
3320 * "item" is the function name, "item->li_next" has the arguments.
3321 */
3322 static void
3323handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3324{
3325 char_u *func;
3326 typval_T argvars[2];
3327 typval_T rettv;
3328 int doesrange;
3329
3330 if (item->li_next == NULL)
3331 {
3332 ch_log(channel, "Missing function arguments for call");
3333 return;
3334 }
3335 func = get_tv_string(&item->li_tv);
3336
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003337 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003338 {
3339 ch_log(channel, "Invalid function name: %s", func);
3340 return;
3341 }
3342
3343 argvars[0].v_type = VAR_NUMBER;
3344 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3345 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003346 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003347 2, argvars, /* argv_func */ NULL,
3348 /* firstline */ 1, /* lastline */ 1,
3349 &doesrange, /* evaluate */ TRUE,
3350 /* partial */ NULL, /* selfdict */ NULL) == OK)
3351 {
3352 clear_tv(&rettv);
3353 ch_log(channel, "Function %s called", func);
3354 }
3355 else
3356 ch_log(channel, "Calling function %s failed", func);
3357}
3358
3359/*
3360 * Called by libvterm when it cannot recognize an OSC sequence.
3361 * We recognize a terminal API command.
3362 */
3363 static int
3364parse_osc(const char *command, size_t cmdlen, void *user)
3365{
3366 term_T *term = (term_T *)user;
3367 js_read_T reader;
3368 typval_T tv;
3369 channel_T *channel = term->tl_job == NULL ? NULL
3370 : term->tl_job->jv_channel;
3371
3372 /* We recognize only OSC 5 1 ; {command} */
3373 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3374 return 0; /* not handled */
3375
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003376 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003377 if (reader.js_buf == NULL)
3378 return 1;
3379 reader.js_fill = NULL;
3380 reader.js_used = 0;
3381 if (json_decode(&reader, &tv, 0) == OK
3382 && tv.v_type == VAR_LIST
3383 && tv.vval.v_list != NULL)
3384 {
3385 listitem_T *item = tv.vval.v_list->lv_first;
3386
3387 if (item == NULL)
3388 ch_log(channel, "Missing command");
3389 else
3390 {
3391 char_u *cmd = get_tv_string(&item->li_tv);
3392
Bram Moolenaara997b452018-04-17 23:24:06 +02003393 /* Make sure an invoked command doesn't delete the buffer (and the
3394 * terminal) under our fingers. */
3395 ++term->tl_buffer->b_locked;
3396
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003397 item = item->li_next;
3398 if (item == NULL)
3399 ch_log(channel, "Missing argument for %s", cmd);
3400 else if (STRCMP(cmd, "drop") == 0)
3401 handle_drop_command(item);
3402 else if (STRCMP(cmd, "call") == 0)
3403 handle_call_command(term, channel, item);
3404 else
3405 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003406 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003407 }
3408 }
3409 else
3410 ch_log(channel, "Invalid JSON received");
3411
3412 vim_free(reader.js_buf);
3413 clear_tv(&tv);
3414 return 1;
3415}
3416
3417static VTermParserCallbacks parser_fallbacks = {
3418 NULL, /* text */
3419 NULL, /* control */
3420 NULL, /* escape */
3421 NULL, /* csi */
3422 parse_osc, /* osc */
3423 NULL, /* dcs */
3424 NULL /* resize */
3425};
3426
3427/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003428 * Use Vim's allocation functions for vterm so profiling works.
3429 */
3430 static void *
3431vterm_malloc(size_t size, void *data UNUSED)
3432{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003433 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003434}
3435
3436 static void
3437vterm_memfree(void *ptr, void *data UNUSED)
3438{
3439 vim_free(ptr);
3440}
3441
3442static VTermAllocatorFunctions vterm_allocator = {
3443 &vterm_malloc,
3444 &vterm_memfree
3445};
3446
3447/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003448 * Create a new vterm and initialize it.
3449 */
3450 static void
3451create_vterm(term_T *term, int rows, int cols)
3452{
3453 VTerm *vterm;
3454 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003455 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003456 VTermValue value;
3457
Bram Moolenaar756ef112018-04-10 12:04:27 +02003458 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003459 term->tl_vterm = vterm;
3460 screen = vterm_obtain_screen(vterm);
3461 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3462 /* TODO: depends on 'encoding'. */
3463 vterm_set_utf8(vterm, 1);
3464
3465 init_default_colors(term);
3466
3467 vterm_state_set_default_colors(
3468 vterm_obtain_state(vterm),
3469 &term->tl_default_color.fg,
3470 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003471
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003472 if (t_colors >= 16)
3473 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3474
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003475 /* Required to initialize most things. */
3476 vterm_screen_reset(screen, 1 /* hard */);
3477
3478 /* Allow using alternate screen. */
3479 vterm_screen_enable_altscreen(screen, 1);
3480
3481 /* For unix do not use a blinking cursor. In an xterm this causes the
3482 * cursor to blink if it's blinking in the xterm.
3483 * For Windows we respect the system wide setting. */
3484#ifdef WIN3264
3485 if (GetCaretBlinkTime() == INFINITE)
3486 value.boolean = 0;
3487 else
3488 value.boolean = 1;
3489#else
3490 value.boolean = 0;
3491#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003492 state = vterm_obtain_state(vterm);
3493 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3494 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003495}
3496
3497/*
3498 * Return the text to show for the buffer name and status.
3499 */
3500 char_u *
3501term_get_status_text(term_T *term)
3502{
3503 if (term->tl_status_text == NULL)
3504 {
3505 char_u *txt;
3506 size_t len;
3507
3508 if (term->tl_normal_mode)
3509 {
3510 if (term_job_running(term))
3511 txt = (char_u *)_("Terminal");
3512 else
3513 txt = (char_u *)_("Terminal-finished");
3514 }
3515 else if (term->tl_title != NULL)
3516 txt = term->tl_title;
3517 else if (term_none_open(term))
3518 txt = (char_u *)_("active");
3519 else if (term_job_running(term))
3520 txt = (char_u *)_("running");
3521 else
3522 txt = (char_u *)_("finished");
3523 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3524 term->tl_status_text = alloc((int)len);
3525 if (term->tl_status_text != NULL)
3526 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3527 term->tl_buffer->b_fname, txt);
3528 }
3529 return term->tl_status_text;
3530}
3531
3532/*
3533 * Mark references in jobs of terminals.
3534 */
3535 int
3536set_ref_in_term(int copyID)
3537{
3538 int abort = FALSE;
3539 term_T *term;
3540 typval_T tv;
3541
3542 for (term = first_term; term != NULL; term = term->tl_next)
3543 if (term->tl_job != NULL)
3544 {
3545 tv.v_type = VAR_JOB;
3546 tv.vval.v_job = term->tl_job;
3547 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3548 }
3549 return abort;
3550}
3551
3552/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003553 * Cache "Terminal" highlight group colors.
3554 */
3555 void
3556set_terminal_default_colors(int cterm_fg, int cterm_bg)
3557{
3558 term_default_cterm_fg = cterm_fg - 1;
3559 term_default_cterm_bg = cterm_bg - 1;
3560}
3561
3562/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003563 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003564 * Returns NULL when the buffer is not for a terminal window and logs a message
3565 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003566 */
3567 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003568term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003569{
3570 buf_T *buf;
3571
3572 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3573 ++emsg_off;
3574 buf = get_buf_tv(&argvars[0], FALSE);
3575 --emsg_off;
3576 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003577 {
3578 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003579 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003580 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003581 return buf;
3582}
3583
Bram Moolenaard96ff162018-02-18 22:13:29 +01003584 static int
3585same_color(VTermColor *a, VTermColor *b)
3586{
3587 return a->red == b->red
3588 && a->green == b->green
3589 && a->blue == b->blue
3590 && a->ansi_index == b->ansi_index;
3591}
3592
3593 static void
3594dump_term_color(FILE *fd, VTermColor *color)
3595{
3596 fprintf(fd, "%02x%02x%02x%d",
3597 (int)color->red, (int)color->green, (int)color->blue,
3598 (int)color->ansi_index);
3599}
3600
3601/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003602 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003603 *
3604 * Each screen cell in full is:
3605 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3606 * {characters} is a space for an empty cell
3607 * For a double-width character "+" is changed to "*" and the next cell is
3608 * skipped.
3609 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3610 * when "&" use the same as the previous cell.
3611 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3612 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3613 * {color-idx} is a number from 0 to 255
3614 *
3615 * Screen cell with same width, attributes and color as the previous one:
3616 * |{characters}
3617 *
3618 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3619 *
3620 * Repeating the previous screen cell:
3621 * @{count}
3622 */
3623 void
3624f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3625{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003626 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003627 term_T *term;
3628 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003629 int max_height = 0;
3630 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003631 stat_T st;
3632 FILE *fd;
3633 VTermPos pos;
3634 VTermScreen *screen;
3635 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003636 VTermState *state;
3637 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003638
3639 if (check_restricted() || check_secure())
3640 return;
3641 if (buf == NULL)
3642 return;
3643 term = buf->b_term;
3644
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003645 if (argvars[2].v_type != VAR_UNKNOWN)
3646 {
3647 dict_T *d;
3648
3649 if (argvars[2].v_type != VAR_DICT)
3650 {
3651 EMSG(_(e_dictreq));
3652 return;
3653 }
3654 d = argvars[2].vval.v_dict;
3655 if (d != NULL)
3656 {
3657 max_height = get_dict_number(d, (char_u *)"rows");
3658 max_width = get_dict_number(d, (char_u *)"columns");
3659 }
3660 }
3661
Bram Moolenaard96ff162018-02-18 22:13:29 +01003662 fname = get_tv_string_chk(&argvars[1]);
3663 if (fname == NULL)
3664 return;
3665 if (mch_stat((char *)fname, &st) >= 0)
3666 {
3667 EMSG2(_("E953: File exists: %s"), fname);
3668 return;
3669 }
3670
Bram Moolenaard96ff162018-02-18 22:13:29 +01003671 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3672 {
3673 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3674 return;
3675 }
3676
3677 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3678
3679 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003680 state = vterm_obtain_state(term->tl_vterm);
3681 vterm_state_get_cursorpos(state, &cursor_pos);
3682
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003683 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3684 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003685 {
3686 int repeat = 0;
3687
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003688 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3689 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003690 {
3691 VTermScreenCell cell;
3692 int same_attr;
3693 int same_chars = TRUE;
3694 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003695 int is_cursor_pos = (pos.col == cursor_pos.col
3696 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003697
3698 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3699 vim_memset(&cell, 0, sizeof(cell));
3700
3701 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3702 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003703 int c = cell.chars[i];
3704 int pc = prev_cell.chars[i];
3705
3706 /* For the first character NUL is the same as space. */
3707 if (i == 0)
3708 {
3709 c = (c == NUL) ? ' ' : c;
3710 pc = (pc == NUL) ? ' ' : pc;
3711 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003712 if (cell.chars[i] != prev_cell.chars[i])
3713 same_chars = FALSE;
3714 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3715 break;
3716 }
3717 same_attr = vtermAttr2hl(cell.attrs)
3718 == vtermAttr2hl(prev_cell.attrs)
3719 && same_color(&cell.fg, &prev_cell.fg)
3720 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003721 if (same_chars && cell.width == prev_cell.width && same_attr
3722 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003723 {
3724 ++repeat;
3725 }
3726 else
3727 {
3728 if (repeat > 0)
3729 {
3730 fprintf(fd, "@%d", repeat);
3731 repeat = 0;
3732 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003733 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003734
3735 if (cell.chars[0] == NUL)
3736 fputs(" ", fd);
3737 else
3738 {
3739 char_u charbuf[10];
3740 int len;
3741
3742 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3743 && cell.chars[i] != NUL; ++i)
3744 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003745 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003746 fwrite(charbuf, len, 1, fd);
3747 }
3748 }
3749
3750 /* When only the characters differ we don't write anything, the
3751 * following "|", "@" or NL will indicate using the same
3752 * attributes. */
3753 if (cell.width != prev_cell.width || !same_attr)
3754 {
3755 if (cell.width == 2)
3756 {
3757 fputs("*", fd);
3758 ++pos.col;
3759 }
3760 else
3761 fputs("+", fd);
3762
3763 if (same_attr)
3764 {
3765 fputs("&", fd);
3766 }
3767 else
3768 {
3769 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3770 if (same_color(&cell.fg, &prev_cell.fg))
3771 fputs("&", fd);
3772 else
3773 {
3774 fputs("#", fd);
3775 dump_term_color(fd, &cell.fg);
3776 }
3777 if (same_color(&cell.bg, &prev_cell.bg))
3778 fputs("&", fd);
3779 else
3780 {
3781 fputs("#", fd);
3782 dump_term_color(fd, &cell.bg);
3783 }
3784 }
3785 }
3786
3787 prev_cell = cell;
3788 }
3789 }
3790 if (repeat > 0)
3791 fprintf(fd, "@%d", repeat);
3792 fputs("\n", fd);
3793 }
3794
3795 fclose(fd);
3796}
3797
3798/*
3799 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3800 */
3801 static void
3802dump_is_corrupt(garray_T *gap)
3803{
3804 ga_concat(gap, (char_u *)"CORRUPT");
3805}
3806
3807 static void
3808append_cell(garray_T *gap, cellattr_T *cell)
3809{
3810 if (ga_grow(gap, 1) == OK)
3811 {
3812 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3813 ++gap->ga_len;
3814 }
3815}
3816
3817/*
3818 * Read the dump file from "fd" and append lines to the current buffer.
3819 * Return the cell width of the longest line.
3820 */
3821 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003822read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003823{
3824 int c;
3825 garray_T ga_text;
3826 garray_T ga_cell;
3827 char_u *prev_char = NULL;
3828 int attr = 0;
3829 cellattr_T cell;
3830 term_T *term = curbuf->b_term;
3831 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003832 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003833
3834 ga_init2(&ga_text, 1, 90);
3835 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3836 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003837 cursor_pos->row = -1;
3838 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003839
3840 c = fgetc(fd);
3841 for (;;)
3842 {
3843 if (c == EOF)
3844 break;
3845 if (c == '\n')
3846 {
3847 /* End of a line: append it to the buffer. */
3848 if (ga_text.ga_data == NULL)
3849 dump_is_corrupt(&ga_text);
3850 if (ga_grow(&term->tl_scrollback, 1) == OK)
3851 {
3852 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3853 + term->tl_scrollback.ga_len;
3854
3855 if (max_cells < ga_cell.ga_len)
3856 max_cells = ga_cell.ga_len;
3857 line->sb_cols = ga_cell.ga_len;
3858 line->sb_cells = ga_cell.ga_data;
3859 line->sb_fill_attr = term->tl_default_color;
3860 ++term->tl_scrollback.ga_len;
3861 ga_init(&ga_cell);
3862
3863 ga_append(&ga_text, NUL);
3864 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3865 ga_text.ga_len, FALSE);
3866 }
3867 else
3868 ga_clear(&ga_cell);
3869 ga_text.ga_len = 0;
3870
3871 c = fgetc(fd);
3872 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003873 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003874 {
3875 int prev_len = ga_text.ga_len;
3876
Bram Moolenaar9271d052018-02-25 21:39:46 +01003877 if (c == '>')
3878 {
3879 if (cursor_pos->row != -1)
3880 dump_is_corrupt(&ga_text); /* duplicate cursor */
3881 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3882 cursor_pos->col = ga_cell.ga_len;
3883 }
3884
Bram Moolenaard96ff162018-02-18 22:13:29 +01003885 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3886 c = fgetc(fd);
3887 if (c != EOF)
3888 ga_append(&ga_text, c);
3889 for (;;)
3890 {
3891 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003892 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003893 || c == EOF || c == '\n')
3894 break;
3895 ga_append(&ga_text, c);
3896 }
3897
3898 /* save the character for repeating it */
3899 vim_free(prev_char);
3900 if (ga_text.ga_data != NULL)
3901 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3902 ga_text.ga_len - prev_len);
3903
Bram Moolenaar9271d052018-02-25 21:39:46 +01003904 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003905 {
3906 /* use all attributes from previous cell */
3907 }
3908 else if (c == '+' || c == '*')
3909 {
3910 int is_bg;
3911
3912 cell.width = c == '+' ? 1 : 2;
3913
3914 c = fgetc(fd);
3915 if (c == '&')
3916 {
3917 /* use same attr as previous cell */
3918 c = fgetc(fd);
3919 }
3920 else if (isdigit(c))
3921 {
3922 /* get the decimal attribute */
3923 attr = 0;
3924 while (isdigit(c))
3925 {
3926 attr = attr * 10 + (c - '0');
3927 c = fgetc(fd);
3928 }
3929 hl2vtermAttr(attr, &cell);
3930 }
3931 else
3932 dump_is_corrupt(&ga_text);
3933
3934 /* is_bg == 0: fg, is_bg == 1: bg */
3935 for (is_bg = 0; is_bg <= 1; ++is_bg)
3936 {
3937 if (c == '&')
3938 {
3939 /* use same color as previous cell */
3940 c = fgetc(fd);
3941 }
3942 else if (c == '#')
3943 {
3944 int red, green, blue, index = 0;
3945
3946 c = fgetc(fd);
3947 red = hex2nr(c);
3948 c = fgetc(fd);
3949 red = (red << 4) + hex2nr(c);
3950 c = fgetc(fd);
3951 green = hex2nr(c);
3952 c = fgetc(fd);
3953 green = (green << 4) + hex2nr(c);
3954 c = fgetc(fd);
3955 blue = hex2nr(c);
3956 c = fgetc(fd);
3957 blue = (blue << 4) + hex2nr(c);
3958 c = fgetc(fd);
3959 if (!isdigit(c))
3960 dump_is_corrupt(&ga_text);
3961 while (isdigit(c))
3962 {
3963 index = index * 10 + (c - '0');
3964 c = fgetc(fd);
3965 }
3966
3967 if (is_bg)
3968 {
3969 cell.bg.red = red;
3970 cell.bg.green = green;
3971 cell.bg.blue = blue;
3972 cell.bg.ansi_index = index;
3973 }
3974 else
3975 {
3976 cell.fg.red = red;
3977 cell.fg.green = green;
3978 cell.fg.blue = blue;
3979 cell.fg.ansi_index = index;
3980 }
3981 }
3982 else
3983 dump_is_corrupt(&ga_text);
3984 }
3985 }
3986 else
3987 dump_is_corrupt(&ga_text);
3988
3989 append_cell(&ga_cell, &cell);
3990 }
3991 else if (c == '@')
3992 {
3993 if (prev_char == NULL)
3994 dump_is_corrupt(&ga_text);
3995 else
3996 {
3997 int count = 0;
3998
3999 /* repeat previous character, get the count */
4000 for (;;)
4001 {
4002 c = fgetc(fd);
4003 if (!isdigit(c))
4004 break;
4005 count = count * 10 + (c - '0');
4006 }
4007
4008 while (count-- > 0)
4009 {
4010 ga_concat(&ga_text, prev_char);
4011 append_cell(&ga_cell, &cell);
4012 }
4013 }
4014 }
4015 else
4016 {
4017 dump_is_corrupt(&ga_text);
4018 c = fgetc(fd);
4019 }
4020 }
4021
4022 if (ga_text.ga_len > 0)
4023 {
4024 /* trailing characters after last NL */
4025 dump_is_corrupt(&ga_text);
4026 ga_append(&ga_text, NUL);
4027 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4028 ga_text.ga_len, FALSE);
4029 }
4030
4031 ga_clear(&ga_text);
4032 vim_free(prev_char);
4033
4034 return max_cells;
4035}
4036
4037/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004038 * Return an allocated string with at least "text_width" "=" characters and
4039 * "fname" inserted in the middle.
4040 */
4041 static char_u *
4042get_separator(int text_width, char_u *fname)
4043{
4044 int width = MAX(text_width, curwin->w_width);
4045 char_u *textline;
4046 int fname_size;
4047 char_u *p = fname;
4048 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004049 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004050
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004051 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004052 if (textline == NULL)
4053 return NULL;
4054
4055 fname_size = vim_strsize(fname);
4056 if (fname_size < width - 8)
4057 {
4058 /* enough room, don't use the full window width */
4059 width = MAX(text_width, fname_size + 8);
4060 }
4061 else if (fname_size > width - 8)
4062 {
4063 /* full name doesn't fit, use only the tail */
4064 p = gettail(fname);
4065 fname_size = vim_strsize(p);
4066 }
4067 /* skip characters until the name fits */
4068 while (fname_size > width - 8)
4069 {
4070 p += (*mb_ptr2len)(p);
4071 fname_size = vim_strsize(p);
4072 }
4073
4074 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4075 textline[i] = '=';
4076 textline[i++] = ' ';
4077
4078 STRCPY(textline + i, p);
4079 off = STRLEN(textline);
4080 textline[off] = ' ';
4081 for (i = 1; i < (width - fname_size) / 2; ++i)
4082 textline[off + i] = '=';
4083 textline[off + i] = NUL;
4084
4085 return textline;
4086}
4087
4088/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004089 * Common for "term_dumpdiff()" and "term_dumpload()".
4090 */
4091 static void
4092term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4093{
4094 jobopt_T opt;
4095 buf_T *buf;
4096 char_u buf1[NUMBUFLEN];
4097 char_u buf2[NUMBUFLEN];
4098 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004099 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004100 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004101 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004102 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004103 char_u *textline = NULL;
4104
4105 /* First open the files. If this fails bail out. */
4106 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4107 if (do_diff)
4108 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4109 if (fname1 == NULL || (do_diff && fname2 == NULL))
4110 {
4111 EMSG(_(e_invarg));
4112 return;
4113 }
4114 fd1 = mch_fopen((char *)fname1, READBIN);
4115 if (fd1 == NULL)
4116 {
4117 EMSG2(_(e_notread), fname1);
4118 return;
4119 }
4120 if (do_diff)
4121 {
4122 fd2 = mch_fopen((char *)fname2, READBIN);
4123 if (fd2 == NULL)
4124 {
4125 fclose(fd1);
4126 EMSG2(_(e_notread), fname2);
4127 return;
4128 }
4129 }
4130
4131 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004132 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4133 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4134 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4135 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4136 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004137
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004138 if (opt.jo_term_name == NULL)
4139 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004140 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004141
Bram Moolenaarb571c632018-03-21 22:27:59 +01004142 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004143 if (fname_tofree != NULL)
4144 {
4145 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4146 opt.jo_term_name = fname_tofree;
4147 }
4148 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004149
Bram Moolenaar13568252018-03-16 20:46:58 +01004150 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004151 if (buf != NULL && buf->b_term != NULL)
4152 {
4153 int i;
4154 linenr_T bot_lnum;
4155 linenr_T lnum;
4156 term_T *term = buf->b_term;
4157 int width;
4158 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004159 VTermPos cursor_pos1;
4160 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004161
Bram Moolenaar52acb112018-03-18 19:20:22 +01004162 init_default_colors(term);
4163
Bram Moolenaard96ff162018-02-18 22:13:29 +01004164 rettv->vval.v_number = buf->b_fnum;
4165
4166 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004167 width = read_dump_file(fd1, &cursor_pos1);
4168
4169 /* position the cursor */
4170 if (cursor_pos1.row >= 0)
4171 {
4172 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4173 coladvance(cursor_pos1.col);
4174 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004175
4176 /* Delete the empty line that was in the empty buffer. */
4177 ml_delete(1, FALSE);
4178
4179 /* For term_dumpload() we are done here. */
4180 if (!do_diff)
4181 goto theend;
4182
4183 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4184
Bram Moolenaar4a696342018-04-05 18:45:26 +02004185 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004186 if (textline == NULL)
4187 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004188 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4189 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4190 vim_free(textline);
4191
4192 textline = get_separator(width, fname2);
4193 if (textline == NULL)
4194 goto theend;
4195 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4196 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004197 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004198
4199 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004200 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004201 if (width2 > width)
4202 {
4203 vim_free(textline);
4204 textline = alloc(width2 + 1);
4205 if (textline == NULL)
4206 goto theend;
4207 width = width2;
4208 textline[width] = NUL;
4209 }
4210 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4211
4212 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4213 {
4214 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4215 {
4216 /* bottom part has fewer rows, fill with "-" */
4217 for (i = 0; i < width; ++i)
4218 textline[i] = '-';
4219 }
4220 else
4221 {
4222 char_u *line1;
4223 char_u *line2;
4224 char_u *p1;
4225 char_u *p2;
4226 int col;
4227 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4228 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4229 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4230 ->sb_cells;
4231
4232 /* Make a copy, getting the second line will invalidate it. */
4233 line1 = vim_strsave(ml_get(lnum));
4234 if (line1 == NULL)
4235 break;
4236 p1 = line1;
4237
4238 line2 = ml_get(lnum + bot_lnum);
4239 p2 = line2;
4240 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4241 {
4242 int len1 = utfc_ptr2len(p1);
4243 int len2 = utfc_ptr2len(p2);
4244
4245 textline[col] = ' ';
4246 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004247 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004248 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004249 else if (lnum == cursor_pos1.row + 1
4250 && col == cursor_pos1.col
4251 && (cursor_pos1.row != cursor_pos2.row
4252 || cursor_pos1.col != cursor_pos2.col))
4253 /* cursor in first but not in second */
4254 textline[col] = '>';
4255 else if (lnum == cursor_pos2.row + 1
4256 && col == cursor_pos2.col
4257 && (cursor_pos1.row != cursor_pos2.row
4258 || cursor_pos1.col != cursor_pos2.col))
4259 /* cursor in second but not in first */
4260 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004261 else if (cellattr1 != NULL && cellattr2 != NULL)
4262 {
4263 if ((cellattr1 + col)->width
4264 != (cellattr2 + col)->width)
4265 textline[col] = 'w';
4266 else if (!same_color(&(cellattr1 + col)->fg,
4267 &(cellattr2 + col)->fg))
4268 textline[col] = 'f';
4269 else if (!same_color(&(cellattr1 + col)->bg,
4270 &(cellattr2 + col)->bg))
4271 textline[col] = 'b';
4272 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4273 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4274 textline[col] = 'a';
4275 }
4276 p1 += len1;
4277 p2 += len2;
4278 /* TODO: handle different width */
4279 }
4280 vim_free(line1);
4281
4282 while (col < width)
4283 {
4284 if (*p1 == NUL && *p2 == NUL)
4285 textline[col] = '?';
4286 else if (*p1 == NUL)
4287 {
4288 textline[col] = '+';
4289 p2 += utfc_ptr2len(p2);
4290 }
4291 else
4292 {
4293 textline[col] = '-';
4294 p1 += utfc_ptr2len(p1);
4295 }
4296 ++col;
4297 }
4298 }
4299 if (add_empty_scrollback(term, &term->tl_default_color,
4300 term->tl_top_diff_rows) == OK)
4301 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4302 ++bot_lnum;
4303 }
4304
4305 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4306 {
4307 /* bottom part has more rows, fill with "+" */
4308 for (i = 0; i < width; ++i)
4309 textline[i] = '+';
4310 if (add_empty_scrollback(term, &term->tl_default_color,
4311 term->tl_top_diff_rows) == OK)
4312 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4313 ++lnum;
4314 ++bot_lnum;
4315 }
4316
4317 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004318
4319 /* looks better without wrapping */
4320 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004321 }
4322
4323theend:
4324 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004325 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004326 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004327 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004328 fclose(fd2);
4329}
4330
4331/*
4332 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4333 * bottom files.
4334 * Return FAIL when this is not possible.
4335 */
4336 int
4337term_swap_diff()
4338{
4339 term_T *term = curbuf->b_term;
4340 linenr_T line_count;
4341 linenr_T top_rows;
4342 linenr_T bot_rows;
4343 linenr_T bot_start;
4344 linenr_T lnum;
4345 char_u *p;
4346 sb_line_T *sb_line;
4347
4348 if (term == NULL
4349 || !term_is_finished(curbuf)
4350 || term->tl_top_diff_rows == 0
4351 || term->tl_scrollback.ga_len == 0)
4352 return FAIL;
4353
4354 line_count = curbuf->b_ml.ml_line_count;
4355 top_rows = term->tl_top_diff_rows;
4356 bot_rows = term->tl_bot_diff_rows;
4357 bot_start = line_count - bot_rows;
4358 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4359
4360 /* move lines from top to above the bottom part */
4361 for (lnum = 1; lnum <= top_rows; ++lnum)
4362 {
4363 p = vim_strsave(ml_get(1));
4364 if (p == NULL)
4365 return OK;
4366 ml_append(bot_start, p, 0, FALSE);
4367 ml_delete(1, FALSE);
4368 vim_free(p);
4369 }
4370
4371 /* move lines from bottom to the top */
4372 for (lnum = 1; lnum <= bot_rows; ++lnum)
4373 {
4374 p = vim_strsave(ml_get(bot_start + lnum));
4375 if (p == NULL)
4376 return OK;
4377 ml_delete(bot_start + lnum, FALSE);
4378 ml_append(lnum - 1, p, 0, FALSE);
4379 vim_free(p);
4380 }
4381
4382 if (top_rows == bot_rows)
4383 {
4384 /* rows counts are equal, can swap cell properties */
4385 for (lnum = 0; lnum < top_rows; ++lnum)
4386 {
4387 sb_line_T temp;
4388
4389 temp = *(sb_line + lnum);
4390 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4391 *(sb_line + bot_start + lnum) = temp;
4392 }
4393 }
4394 else
4395 {
4396 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4397 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4398
4399 /* need to copy cell properties into temp memory */
4400 if (temp != NULL)
4401 {
4402 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4403 mch_memmove(term->tl_scrollback.ga_data,
4404 temp + bot_start,
4405 sizeof(sb_line_T) * bot_rows);
4406 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4407 temp + top_rows,
4408 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4409 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4410 + line_count - top_rows,
4411 temp,
4412 sizeof(sb_line_T) * top_rows);
4413 vim_free(temp);
4414 }
4415 }
4416
4417 term->tl_top_diff_rows = bot_rows;
4418 term->tl_bot_diff_rows = top_rows;
4419
4420 update_screen(NOT_VALID);
4421 return OK;
4422}
4423
4424/*
4425 * "term_dumpdiff(filename, filename, options)" function
4426 */
4427 void
4428f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4429{
4430 term_load_dump(argvars, rettv, TRUE);
4431}
4432
4433/*
4434 * "term_dumpload(filename, options)" function
4435 */
4436 void
4437f_term_dumpload(typval_T *argvars, typval_T *rettv)
4438{
4439 term_load_dump(argvars, rettv, FALSE);
4440}
4441
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004442/*
4443 * "term_getaltscreen(buf)" function
4444 */
4445 void
4446f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4447{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004448 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004449
4450 if (buf == NULL)
4451 return;
4452 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4453}
4454
4455/*
4456 * "term_getattr(attr, name)" function
4457 */
4458 void
4459f_term_getattr(typval_T *argvars, typval_T *rettv)
4460{
4461 int attr;
4462 size_t i;
4463 char_u *name;
4464
4465 static struct {
4466 char *name;
4467 int attr;
4468 } attrs[] = {
4469 {"bold", HL_BOLD},
4470 {"italic", HL_ITALIC},
4471 {"underline", HL_UNDERLINE},
4472 {"strike", HL_STRIKETHROUGH},
4473 {"reverse", HL_INVERSE},
4474 };
4475
4476 attr = get_tv_number(&argvars[0]);
4477 name = get_tv_string_chk(&argvars[1]);
4478 if (name == NULL)
4479 return;
4480
4481 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4482 if (STRCMP(name, attrs[i].name) == 0)
4483 {
4484 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4485 break;
4486 }
4487}
4488
4489/*
4490 * "term_getcursor(buf)" function
4491 */
4492 void
4493f_term_getcursor(typval_T *argvars, typval_T *rettv)
4494{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004495 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004496 term_T *term;
4497 list_T *l;
4498 dict_T *d;
4499
4500 if (rettv_list_alloc(rettv) == FAIL)
4501 return;
4502 if (buf == NULL)
4503 return;
4504 term = buf->b_term;
4505
4506 l = rettv->vval.v_list;
4507 list_append_number(l, term->tl_cursor_pos.row + 1);
4508 list_append_number(l, term->tl_cursor_pos.col + 1);
4509
4510 d = dict_alloc();
4511 if (d != NULL)
4512 {
4513 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4514 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4515 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4516 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4517 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4518 ? (char_u *)"" : term->tl_cursor_color);
4519 list_append_dict(l, d);
4520 }
4521}
4522
4523/*
4524 * "term_getjob(buf)" function
4525 */
4526 void
4527f_term_getjob(typval_T *argvars, typval_T *rettv)
4528{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004529 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004530
4531 rettv->v_type = VAR_JOB;
4532 rettv->vval.v_job = NULL;
4533 if (buf == NULL)
4534 return;
4535
4536 rettv->vval.v_job = buf->b_term->tl_job;
4537 if (rettv->vval.v_job != NULL)
4538 ++rettv->vval.v_job->jv_refcount;
4539}
4540
4541 static int
4542get_row_number(typval_T *tv, term_T *term)
4543{
4544 if (tv->v_type == VAR_STRING
4545 && tv->vval.v_string != NULL
4546 && STRCMP(tv->vval.v_string, ".") == 0)
4547 return term->tl_cursor_pos.row;
4548 return (int)get_tv_number(tv) - 1;
4549}
4550
4551/*
4552 * "term_getline(buf, row)" function
4553 */
4554 void
4555f_term_getline(typval_T *argvars, typval_T *rettv)
4556{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004557 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004558 term_T *term;
4559 int row;
4560
4561 rettv->v_type = VAR_STRING;
4562 if (buf == NULL)
4563 return;
4564 term = buf->b_term;
4565 row = get_row_number(&argvars[1], term);
4566
4567 if (term->tl_vterm == NULL)
4568 {
4569 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4570
4571 /* vterm is finished, get the text from the buffer */
4572 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4573 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4574 }
4575 else
4576 {
4577 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4578 VTermRect rect;
4579 int len;
4580 char_u *p;
4581
4582 if (row < 0 || row >= term->tl_rows)
4583 return;
4584 len = term->tl_cols * MB_MAXBYTES + 1;
4585 p = alloc(len);
4586 if (p == NULL)
4587 return;
4588 rettv->vval.v_string = p;
4589
4590 rect.start_col = 0;
4591 rect.end_col = term->tl_cols;
4592 rect.start_row = row;
4593 rect.end_row = row + 1;
4594 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4595 }
4596}
4597
4598/*
4599 * "term_getscrolled(buf)" function
4600 */
4601 void
4602f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4603{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004604 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004605
4606 if (buf == NULL)
4607 return;
4608 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4609}
4610
4611/*
4612 * "term_getsize(buf)" function
4613 */
4614 void
4615f_term_getsize(typval_T *argvars, typval_T *rettv)
4616{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004617 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004618 list_T *l;
4619
4620 if (rettv_list_alloc(rettv) == FAIL)
4621 return;
4622 if (buf == NULL)
4623 return;
4624
4625 l = rettv->vval.v_list;
4626 list_append_number(l, buf->b_term->tl_rows);
4627 list_append_number(l, buf->b_term->tl_cols);
4628}
4629
4630/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004631 * "term_setsize(buf, rows, cols)" function
4632 */
4633 void
4634f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4635{
4636 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4637 term_T *term;
4638 varnumber_T rows, cols;
4639
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004640 if (buf == NULL)
4641 {
4642 EMSG(_("E955: Not a terminal buffer"));
4643 return;
4644 }
4645 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004646 return;
4647 term = buf->b_term;
4648 rows = get_tv_number(&argvars[1]);
4649 rows = rows <= 0 ? term->tl_rows : rows;
4650 cols = get_tv_number(&argvars[2]);
4651 cols = cols <= 0 ? term->tl_cols : cols;
4652 vterm_set_size(term->tl_vterm, rows, cols);
4653 /* handle_resize() will resize the windows */
4654
4655 /* Get and remember the size we ended up with. Update the pty. */
4656 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4657 term_report_winsize(term, term->tl_rows, term->tl_cols);
4658}
4659
4660/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004661 * "term_getstatus(buf)" function
4662 */
4663 void
4664f_term_getstatus(typval_T *argvars, typval_T *rettv)
4665{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004666 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004667 term_T *term;
4668 char_u val[100];
4669
4670 rettv->v_type = VAR_STRING;
4671 if (buf == NULL)
4672 return;
4673 term = buf->b_term;
4674
4675 if (term_job_running(term))
4676 STRCPY(val, "running");
4677 else
4678 STRCPY(val, "finished");
4679 if (term->tl_normal_mode)
4680 STRCAT(val, ",normal");
4681 rettv->vval.v_string = vim_strsave(val);
4682}
4683
4684/*
4685 * "term_gettitle(buf)" function
4686 */
4687 void
4688f_term_gettitle(typval_T *argvars, typval_T *rettv)
4689{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004690 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004691
4692 rettv->v_type = VAR_STRING;
4693 if (buf == NULL)
4694 return;
4695
4696 if (buf->b_term->tl_title != NULL)
4697 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4698}
4699
4700/*
4701 * "term_gettty(buf)" function
4702 */
4703 void
4704f_term_gettty(typval_T *argvars, typval_T *rettv)
4705{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004706 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004707 char_u *p;
4708 int num = 0;
4709
4710 rettv->v_type = VAR_STRING;
4711 if (buf == NULL)
4712 return;
4713 if (argvars[1].v_type != VAR_UNKNOWN)
4714 num = get_tv_number(&argvars[1]);
4715
4716 switch (num)
4717 {
4718 case 0:
4719 if (buf->b_term->tl_job != NULL)
4720 p = buf->b_term->tl_job->jv_tty_out;
4721 else
4722 p = buf->b_term->tl_tty_out;
4723 break;
4724 case 1:
4725 if (buf->b_term->tl_job != NULL)
4726 p = buf->b_term->tl_job->jv_tty_in;
4727 else
4728 p = buf->b_term->tl_tty_in;
4729 break;
4730 default:
4731 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4732 return;
4733 }
4734 if (p != NULL)
4735 rettv->vval.v_string = vim_strsave(p);
4736}
4737
4738/*
4739 * "term_list()" function
4740 */
4741 void
4742f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4743{
4744 term_T *tp;
4745 list_T *l;
4746
4747 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4748 return;
4749
4750 l = rettv->vval.v_list;
4751 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4752 if (tp != NULL && tp->tl_buffer != NULL)
4753 if (list_append_number(l,
4754 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4755 return;
4756}
4757
4758/*
4759 * "term_scrape(buf, row)" function
4760 */
4761 void
4762f_term_scrape(typval_T *argvars, typval_T *rettv)
4763{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004764 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004765 VTermScreen *screen = NULL;
4766 VTermPos pos;
4767 list_T *l;
4768 term_T *term;
4769 char_u *p;
4770 sb_line_T *line;
4771
4772 if (rettv_list_alloc(rettv) == FAIL)
4773 return;
4774 if (buf == NULL)
4775 return;
4776 term = buf->b_term;
4777
4778 l = rettv->vval.v_list;
4779 pos.row = get_row_number(&argvars[1], term);
4780
4781 if (term->tl_vterm != NULL)
4782 {
4783 screen = vterm_obtain_screen(term->tl_vterm);
4784 p = NULL;
4785 line = NULL;
4786 }
4787 else
4788 {
4789 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4790
4791 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4792 return;
4793 p = ml_get_buf(buf, lnum + 1, FALSE);
4794 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4795 }
4796
4797 for (pos.col = 0; pos.col < term->tl_cols; )
4798 {
4799 dict_T *dcell;
4800 int width;
4801 VTermScreenCellAttrs attrs;
4802 VTermColor fg, bg;
4803 char_u rgb[8];
4804 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4805 int off = 0;
4806 int i;
4807
4808 if (screen == NULL)
4809 {
4810 cellattr_T *cellattr;
4811 int len;
4812
4813 /* vterm has finished, get the cell from scrollback */
4814 if (pos.col >= line->sb_cols)
4815 break;
4816 cellattr = line->sb_cells + pos.col;
4817 width = cellattr->width;
4818 attrs = cellattr->attrs;
4819 fg = cellattr->fg;
4820 bg = cellattr->bg;
4821 len = MB_PTR2LEN(p);
4822 mch_memmove(mbs, p, len);
4823 mbs[len] = NUL;
4824 p += len;
4825 }
4826 else
4827 {
4828 VTermScreenCell cell;
4829 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4830 break;
4831 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4832 {
4833 if (cell.chars[i] == 0)
4834 break;
4835 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4836 }
4837 mbs[off] = NUL;
4838 width = cell.width;
4839 attrs = cell.attrs;
4840 fg = cell.fg;
4841 bg = cell.bg;
4842 }
4843 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004844 if (dcell == NULL)
4845 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004846 list_append_dict(l, dcell);
4847
4848 dict_add_nr_str(dcell, "chars", 0, mbs);
4849
4850 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4851 fg.red, fg.green, fg.blue);
4852 dict_add_nr_str(dcell, "fg", 0, rgb);
4853 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4854 bg.red, bg.green, bg.blue);
4855 dict_add_nr_str(dcell, "bg", 0, rgb);
4856
4857 dict_add_nr_str(dcell, "attr",
4858 cell2attr(attrs, fg, bg), NULL);
4859 dict_add_nr_str(dcell, "width", width, NULL);
4860
4861 ++pos.col;
4862 if (width == 2)
4863 ++pos.col;
4864 }
4865}
4866
4867/*
4868 * "term_sendkeys(buf, keys)" function
4869 */
4870 void
4871f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4872{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004873 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004874 char_u *msg;
4875 term_T *term;
4876
4877 rettv->v_type = VAR_UNKNOWN;
4878 if (buf == NULL)
4879 return;
4880
4881 msg = get_tv_string_chk(&argvars[1]);
4882 if (msg == NULL)
4883 return;
4884 term = buf->b_term;
4885 if (term->tl_vterm == NULL)
4886 return;
4887
4888 while (*msg != NUL)
4889 {
4890 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004891 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004892 }
4893}
4894
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004895#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
4896/*
4897 * "term_getansicolors(buf)" function
4898 */
4899 void
4900f_term_getansicolors(typval_T *argvars, typval_T *rettv)
4901{
4902 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
4903 term_T *term;
4904 VTermState *state;
4905 VTermColor color;
4906 char_u hexbuf[10];
4907 int index;
4908 list_T *list;
4909
4910 if (rettv_list_alloc(rettv) == FAIL)
4911 return;
4912
4913 if (buf == NULL)
4914 return;
4915 term = buf->b_term;
4916 if (term->tl_vterm == NULL)
4917 return;
4918
4919 list = rettv->vval.v_list;
4920 state = vterm_obtain_state(term->tl_vterm);
4921 for (index = 0; index < 16; index++)
4922 {
4923 vterm_state_get_palette_color(state, index, &color);
4924 sprintf((char *)hexbuf, "#%02x%02x%02x",
4925 color.red, color.green, color.blue);
4926 if (list_append_string(list, hexbuf, 7) == FAIL)
4927 return;
4928 }
4929}
4930
4931/*
4932 * "term_setansicolors(buf, list)" function
4933 */
4934 void
4935f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
4936{
4937 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
4938 term_T *term;
4939
4940 if (buf == NULL)
4941 return;
4942 term = buf->b_term;
4943 if (term->tl_vterm == NULL)
4944 return;
4945
4946 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
4947 {
4948 EMSG(_(e_listreq));
4949 return;
4950 }
4951
4952 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
4953 EMSG(_(e_invarg));
4954}
4955#endif
4956
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004957/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004958 * "term_setrestore(buf, command)" function
4959 */
4960 void
4961f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4962{
4963#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004964 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004965 term_T *term;
4966 char_u *cmd;
4967
4968 if (buf == NULL)
4969 return;
4970 term = buf->b_term;
4971 vim_free(term->tl_command);
4972 cmd = get_tv_string_chk(&argvars[1]);
4973 if (cmd != NULL)
4974 term->tl_command = vim_strsave(cmd);
4975 else
4976 term->tl_command = NULL;
4977#endif
4978}
4979
4980/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004981 * "term_setkill(buf, how)" function
4982 */
4983 void
4984f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4985{
4986 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4987 term_T *term;
4988 char_u *how;
4989
4990 if (buf == NULL)
4991 return;
4992 term = buf->b_term;
4993 vim_free(term->tl_kill);
4994 how = get_tv_string_chk(&argvars[1]);
4995 if (how != NULL)
4996 term->tl_kill = vim_strsave(how);
4997 else
4998 term->tl_kill = NULL;
4999}
5000
5001/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005002 * "term_start(command, options)" function
5003 */
5004 void
5005f_term_start(typval_T *argvars, typval_T *rettv)
5006{
5007 jobopt_T opt;
5008 buf_T *buf;
5009
5010 init_job_options(&opt);
5011 if (argvars[1].v_type != VAR_UNKNOWN
5012 && get_job_options(&argvars[1], &opt,
5013 JO_TIMEOUT_ALL + JO_STOPONEXIT
5014 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5015 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5016 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5017 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005018 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005019 + JO2_NORESTORE + JO2_TERM_KILL
5020 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005021 return;
5022
Bram Moolenaar13568252018-03-16 20:46:58 +01005023 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005024
5025 if (buf != NULL && buf->b_term != NULL)
5026 rettv->vval.v_number = buf->b_fnum;
5027}
5028
5029/*
5030 * "term_wait" function
5031 */
5032 void
5033f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5034{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005035 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005036
5037 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005038 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005039 if (buf->b_term->tl_job == NULL)
5040 {
5041 ch_log(NULL, "term_wait(): no job to wait for");
5042 return;
5043 }
5044 if (buf->b_term->tl_job->jv_channel == NULL)
5045 /* channel is closed, nothing to do */
5046 return;
5047
5048 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005049 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005050 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5051 {
5052 /* The job is dead, keep reading channel I/O until the channel is
5053 * closed. buf->b_term may become NULL if the terminal was closed while
5054 * waiting. */
5055 ch_log(NULL, "term_wait(): waiting for channel to close");
5056 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5057 {
5058 mch_check_messages();
5059 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005060 if (!buf_valid(buf))
5061 /* If the terminal is closed when the channel is closed the
5062 * buffer disappears. */
5063 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005064 ui_delay(10L, FALSE);
5065 }
5066 mch_check_messages();
5067 parse_queued_messages();
5068 }
5069 else
5070 {
5071 long wait = 10L;
5072
5073 mch_check_messages();
5074 parse_queued_messages();
5075
5076 /* Wait for some time for any channel I/O. */
5077 if (argvars[1].v_type != VAR_UNKNOWN)
5078 wait = get_tv_number(&argvars[1]);
5079 ui_delay(wait, TRUE);
5080 mch_check_messages();
5081
5082 /* Flushing messages on channels is hopefully sufficient.
5083 * TODO: is there a better way? */
5084 parse_queued_messages();
5085 }
5086}
5087
5088/*
5089 * Called when a channel has sent all the lines to a terminal.
5090 * Send a CTRL-D to mark the end of the text.
5091 */
5092 void
5093term_send_eof(channel_T *ch)
5094{
5095 term_T *term;
5096
5097 for (term = first_term; term != NULL; term = term->tl_next)
5098 if (term->tl_job == ch->ch_job)
5099 {
5100 if (term->tl_eof_chars != NULL)
5101 {
5102 channel_send(ch, PART_IN, term->tl_eof_chars,
5103 (int)STRLEN(term->tl_eof_chars), NULL);
5104 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5105 }
5106# ifdef WIN3264
5107 else
5108 /* Default: CTRL-D */
5109 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5110# endif
5111 }
5112}
5113
5114# if defined(WIN3264) || defined(PROTO)
5115
5116/**************************************
5117 * 2. MS-Windows implementation.
5118 */
5119
5120# ifndef PROTO
5121
5122#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5123#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005124#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005125
5126void* (*winpty_config_new)(UINT64, void*);
5127void* (*winpty_open)(void*, void*);
5128void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5129BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5130void (*winpty_config_set_mouse_mode)(void*, int);
5131void (*winpty_config_set_initial_size)(void*, int, int);
5132LPCWSTR (*winpty_conin_name)(void*);
5133LPCWSTR (*winpty_conout_name)(void*);
5134LPCWSTR (*winpty_conerr_name)(void*);
5135void (*winpty_free)(void*);
5136void (*winpty_config_free)(void*);
5137void (*winpty_spawn_config_free)(void*);
5138void (*winpty_error_free)(void*);
5139LPCWSTR (*winpty_error_msg)(void*);
5140BOOL (*winpty_set_size)(void*, int, int, void*);
5141HANDLE (*winpty_agent_process)(void*);
5142
5143#define WINPTY_DLL "winpty.dll"
5144
5145static HINSTANCE hWinPtyDLL = NULL;
5146# endif
5147
5148 static int
5149dyn_winpty_init(int verbose)
5150{
5151 int i;
5152 static struct
5153 {
5154 char *name;
5155 FARPROC *ptr;
5156 } winpty_entry[] =
5157 {
5158 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5159 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5160 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5161 {"winpty_config_set_mouse_mode",
5162 (FARPROC*)&winpty_config_set_mouse_mode},
5163 {"winpty_config_set_initial_size",
5164 (FARPROC*)&winpty_config_set_initial_size},
5165 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5166 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5167 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5168 {"winpty_free", (FARPROC*)&winpty_free},
5169 {"winpty_open", (FARPROC*)&winpty_open},
5170 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5171 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5172 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5173 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5174 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5175 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5176 {NULL, NULL}
5177 };
5178
5179 /* No need to initialize twice. */
5180 if (hWinPtyDLL)
5181 return OK;
5182 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5183 * winpty.dll. */
5184 if (*p_winptydll != NUL)
5185 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5186 if (!hWinPtyDLL)
5187 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5188 if (!hWinPtyDLL)
5189 {
5190 if (verbose)
5191 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5192 : (char_u *)WINPTY_DLL);
5193 return FAIL;
5194 }
5195 for (i = 0; winpty_entry[i].name != NULL
5196 && winpty_entry[i].ptr != NULL; ++i)
5197 {
5198 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5199 winpty_entry[i].name)) == NULL)
5200 {
5201 if (verbose)
5202 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5203 return FAIL;
5204 }
5205 }
5206
5207 return OK;
5208}
5209
5210/*
5211 * Create a new terminal of "rows" by "cols" cells.
5212 * Store a reference in "term".
5213 * Return OK or FAIL.
5214 */
5215 static int
5216term_and_job_init(
5217 term_T *term,
5218 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005219 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005220 jobopt_T *opt)
5221{
5222 WCHAR *cmd_wchar = NULL;
5223 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005224 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005225 channel_T *channel = NULL;
5226 job_T *job = NULL;
5227 DWORD error;
5228 HANDLE jo = NULL;
5229 HANDLE child_process_handle;
5230 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005231 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005232 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005233 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005234 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005235
5236 if (dyn_winpty_init(TRUE) == FAIL)
5237 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005238 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5239 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005240
5241 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005242 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005243 cmd = argvar->vval.v_string;
5244 }
5245 else if (argvar->v_type == VAR_LIST)
5246 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005247 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005248 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005249 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005250 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005251 if (cmd == NULL || *cmd == NUL)
5252 {
5253 EMSG(_(e_invarg));
5254 goto failed;
5255 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005256
5257 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005258 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005259 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005260 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005261 if (opt->jo_cwd != NULL)
5262 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005263
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005264 win32_build_env(opt->jo_env, &ga_env, TRUE);
5265 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005266
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005267 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5268 if (term->tl_winpty_config == NULL)
5269 goto failed;
5270
5271 winpty_config_set_mouse_mode(term->tl_winpty_config,
5272 WINPTY_MOUSE_MODE_FORCE);
5273 winpty_config_set_initial_size(term->tl_winpty_config,
5274 term->tl_cols, term->tl_rows);
5275 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5276 if (term->tl_winpty == NULL)
5277 goto failed;
5278
5279 spawn_config = winpty_spawn_config_new(
5280 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5281 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5282 NULL,
5283 cmd_wchar,
5284 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005285 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005286 &winpty_err);
5287 if (spawn_config == NULL)
5288 goto failed;
5289
5290 channel = add_channel();
5291 if (channel == NULL)
5292 goto failed;
5293
5294 job = job_alloc();
5295 if (job == NULL)
5296 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02005297 if (argvar->v_type == VAR_STRING)
5298 {
5299 int argc;
5300
5301 build_argv_from_string(cmd, &job->jv_argv, &argc);
5302 }
5303 else
5304 {
5305 int argc;
5306
5307 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
5308 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005309
5310 if (opt->jo_set & JO_IN_BUF)
5311 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5312
5313 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5314 &child_thread_handle, &error, &winpty_err))
5315 goto failed;
5316
5317 channel_set_pipes(channel,
5318 (sock_T)CreateFileW(
5319 winpty_conin_name(term->tl_winpty),
5320 GENERIC_WRITE, 0, NULL,
5321 OPEN_EXISTING, 0, NULL),
5322 (sock_T)CreateFileW(
5323 winpty_conout_name(term->tl_winpty),
5324 GENERIC_READ, 0, NULL,
5325 OPEN_EXISTING, 0, NULL),
5326 (sock_T)CreateFileW(
5327 winpty_conerr_name(term->tl_winpty),
5328 GENERIC_READ, 0, NULL,
5329 OPEN_EXISTING, 0, NULL));
5330
5331 /* Write lines with CR instead of NL. */
5332 channel->ch_write_text_mode = TRUE;
5333
5334 jo = CreateJobObject(NULL, NULL);
5335 if (jo == NULL)
5336 goto failed;
5337
5338 if (!AssignProcessToJobObject(jo, child_process_handle))
5339 {
5340 /* Failed, switch the way to terminate process with TerminateProcess. */
5341 CloseHandle(jo);
5342 jo = NULL;
5343 }
5344
5345 winpty_spawn_config_free(spawn_config);
5346 vim_free(cmd_wchar);
5347 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005348 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005349
5350 create_vterm(term, term->tl_rows, term->tl_cols);
5351
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005352#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5353 if (opt->jo_set2 & JO2_ANSI_COLORS)
5354 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5355 else
5356 init_vterm_ansi_colors(term->tl_vterm);
5357#endif
5358
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005359 channel_set_job(channel, job, opt);
5360 job_set_options(job, opt);
5361
5362 job->jv_channel = channel;
5363 job->jv_proc_info.hProcess = child_process_handle;
5364 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5365 job->jv_job_object = jo;
5366 job->jv_status = JOB_STARTED;
5367 job->jv_tty_in = utf16_to_enc(
5368 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5369 job->jv_tty_out = utf16_to_enc(
5370 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5371 ++job->jv_refcount;
5372 term->tl_job = job;
5373
5374 return OK;
5375
5376failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005377 ga_clear(&ga_cmd);
5378 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005379 vim_free(cmd_wchar);
5380 vim_free(cwd_wchar);
5381 if (spawn_config != NULL)
5382 winpty_spawn_config_free(spawn_config);
5383 if (channel != NULL)
5384 channel_clear(channel);
5385 if (job != NULL)
5386 {
5387 job->jv_channel = NULL;
5388 job_cleanup(job);
5389 }
5390 term->tl_job = NULL;
5391 if (jo != NULL)
5392 CloseHandle(jo);
5393 if (term->tl_winpty != NULL)
5394 winpty_free(term->tl_winpty);
5395 term->tl_winpty = NULL;
5396 if (term->tl_winpty_config != NULL)
5397 winpty_config_free(term->tl_winpty_config);
5398 term->tl_winpty_config = NULL;
5399 if (winpty_err != NULL)
5400 {
5401 char_u *msg = utf16_to_enc(
5402 (short_u *)winpty_error_msg(winpty_err), NULL);
5403
5404 EMSG(msg);
5405 winpty_error_free(winpty_err);
5406 }
5407 return FAIL;
5408}
5409
5410 static int
5411create_pty_only(term_T *term, jobopt_T *options)
5412{
5413 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5414 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5415 char in_name[80], out_name[80];
5416 channel_T *channel = NULL;
5417
5418 create_vterm(term, term->tl_rows, term->tl_cols);
5419
5420 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5421 GetCurrentProcessId(),
5422 curbuf->b_fnum);
5423 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5424 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5425 PIPE_UNLIMITED_INSTANCES,
5426 0, 0, NMPWAIT_NOWAIT, NULL);
5427 if (hPipeIn == INVALID_HANDLE_VALUE)
5428 goto failed;
5429
5430 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5431 GetCurrentProcessId(),
5432 curbuf->b_fnum);
5433 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5434 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5435 PIPE_UNLIMITED_INSTANCES,
5436 0, 0, 0, NULL);
5437 if (hPipeOut == INVALID_HANDLE_VALUE)
5438 goto failed;
5439
5440 ConnectNamedPipe(hPipeIn, NULL);
5441 ConnectNamedPipe(hPipeOut, NULL);
5442
5443 term->tl_job = job_alloc();
5444 if (term->tl_job == NULL)
5445 goto failed;
5446 ++term->tl_job->jv_refcount;
5447
5448 /* behave like the job is already finished */
5449 term->tl_job->jv_status = JOB_FINISHED;
5450
5451 channel = add_channel();
5452 if (channel == NULL)
5453 goto failed;
5454 term->tl_job->jv_channel = channel;
5455 channel->ch_keep_open = TRUE;
5456 channel->ch_named_pipe = TRUE;
5457
5458 channel_set_pipes(channel,
5459 (sock_T)hPipeIn,
5460 (sock_T)hPipeOut,
5461 (sock_T)hPipeOut);
5462 channel_set_job(channel, term->tl_job, options);
5463 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5464 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5465
5466 return OK;
5467
5468failed:
5469 if (hPipeIn != NULL)
5470 CloseHandle(hPipeIn);
5471 if (hPipeOut != NULL)
5472 CloseHandle(hPipeOut);
5473 return FAIL;
5474}
5475
5476/*
5477 * Free the terminal emulator part of "term".
5478 */
5479 static void
5480term_free_vterm(term_T *term)
5481{
5482 if (term->tl_winpty != NULL)
5483 winpty_free(term->tl_winpty);
5484 term->tl_winpty = NULL;
5485 if (term->tl_winpty_config != NULL)
5486 winpty_config_free(term->tl_winpty_config);
5487 term->tl_winpty_config = NULL;
5488 if (term->tl_vterm != NULL)
5489 vterm_free(term->tl_vterm);
5490 term->tl_vterm = NULL;
5491}
5492
5493/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005494 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005495 */
5496 static void
5497term_report_winsize(term_T *term, int rows, int cols)
5498{
5499 if (term->tl_winpty)
5500 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5501}
5502
5503 int
5504terminal_enabled(void)
5505{
5506 return dyn_winpty_init(FALSE) == OK;
5507}
5508
5509# else
5510
5511/**************************************
5512 * 3. Unix-like implementation.
5513 */
5514
5515/*
5516 * Create a new terminal of "rows" by "cols" cells.
5517 * Start job for "cmd".
5518 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005519 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005520 * Return OK or FAIL.
5521 */
5522 static int
5523term_and_job_init(
5524 term_T *term,
5525 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005526 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005527 jobopt_T *opt)
5528{
5529 create_vterm(term, term->tl_rows, term->tl_cols);
5530
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005531#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5532 if (opt->jo_set2 & JO2_ANSI_COLORS)
5533 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5534 else
5535 init_vterm_ansi_colors(term->tl_vterm);
5536#endif
5537
Bram Moolenaar13568252018-03-16 20:46:58 +01005538 /* This may change a string in "argvar". */
5539 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005540 if (term->tl_job != NULL)
5541 ++term->tl_job->jv_refcount;
5542
5543 return term->tl_job != NULL
5544 && term->tl_job->jv_channel != NULL
5545 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5546}
5547
5548 static int
5549create_pty_only(term_T *term, jobopt_T *opt)
5550{
5551 create_vterm(term, term->tl_rows, term->tl_cols);
5552
5553 term->tl_job = job_alloc();
5554 if (term->tl_job == NULL)
5555 return FAIL;
5556 ++term->tl_job->jv_refcount;
5557
5558 /* behave like the job is already finished */
5559 term->tl_job->jv_status = JOB_FINISHED;
5560
5561 return mch_create_pty_channel(term->tl_job, opt);
5562}
5563
5564/*
5565 * Free the terminal emulator part of "term".
5566 */
5567 static void
5568term_free_vterm(term_T *term)
5569{
5570 if (term->tl_vterm != NULL)
5571 vterm_free(term->tl_vterm);
5572 term->tl_vterm = NULL;
5573}
5574
5575/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005576 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005577 */
5578 static void
5579term_report_winsize(term_T *term, int rows, int cols)
5580{
5581 /* Use an ioctl() to report the new window size to the job. */
5582 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5583 {
5584 int fd = -1;
5585 int part;
5586
5587 for (part = PART_OUT; part < PART_COUNT; ++part)
5588 {
5589 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5590 if (isatty(fd))
5591 break;
5592 }
5593 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5594 mch_signal_job(term->tl_job, (char_u *)"winch");
5595 }
5596}
5597
5598# endif
5599
5600#endif /* FEAT_TERMINAL */