blob: 7f6e48db64741ee7af9703dfe62559c84527d895 [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
218 if (*wp->w_p_tms != NUL)
219 {
220 char_u *p = vim_strchr(wp->w_p_tms, 'x');
221
222 /* Syntax of value was already checked when it's set. */
223 if (p == NULL)
224 {
225 minsize = TRUE;
226 p = vim_strchr(wp->w_p_tms, '*');
227 }
228 *rows = atoi((char *)wp->w_p_tms);
229 *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:
1692 return FAIL;
1693
1694 case K_LEFTDRAG:
1695 case K_MIDDLEDRAG:
1696 case K_RIGHTDRAG:
1697 case K_X1DRAG:
1698 case K_X2DRAG:
1699 dragging_outside = mouse_was_outside;
1700 /* FALLTHROUGH */
1701 case K_LEFTMOUSE:
1702 case K_LEFTMOUSE_NM:
1703 case K_LEFTRELEASE:
1704 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001705 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001706 case K_MIDDLEMOUSE:
1707 case K_MIDDLERELEASE:
1708 case K_RIGHTMOUSE:
1709 case K_RIGHTRELEASE:
1710 case K_X1MOUSE:
1711 case K_X1RELEASE:
1712 case K_X2MOUSE:
1713 case K_X2RELEASE:
1714
1715 case K_MOUSEUP:
1716 case K_MOUSEDOWN:
1717 case K_MOUSELEFT:
1718 case K_MOUSERIGHT:
1719 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001720 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001721 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001722 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001723 || dragging_outside)
1724 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001725 /* click or scroll outside the current window or on status line
1726 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001727 if (typed)
1728 {
1729 stuffcharReadbuff(c);
1730 mouse_was_outside = TRUE;
1731 }
1732 return FAIL;
1733 }
1734 }
1735 if (typed)
1736 mouse_was_outside = FALSE;
1737
1738 /* Convert the typed key to a sequence of bytes for the job. */
1739 len = term_convert_key(term, c, msg);
1740 if (len > 0)
1741 /* TODO: if FAIL is returned, stop? */
1742 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1743 (char_u *)msg, (int)len, NULL);
1744
1745 return OK;
1746}
1747
1748 static void
1749position_cursor(win_T *wp, VTermPos *pos)
1750{
1751 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1752 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1753 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1754}
1755
1756/*
1757 * Handle CTRL-W "": send register contents to the job.
1758 */
1759 static void
1760term_paste_register(int prev_c UNUSED)
1761{
1762 int c;
1763 list_T *l;
1764 listitem_T *item;
1765 long reglen = 0;
1766 int type;
1767
1768#ifdef FEAT_CMDL_INFO
1769 if (add_to_showcmd(prev_c))
1770 if (add_to_showcmd('"'))
1771 out_flush();
1772#endif
1773 c = term_vgetc();
1774#ifdef FEAT_CMDL_INFO
1775 clear_showcmd();
1776#endif
1777 if (!term_use_loop())
1778 /* job finished while waiting for a character */
1779 return;
1780
1781 /* CTRL-W "= prompt for expression to evaluate. */
1782 if (c == '=' && get_expr_register() != '=')
1783 return;
1784 if (!term_use_loop())
1785 /* job finished while waiting for a character */
1786 return;
1787
1788 l = (list_T *)get_reg_contents(c, GREG_LIST);
1789 if (l != NULL)
1790 {
1791 type = get_reg_type(c, &reglen);
1792 for (item = l->lv_first; item != NULL; item = item->li_next)
1793 {
1794 char_u *s = get_tv_string(&item->li_tv);
1795#ifdef WIN3264
1796 char_u *tmp = s;
1797
1798 if (!enc_utf8 && enc_codepage > 0)
1799 {
1800 WCHAR *ret = NULL;
1801 int length = 0;
1802
1803 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1804 (int)STRLEN(s), &ret, &length);
1805 if (ret != NULL)
1806 {
1807 WideCharToMultiByte_alloc(CP_UTF8, 0,
1808 ret, length, (char **)&s, &length, 0, 0);
1809 vim_free(ret);
1810 }
1811 }
1812#endif
1813 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1814 s, (int)STRLEN(s), NULL);
1815#ifdef WIN3264
1816 if (tmp != s)
1817 vim_free(s);
1818#endif
1819
1820 if (item->li_next != NULL || type == MLINE)
1821 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1822 (char_u *)"\r", 1, NULL);
1823 }
1824 list_free(l);
1825 }
1826}
1827
1828#if defined(FEAT_GUI) || defined(PROTO)
1829/*
1830 * Return TRUE when the cursor of the terminal should be displayed.
1831 */
1832 int
1833terminal_is_active()
1834{
1835 return in_terminal_loop != NULL;
1836}
1837
1838 cursorentry_T *
1839term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1840{
1841 term_T *term = in_terminal_loop;
1842 static cursorentry_T entry;
1843
1844 vim_memset(&entry, 0, sizeof(entry));
1845 entry.shape = entry.mshape =
1846 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1847 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1848 SHAPE_BLOCK;
1849 entry.percentage = 20;
1850 if (term->tl_cursor_blink)
1851 {
1852 entry.blinkwait = 700;
1853 entry.blinkon = 400;
1854 entry.blinkoff = 250;
1855 }
1856 *fg = gui.back_pixel;
1857 if (term->tl_cursor_color == NULL)
1858 *bg = gui.norm_pixel;
1859 else
1860 *bg = color_name2handle(term->tl_cursor_color);
1861 entry.name = "n";
1862 entry.used_for = SHAPE_CURSOR;
1863
1864 return &entry;
1865}
1866#endif
1867
Bram Moolenaard317b382018-02-08 22:33:31 +01001868 static void
1869may_output_cursor_props(void)
1870{
1871 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1872 || last_set_cursor_shape != desired_cursor_shape
1873 || last_set_cursor_blink != desired_cursor_blink)
1874 {
1875 last_set_cursor_color = desired_cursor_color;
1876 last_set_cursor_shape = desired_cursor_shape;
1877 last_set_cursor_blink = desired_cursor_blink;
1878 term_cursor_color(desired_cursor_color);
1879 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1880 /* this will restore the initial cursor style, if possible */
1881 ui_cursor_shape_forced(TRUE);
1882 else
1883 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1884 }
1885}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001886
Bram Moolenaard317b382018-02-08 22:33:31 +01001887/*
1888 * Set the cursor color and shape, if not last set to these.
1889 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001890 static void
1891may_set_cursor_props(term_T *term)
1892{
1893#ifdef FEAT_GUI
1894 /* For the GUI the cursor properties are obtained with
1895 * term_get_cursor_shape(). */
1896 if (gui.in_use)
1897 return;
1898#endif
1899 if (in_terminal_loop == term)
1900 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001901 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001902 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001903 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001904 desired_cursor_color = (char_u *)"";
1905 desired_cursor_shape = term->tl_cursor_shape;
1906 desired_cursor_blink = term->tl_cursor_blink;
1907 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001908 }
1909}
1910
Bram Moolenaard317b382018-02-08 22:33:31 +01001911/*
1912 * Reset the desired cursor properties and restore them when needed.
1913 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001914 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001915prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001916{
1917#ifdef FEAT_GUI
1918 if (gui.in_use)
1919 return;
1920#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001921 desired_cursor_color = (char_u *)"";
1922 desired_cursor_shape = -1;
1923 desired_cursor_blink = -1;
1924 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001925}
1926
1927/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001928 * Returns TRUE if the current window contains a terminal and we are sending
1929 * keys to the job.
1930 * If "check_job_status" is TRUE update the job status.
1931 */
1932 static int
1933term_use_loop_check(int check_job_status)
1934{
1935 term_T *term = curbuf->b_term;
1936
1937 return term != NULL
1938 && !term->tl_normal_mode
1939 && term->tl_vterm != NULL
1940 && term_job_running_check(term, check_job_status);
1941}
1942
1943/*
1944 * Returns TRUE if the current window contains a terminal and we are sending
1945 * keys to the job.
1946 */
1947 int
1948term_use_loop(void)
1949{
1950 return term_use_loop_check(FALSE);
1951}
1952
1953/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001954 * Called when entering a window with the mouse. If this is a terminal window
1955 * we may want to change state.
1956 */
1957 void
1958term_win_entered()
1959{
1960 term_T *term = curbuf->b_term;
1961
1962 if (term != NULL)
1963 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001964 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001965 {
1966 reset_VIsual_and_resel();
1967 if (State & INSERT)
1968 stop_insert_mode = TRUE;
1969 }
1970 mouse_was_outside = FALSE;
1971 enter_mouse_col = mouse_col;
1972 enter_mouse_row = mouse_row;
1973 }
1974}
1975
1976/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001977 * Wait for input and send it to the job.
1978 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1979 * when there is no more typahead.
1980 * Return when the start of a CTRL-W command is typed or anything else that
1981 * should be handled as a Normal mode command.
1982 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1983 * the terminal was closed.
1984 */
1985 int
1986terminal_loop(int blocking)
1987{
1988 int c;
1989 int termkey = 0;
1990 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001991#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001992 int tty_fd = curbuf->b_term->tl_job->jv_channel
1993 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001994#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001995 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001996
1997 /* Remember the terminal we are sending keys to. However, the terminal
1998 * might be closed while waiting for a character, e.g. typing "exit" in a
1999 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2000 * stored reference. */
2001 in_terminal_loop = curbuf->b_term;
2002
2003 if (*curwin->w_p_tk != NUL)
2004 termkey = string_to_key(curwin->w_p_tk, TRUE);
2005 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2006 may_set_cursor_props(curbuf->b_term);
2007
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002008 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002009 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002010#ifdef FEAT_GUI
2011 if (!curbuf->b_term->tl_system)
2012#endif
2013 /* TODO: skip screen update when handling a sequence of keys. */
2014 /* Repeat redrawing in case a message is received while redrawing.
2015 */
2016 while (must_redraw != 0)
2017 if (update_screen(0) == FAIL)
2018 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002019 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002020 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002021
2022 c = term_vgetc();
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002023 if (!term_use_loop_check(TRUE))
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002024 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002025 /* Job finished while waiting for a character. Push back the
2026 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002027 if (c != K_IGNORE)
2028 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002029 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002030 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002031 if (c == K_IGNORE)
2032 continue;
2033
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002034#ifdef UNIX
2035 /*
2036 * The shell or another program may change the tty settings. Getting
2037 * them for every typed character is a bit of overhead, but it's needed
2038 * for the first character typed, e.g. when Vim starts in a shell.
2039 */
2040 if (isatty(tty_fd))
2041 {
2042 ttyinfo_T info;
2043
2044 /* Get the current backspace character of the pty. */
2045 if (get_tty_info(tty_fd, &info) == OK)
2046 term_backspace_char = info.backspace;
2047 }
2048#endif
2049
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002050#ifdef WIN3264
2051 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2052 * Use CTRL-BREAK to kill the job. */
2053 if (ctrl_break_was_pressed)
2054 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2055#endif
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002056 /* Was either CTRL-W (termkey) or CTRL-\ pressed?
2057 * Not in a system terminal. */
2058 if ((c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2059#ifdef FEAT_GUI
2060 && !curbuf->b_term->tl_system
2061#endif
2062 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002063 {
2064 int prev_c = c;
2065
2066#ifdef FEAT_CMDL_INFO
2067 if (add_to_showcmd(c))
2068 out_flush();
2069#endif
2070 c = term_vgetc();
2071#ifdef FEAT_CMDL_INFO
2072 clear_showcmd();
2073#endif
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002074 if (!term_use_loop_check(TRUE))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002075 /* job finished while waiting for a character */
2076 break;
2077
2078 if (prev_c == Ctrl_BSL)
2079 {
2080 if (c == Ctrl_N)
2081 {
2082 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2083 term_enter_normal_mode();
2084 ret = FAIL;
2085 goto theend;
2086 }
2087 /* Send both keys to the terminal. */
2088 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2089 }
2090 else if (c == Ctrl_C)
2091 {
2092 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2093 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2094 }
2095 else if (termkey == 0 && c == '.')
2096 {
2097 /* "CTRL-W .": send CTRL-W to the job */
2098 c = Ctrl_W;
2099 }
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002100 else if (termkey == 0 && c == Ctrl_BSL)
2101 {
2102 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
2103 c = Ctrl_BSL;
2104 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002105 else if (c == 'N')
2106 {
2107 /* CTRL-W N : go to Terminal-Normal mode. */
2108 term_enter_normal_mode();
2109 ret = FAIL;
2110 goto theend;
2111 }
2112 else if (c == '"')
2113 {
2114 term_paste_register(prev_c);
2115 continue;
2116 }
2117 else if (termkey == 0 || c != termkey)
2118 {
2119 stuffcharReadbuff(Ctrl_W);
2120 stuffcharReadbuff(c);
2121 ret = OK;
2122 goto theend;
2123 }
2124 }
2125# ifdef WIN3264
2126 if (!enc_utf8 && has_mbyte && c >= 0x80)
2127 {
2128 WCHAR wc;
2129 char_u mb[3];
2130
2131 mb[0] = (unsigned)c >> 8;
2132 mb[1] = c;
2133 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2134 c = wc;
2135 }
2136# endif
2137 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2138 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002139 if (c == K_MOUSEMOVE)
2140 /* We are sure to come back here, don't reset the cursor color
2141 * and shape to avoid flickering. */
2142 restore_cursor = FALSE;
2143
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002144 ret = OK;
2145 goto theend;
2146 }
2147 }
2148 ret = FAIL;
2149
2150theend:
2151 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002152 if (restore_cursor)
2153 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002154 return ret;
2155}
2156
2157/*
2158 * Called when a job has finished.
2159 * This updates the title and status, but does not close the vterm, because
2160 * there might still be pending output in the channel.
2161 */
2162 void
2163term_job_ended(job_T *job)
2164{
2165 term_T *term;
2166 int did_one = FALSE;
2167
2168 for (term = first_term; term != NULL; term = term->tl_next)
2169 if (term->tl_job == job)
2170 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002171 VIM_CLEAR(term->tl_title);
2172 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002173 redraw_buf_and_status_later(term->tl_buffer, VALID);
2174 did_one = TRUE;
2175 }
2176 if (did_one)
2177 redraw_statuslines();
2178 if (curbuf->b_term != NULL)
2179 {
2180 if (curbuf->b_term->tl_job == job)
2181 maketitle();
2182 update_cursor(curbuf->b_term, TRUE);
2183 }
2184}
2185
2186 static void
2187may_toggle_cursor(term_T *term)
2188{
2189 if (in_terminal_loop == term)
2190 {
2191 if (term->tl_cursor_visible)
2192 cursor_on();
2193 else
2194 cursor_off();
2195 }
2196}
2197
2198/*
2199 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002200 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002201 */
2202 static int
2203color2index(VTermColor *color, int fg, int *boldp)
2204{
2205 int red = color->red;
2206 int blue = color->blue;
2207 int green = color->green;
2208
Bram Moolenaar46359e12017-11-29 22:33:38 +01002209 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002210 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002211 /* First 16 colors and default: use the ANSI index, because these
2212 * colors can be redefined. */
2213 if (t_colors >= 16)
2214 return color->ansi_index;
2215 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002216 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002217 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002218 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002219 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2220 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2221 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002222 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002223 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2224 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2225 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2226 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2227 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2228 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2229 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2230 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2231 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2232 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2233 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002234 }
2235 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002236
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002237 if (t_colors >= 256)
2238 {
2239 if (red == blue && red == green)
2240 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002241 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002242 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002243 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2244 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2245 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002246 int i;
2247
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002248 if (red < 5)
2249 return 17; /* 00/00/00 */
2250 if (red > 245) /* ff/ff/ff */
2251 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002252 for (i = 0; i < 23; ++i)
2253 if (red < cutoff[i])
2254 return i + 233;
2255 return 256;
2256 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002257 {
2258 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2259 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002260
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002261 /* 216-color cube */
2262 for (ri = 0; ri < 5; ++ri)
2263 if (red < cutoff[ri])
2264 break;
2265 for (gi = 0; gi < 5; ++gi)
2266 if (green < cutoff[gi])
2267 break;
2268 for (bi = 0; bi < 5; ++bi)
2269 if (blue < cutoff[bi])
2270 break;
2271 return 17 + ri * 36 + gi * 6 + bi;
2272 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002273 }
2274 return 0;
2275}
2276
2277/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002278 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002279 */
2280 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002281vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002282{
2283 int attr = 0;
2284
2285 if (cellattrs.bold)
2286 attr |= HL_BOLD;
2287 if (cellattrs.underline)
2288 attr |= HL_UNDERLINE;
2289 if (cellattrs.italic)
2290 attr |= HL_ITALIC;
2291 if (cellattrs.strike)
2292 attr |= HL_STRIKETHROUGH;
2293 if (cellattrs.reverse)
2294 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002295 return attr;
2296}
2297
2298/*
2299 * Store Vterm attributes in "cell" from highlight flags.
2300 */
2301 static void
2302hl2vtermAttr(int attr, cellattr_T *cell)
2303{
2304 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2305 if (attr & HL_BOLD)
2306 cell->attrs.bold = 1;
2307 if (attr & HL_UNDERLINE)
2308 cell->attrs.underline = 1;
2309 if (attr & HL_ITALIC)
2310 cell->attrs.italic = 1;
2311 if (attr & HL_STRIKETHROUGH)
2312 cell->attrs.strike = 1;
2313 if (attr & HL_INVERSE)
2314 cell->attrs.reverse = 1;
2315}
2316
2317/*
2318 * Convert the attributes of a vterm cell into an attribute index.
2319 */
2320 static int
2321cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2322{
2323 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002324
2325#ifdef FEAT_GUI
2326 if (gui.in_use)
2327 {
2328 guicolor_T fg, bg;
2329
2330 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2331 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2332 return get_gui_attr_idx(attr, fg, bg);
2333 }
2334 else
2335#endif
2336#ifdef FEAT_TERMGUICOLORS
2337 if (p_tgc)
2338 {
2339 guicolor_T fg, bg;
2340
2341 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2342 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2343
2344 return get_tgc_attr_idx(attr, fg, bg);
2345 }
2346 else
2347#endif
2348 {
2349 int bold = MAYBE;
2350 int fg = color2index(&cellfg, TRUE, &bold);
2351 int bg = color2index(&cellbg, FALSE, &bold);
2352
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002353 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002354 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002355 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002356 if (fg == 0 && term_default_cterm_fg >= 0)
2357 fg = term_default_cterm_fg + 1;
2358 if (bg == 0 && term_default_cterm_bg >= 0)
2359 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002360 }
2361
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002362 /* with 8 colors set the bold attribute to get a bright foreground */
2363 if (bold == TRUE)
2364 attr |= HL_BOLD;
2365 return get_cterm_attr_idx(attr, fg, bg);
2366 }
2367 return 0;
2368}
2369
2370 static int
2371handle_damage(VTermRect rect, void *user)
2372{
2373 term_T *term = (term_T *)user;
2374
2375 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2376 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2377 redraw_buf_later(term->tl_buffer, NOT_VALID);
2378 return 1;
2379}
2380
2381 static int
2382handle_moverect(VTermRect dest, VTermRect src, void *user)
2383{
2384 term_T *term = (term_T *)user;
2385
2386 /* Scrolling up is done much more efficiently by deleting lines instead of
2387 * redrawing the text. */
2388 if (dest.start_col == src.start_col
2389 && dest.end_col == src.end_col
2390 && dest.start_row < src.start_row)
2391 {
2392 win_T *wp;
2393 VTermColor fg, bg;
2394 VTermScreenCellAttrs attr;
2395 int clear_attr;
2396
2397 /* Set the color to clear lines with. */
2398 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2399 &fg, &bg);
2400 vim_memset(&attr, 0, sizeof(attr));
2401 clear_attr = cell2attr(attr, fg, bg);
2402
2403 FOR_ALL_WINDOWS(wp)
2404 {
2405 if (wp->w_buffer == term->tl_buffer)
2406 win_del_lines(wp, dest.start_row,
2407 src.start_row - dest.start_row, FALSE, FALSE,
2408 clear_attr);
2409 }
2410 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002411
2412 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2413 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2414
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002415 redraw_buf_later(term->tl_buffer, NOT_VALID);
2416 return 1;
2417}
2418
2419 static int
2420handle_movecursor(
2421 VTermPos pos,
2422 VTermPos oldpos UNUSED,
2423 int visible,
2424 void *user)
2425{
2426 term_T *term = (term_T *)user;
2427 win_T *wp;
2428
2429 term->tl_cursor_pos = pos;
2430 term->tl_cursor_visible = visible;
2431
2432 FOR_ALL_WINDOWS(wp)
2433 {
2434 if (wp->w_buffer == term->tl_buffer)
2435 position_cursor(wp, &pos);
2436 }
2437 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2438 {
2439 may_toggle_cursor(term);
2440 update_cursor(term, term->tl_cursor_visible);
2441 }
2442
2443 return 1;
2444}
2445
2446 static int
2447handle_settermprop(
2448 VTermProp prop,
2449 VTermValue *value,
2450 void *user)
2451{
2452 term_T *term = (term_T *)user;
2453
2454 switch (prop)
2455 {
2456 case VTERM_PROP_TITLE:
2457 vim_free(term->tl_title);
2458 /* a blank title isn't useful, make it empty, so that "running" is
2459 * displayed */
2460 if (*skipwhite((char_u *)value->string) == NUL)
2461 term->tl_title = NULL;
2462#ifdef WIN3264
2463 else if (!enc_utf8 && enc_codepage > 0)
2464 {
2465 WCHAR *ret = NULL;
2466 int length = 0;
2467
2468 MultiByteToWideChar_alloc(CP_UTF8, 0,
2469 (char*)value->string, (int)STRLEN(value->string),
2470 &ret, &length);
2471 if (ret != NULL)
2472 {
2473 WideCharToMultiByte_alloc(enc_codepage, 0,
2474 ret, length, (char**)&term->tl_title,
2475 &length, 0, 0);
2476 vim_free(ret);
2477 }
2478 }
2479#endif
2480 else
2481 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002482 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002483 if (term == curbuf->b_term)
2484 maketitle();
2485 break;
2486
2487 case VTERM_PROP_CURSORVISIBLE:
2488 term->tl_cursor_visible = value->boolean;
2489 may_toggle_cursor(term);
2490 out_flush();
2491 break;
2492
2493 case VTERM_PROP_CURSORBLINK:
2494 term->tl_cursor_blink = value->boolean;
2495 may_set_cursor_props(term);
2496 break;
2497
2498 case VTERM_PROP_CURSORSHAPE:
2499 term->tl_cursor_shape = value->number;
2500 may_set_cursor_props(term);
2501 break;
2502
2503 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002504 if (desired_cursor_color == term->tl_cursor_color)
2505 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002506 vim_free(term->tl_cursor_color);
2507 if (*value->string == NUL)
2508 term->tl_cursor_color = NULL;
2509 else
2510 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2511 may_set_cursor_props(term);
2512 break;
2513
2514 case VTERM_PROP_ALTSCREEN:
2515 /* TODO: do anything else? */
2516 term->tl_using_altscreen = value->boolean;
2517 break;
2518
2519 default:
2520 break;
2521 }
2522 /* Always return 1, otherwise vterm doesn't store the value internally. */
2523 return 1;
2524}
2525
2526/*
2527 * The job running in the terminal resized the terminal.
2528 */
2529 static int
2530handle_resize(int rows, int cols, void *user)
2531{
2532 term_T *term = (term_T *)user;
2533 win_T *wp;
2534
2535 term->tl_rows = rows;
2536 term->tl_cols = cols;
2537 if (term->tl_vterm_size_changed)
2538 /* Size was set by vterm_set_size(), don't set the window size. */
2539 term->tl_vterm_size_changed = FALSE;
2540 else
2541 {
2542 FOR_ALL_WINDOWS(wp)
2543 {
2544 if (wp->w_buffer == term->tl_buffer)
2545 {
2546 win_setheight_win(rows, wp);
2547 win_setwidth_win(cols, wp);
2548 }
2549 }
2550 redraw_buf_later(term->tl_buffer, NOT_VALID);
2551 }
2552 return 1;
2553}
2554
2555/*
2556 * Handle a line that is pushed off the top of the screen.
2557 */
2558 static int
2559handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2560{
2561 term_T *term = (term_T *)user;
2562
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002563 /* If the number of lines that are stored goes over 'termscrollback' then
2564 * delete the first 10%. */
Bram Moolenaar8c94a542018-04-15 12:55:13 +02002565 if (term->tl_scrollback.ga_len >= p_tlsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002566 {
2567 int todo = p_tlsl / 10;
2568 int i;
2569
2570 curbuf = term->tl_buffer;
2571 for (i = 0; i < todo; ++i)
2572 {
2573 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
2574 ml_delete(1, FALSE);
2575 }
2576 curbuf = curwin->w_buffer;
2577
2578 term->tl_scrollback.ga_len -= todo;
2579 mch_memmove(term->tl_scrollback.ga_data,
2580 (sb_line_T *)term->tl_scrollback.ga_data + todo,
2581 sizeof(sb_line_T) * term->tl_scrollback.ga_len);
2582 }
2583
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002584 if (ga_grow(&term->tl_scrollback, 1) == OK)
2585 {
2586 cellattr_T *p = NULL;
2587 int len = 0;
2588 int i;
2589 int c;
2590 int col;
2591 sb_line_T *line;
2592 garray_T ga;
2593 cellattr_T fill_attr = term->tl_default_color;
2594
2595 /* do not store empty cells at the end */
2596 for (i = 0; i < cols; ++i)
2597 if (cells[i].chars[0] != 0)
2598 len = i + 1;
2599 else
2600 cell2cellattr(&cells[i], &fill_attr);
2601
2602 ga_init2(&ga, 1, 100);
2603 if (len > 0)
2604 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2605 if (p != NULL)
2606 {
2607 for (col = 0; col < len; col += cells[col].width)
2608 {
2609 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2610 {
2611 ga.ga_len = 0;
2612 break;
2613 }
2614 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2615 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2616 (char_u *)ga.ga_data + ga.ga_len);
2617 cell2cellattr(&cells[col], &p[col]);
2618 }
2619 }
2620 if (ga_grow(&ga, 1) == FAIL)
2621 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2622 else
2623 {
2624 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2625 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2626 }
2627 ga_clear(&ga);
2628
2629 line = (sb_line_T *)term->tl_scrollback.ga_data
2630 + term->tl_scrollback.ga_len;
2631 line->sb_cols = len;
2632 line->sb_cells = p;
2633 line->sb_fill_attr = fill_attr;
2634 ++term->tl_scrollback.ga_len;
2635 ++term->tl_scrollback_scrolled;
2636 }
2637 return 0; /* ignored */
2638}
2639
2640static VTermScreenCallbacks screen_callbacks = {
2641 handle_damage, /* damage */
2642 handle_moverect, /* moverect */
2643 handle_movecursor, /* movecursor */
2644 handle_settermprop, /* settermprop */
2645 NULL, /* bell */
2646 handle_resize, /* resize */
2647 handle_pushline, /* sb_pushline */
2648 NULL /* sb_popline */
2649};
2650
2651/*
2652 * Called when a channel has been closed.
2653 * If this was a channel for a terminal window then finish it up.
2654 */
2655 void
2656term_channel_closed(channel_T *ch)
2657{
2658 term_T *term;
2659 int did_one = FALSE;
2660
2661 for (term = first_term; term != NULL; term = term->tl_next)
2662 if (term->tl_job == ch->ch_job)
2663 {
2664 term->tl_channel_closed = TRUE;
2665 did_one = TRUE;
2666
Bram Moolenaard23a8232018-02-10 18:45:26 +01002667 VIM_CLEAR(term->tl_title);
2668 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002669
2670 /* Unless in Terminal-Normal mode: clear the vterm. */
2671 if (!term->tl_normal_mode)
2672 {
2673 int fnum = term->tl_buffer->b_fnum;
2674
2675 cleanup_vterm(term);
2676
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002677 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002678 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002679 aco_save_T aco;
2680
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002681 /* ++close or term_finish == "close" */
2682 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002683 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002684 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002685 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002686 break;
2687 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002688 if (term->tl_finish == TL_FINISH_OPEN
2689 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002690 {
2691 char buf[50];
2692
2693 /* TODO: use term_opencmd */
2694 ch_log(NULL, "terminal job finished, opening window");
2695 vim_snprintf(buf, sizeof(buf),
2696 term->tl_opencmd == NULL
2697 ? "botright sbuf %d"
2698 : (char *)term->tl_opencmd, fnum);
2699 do_cmdline_cmd((char_u *)buf);
2700 }
2701 else
2702 ch_log(NULL, "terminal job finished");
2703 }
2704
2705 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2706 }
2707 if (did_one)
2708 {
2709 redraw_statuslines();
2710
2711 /* Need to break out of vgetc(). */
2712 ins_char_typebuf(K_IGNORE);
2713 typebuf_was_filled = TRUE;
2714
2715 term = curbuf->b_term;
2716 if (term != NULL)
2717 {
2718 if (term->tl_job == ch->ch_job)
2719 maketitle();
2720 update_cursor(term, term->tl_cursor_visible);
2721 }
2722 }
2723}
2724
2725/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002726 * Fill one screen line from a line of the terminal.
2727 * Advances "pos" to past the last column.
2728 */
2729 static void
2730term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2731{
2732 int off = screen_get_current_line_off();
2733
2734 for (pos->col = 0; pos->col < max_col; )
2735 {
2736 VTermScreenCell cell;
2737 int c;
2738
2739 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2740 vim_memset(&cell, 0, sizeof(cell));
2741
2742 c = cell.chars[0];
2743 if (c == NUL)
2744 {
2745 ScreenLines[off] = ' ';
2746 if (enc_utf8)
2747 ScreenLinesUC[off] = NUL;
2748 }
2749 else
2750 {
2751 if (enc_utf8)
2752 {
2753 int i;
2754
2755 /* composing chars */
2756 for (i = 0; i < Screen_mco
2757 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2758 {
2759 ScreenLinesC[i][off] = cell.chars[i + 1];
2760 if (cell.chars[i + 1] == 0)
2761 break;
2762 }
2763 if (c >= 0x80 || (Screen_mco > 0
2764 && ScreenLinesC[0][off] != 0))
2765 {
2766 ScreenLines[off] = ' ';
2767 ScreenLinesUC[off] = c;
2768 }
2769 else
2770 {
2771 ScreenLines[off] = c;
2772 ScreenLinesUC[off] = NUL;
2773 }
2774 }
2775#ifdef WIN3264
2776 else if (has_mbyte && c >= 0x80)
2777 {
2778 char_u mb[MB_MAXBYTES+1];
2779 WCHAR wc = c;
2780
2781 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2782 (char*)mb, 2, 0, 0) > 1)
2783 {
2784 ScreenLines[off] = mb[0];
2785 ScreenLines[off + 1] = mb[1];
2786 cell.width = mb_ptr2cells(mb);
2787 }
2788 else
2789 ScreenLines[off] = c;
2790 }
2791#endif
2792 else
2793 ScreenLines[off] = c;
2794 }
2795 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2796
2797 ++pos->col;
2798 ++off;
2799 if (cell.width == 2)
2800 {
2801 if (enc_utf8)
2802 ScreenLinesUC[off] = NUL;
2803
2804 /* don't set the second byte to NUL for a DBCS encoding, it
2805 * has been set above */
2806 if (enc_utf8 || !has_mbyte)
2807 ScreenLines[off] = NUL;
2808
2809 ++pos->col;
2810 ++off;
2811 }
2812 }
2813}
2814
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002815#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002816 static void
2817update_system_term(term_T *term)
2818{
2819 VTermPos pos;
2820 VTermScreen *screen;
2821
2822 if (term->tl_vterm == NULL)
2823 return;
2824 screen = vterm_obtain_screen(term->tl_vterm);
2825
2826 /* Scroll up to make more room for terminal lines if needed. */
2827 while (term->tl_toprow > 0
2828 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2829 {
2830 int save_p_more = p_more;
2831
2832 p_more = FALSE;
2833 msg_row = Rows - 1;
2834 msg_puts((char_u *)"\n");
2835 p_more = save_p_more;
2836 --term->tl_toprow;
2837 }
2838
2839 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2840 && pos.row < Rows; ++pos.row)
2841 {
2842 if (pos.row < term->tl_rows)
2843 {
2844 int max_col = MIN(Columns, term->tl_cols);
2845
2846 term_line2screenline(screen, &pos, max_col);
2847 }
2848 else
2849 pos.col = 0;
2850
2851 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2852 }
2853
2854 term->tl_dirty_row_start = MAX_ROW;
2855 term->tl_dirty_row_end = 0;
2856 update_cursor(term, TRUE);
2857}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002858#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002859
2860/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002861 * Called to update a window that contains an active terminal.
2862 * Returns FAIL when there is no terminal running in this window or in
2863 * Terminal-Normal mode.
2864 */
2865 int
2866term_update_window(win_T *wp)
2867{
2868 term_T *term = wp->w_buffer->b_term;
2869 VTerm *vterm;
2870 VTermScreen *screen;
2871 VTermState *state;
2872 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002873 int rows, cols;
2874 int newrows, newcols;
2875 int minsize;
2876 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002877
2878 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2879 return FAIL;
2880
2881 vterm = term->tl_vterm;
2882 screen = vterm_obtain_screen(vterm);
2883 state = vterm_obtain_state(vterm);
2884
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002885 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002886 {
2887 term->tl_dirty_row_start = 0;
2888 term->tl_dirty_row_end = MAX_ROW;
2889 }
2890
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002891 /*
2892 * If the window was resized a redraw will be triggered and we get here.
2893 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2894 */
Bram Moolenaar498c2562018-04-15 23:45:15 +02002895 minsize = parse_termsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002896
Bram Moolenaar498c2562018-04-15 23:45:15 +02002897 newrows = 99999;
2898 newcols = 99999;
2899 FOR_ALL_WINDOWS(twp)
2900 {
2901 /* When more than one window shows the same terminal, use the
2902 * smallest size. */
2903 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002904 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02002905 newrows = MIN(newrows, twp->w_height);
2906 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002907 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02002908 }
2909 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
2910 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
2911
2912 if (term->tl_rows != newrows || term->tl_cols != newcols)
2913 {
2914
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002915
2916 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02002917 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002918 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02002919 newrows);
2920 term_report_winsize(term, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002921 }
2922
2923 /* The cursor may have been moved when resizing. */
2924 vterm_state_get_cursorpos(state, &pos);
2925 position_cursor(wp, &pos);
2926
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002927 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2928 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002929 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002930 if (pos.row < term->tl_rows)
2931 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002932 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002933
Bram Moolenaar13568252018-03-16 20:46:58 +01002934 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002935 }
2936 else
2937 pos.col = 0;
2938
Bram Moolenaarf118d482018-03-13 13:14:00 +01002939 screen_line(wp->w_winrow + pos.row
2940#ifdef FEAT_MENU
2941 + winbar_height(wp)
2942#endif
2943 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002944 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002945 term->tl_dirty_row_start = MAX_ROW;
2946 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002947
2948 return OK;
2949}
2950
2951/*
2952 * Return TRUE if "wp" is a terminal window where the job has finished.
2953 */
2954 int
2955term_is_finished(buf_T *buf)
2956{
2957 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2958}
2959
2960/*
2961 * Return TRUE if "wp" is a terminal window where the job has finished or we
2962 * are in Terminal-Normal mode, thus we show the buffer contents.
2963 */
2964 int
2965term_show_buffer(buf_T *buf)
2966{
2967 term_T *term = buf->b_term;
2968
2969 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2970}
2971
2972/*
2973 * The current buffer is going to be changed. If there is terminal
2974 * highlighting remove it now.
2975 */
2976 void
2977term_change_in_curbuf(void)
2978{
2979 term_T *term = curbuf->b_term;
2980
2981 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2982 {
2983 free_scrollback(term);
2984 redraw_buf_later(term->tl_buffer, NOT_VALID);
2985
2986 /* The buffer is now like a normal buffer, it cannot be easily
2987 * abandoned when changed. */
2988 set_string_option_direct((char_u *)"buftype", -1,
2989 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2990 }
2991}
2992
2993/*
2994 * Get the screen attribute for a position in the buffer.
2995 * Use a negative "col" to get the filler background color.
2996 */
2997 int
2998term_get_attr(buf_T *buf, linenr_T lnum, int col)
2999{
3000 term_T *term = buf->b_term;
3001 sb_line_T *line;
3002 cellattr_T *cellattr;
3003
3004 if (lnum > term->tl_scrollback.ga_len)
3005 cellattr = &term->tl_default_color;
3006 else
3007 {
3008 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3009 if (col < 0 || col >= line->sb_cols)
3010 cellattr = &line->sb_fill_attr;
3011 else
3012 cellattr = line->sb_cells + col;
3013 }
3014 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3015}
3016
3017static VTermColor ansi_table[16] = {
Bram Moolenaar46359e12017-11-29 22:33:38 +01003018 { 0, 0, 0, 1}, /* black */
3019 {224, 0, 0, 2}, /* dark red */
3020 { 0, 224, 0, 3}, /* dark green */
3021 {224, 224, 0, 4}, /* dark yellow / brown */
3022 { 0, 0, 224, 5}, /* dark blue */
3023 {224, 0, 224, 6}, /* dark magenta */
3024 { 0, 224, 224, 7}, /* dark cyan */
3025 {224, 224, 224, 8}, /* light grey */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003026
Bram Moolenaar46359e12017-11-29 22:33:38 +01003027 {128, 128, 128, 9}, /* dark grey */
3028 {255, 64, 64, 10}, /* light red */
3029 { 64, 255, 64, 11}, /* light green */
3030 {255, 255, 64, 12}, /* yellow */
3031 { 64, 64, 255, 13}, /* light blue */
3032 {255, 64, 255, 14}, /* light magenta */
3033 { 64, 255, 255, 15}, /* light cyan */
3034 {255, 255, 255, 16}, /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003035};
3036
3037static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003038 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003039};
3040
3041static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003042 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
3043 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003044};
3045
3046/*
3047 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003048 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003049 */
3050 static void
3051cterm_color2rgb(int nr, VTermColor *rgb)
3052{
3053 int idx;
3054
3055 if (nr < 16)
3056 {
3057 *rgb = ansi_table[nr];
3058 }
3059 else if (nr < 232)
3060 {
3061 /* 216 color cube */
3062 idx = nr - 16;
3063 rgb->blue = cube_value[idx % 6];
3064 rgb->green = cube_value[idx / 6 % 6];
3065 rgb->red = cube_value[idx / 36 % 6];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003066 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003067 }
3068 else if (nr < 256)
3069 {
3070 /* 24 grey scale ramp */
3071 idx = nr - 232;
3072 rgb->blue = grey_ramp[idx];
3073 rgb->green = grey_ramp[idx];
3074 rgb->red = grey_ramp[idx];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003075 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003076 }
3077}
3078
3079/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003080 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003081 */
3082 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003083init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003084{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003085 VTermColor *fg, *bg;
3086 int fgval, bgval;
3087 int id;
3088
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003089 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3090 term->tl_default_color.width = 1;
3091 fg = &term->tl_default_color.fg;
3092 bg = &term->tl_default_color.bg;
3093
3094 /* Vterm uses a default black background. Set it to white when
3095 * 'background' is "light". */
3096 if (*p_bg == 'l')
3097 {
3098 fgval = 0;
3099 bgval = 255;
3100 }
3101 else
3102 {
3103 fgval = 255;
3104 bgval = 0;
3105 }
3106 fg->red = fg->green = fg->blue = fgval;
3107 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003108 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003109
3110 /* The "Terminal" highlight group overrules the defaults. */
3111 id = syn_name2id((char_u *)"Terminal");
3112
Bram Moolenaar46359e12017-11-29 22:33:38 +01003113 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003114#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3115 if (0
3116# ifdef FEAT_GUI
3117 || gui.in_use
3118# endif
3119# ifdef FEAT_TERMGUICOLORS
3120 || p_tgc
3121# endif
3122 )
3123 {
3124 guicolor_T fg_rgb = INVALCOLOR;
3125 guicolor_T bg_rgb = INVALCOLOR;
3126
3127 if (id != 0)
3128 syn_id2colors(id, &fg_rgb, &bg_rgb);
3129
3130# ifdef FEAT_GUI
3131 if (gui.in_use)
3132 {
3133 if (fg_rgb == INVALCOLOR)
3134 fg_rgb = gui.norm_pixel;
3135 if (bg_rgb == INVALCOLOR)
3136 bg_rgb = gui.back_pixel;
3137 }
3138# ifdef FEAT_TERMGUICOLORS
3139 else
3140# endif
3141# endif
3142# ifdef FEAT_TERMGUICOLORS
3143 {
3144 if (fg_rgb == INVALCOLOR)
3145 fg_rgb = cterm_normal_fg_gui_color;
3146 if (bg_rgb == INVALCOLOR)
3147 bg_rgb = cterm_normal_bg_gui_color;
3148 }
3149# endif
3150 if (fg_rgb != INVALCOLOR)
3151 {
3152 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3153
3154 fg->red = (unsigned)(rgb >> 16);
3155 fg->green = (unsigned)(rgb >> 8) & 255;
3156 fg->blue = (unsigned)rgb & 255;
3157 }
3158 if (bg_rgb != INVALCOLOR)
3159 {
3160 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3161
3162 bg->red = (unsigned)(rgb >> 16);
3163 bg->green = (unsigned)(rgb >> 8) & 255;
3164 bg->blue = (unsigned)rgb & 255;
3165 }
3166 }
3167 else
3168#endif
3169 if (id != 0 && t_colors >= 16)
3170 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003171 if (term_default_cterm_fg >= 0)
3172 cterm_color2rgb(term_default_cterm_fg, fg);
3173 if (term_default_cterm_bg >= 0)
3174 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003175 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003176 else
3177 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003178#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003179 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003180#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003181
3182 /* In an MS-Windows console we know the normal colors. */
3183 if (cterm_normal_fg_color > 0)
3184 {
3185 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003186# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003187 tmp = fg->red;
3188 fg->red = fg->blue;
3189 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003190# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003191 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003192# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003193 else
3194 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003195# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003196
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003197 if (cterm_normal_bg_color > 0)
3198 {
3199 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003200# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003201 tmp = bg->red;
3202 bg->red = bg->blue;
3203 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003204# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003205 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003206# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003207 else
3208 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003209# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003210 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003211}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003212
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003213#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3214/*
3215 * Set the 16 ANSI colors from array of RGB values
3216 */
3217 static void
3218set_vterm_palette(VTerm *vterm, long_u *rgb)
3219{
3220 int index = 0;
3221 VTermState *state = vterm_obtain_state(vterm);
3222 for (; index < 16; index++)
3223 {
3224 VTermColor color;
3225 color.red = (unsigned)(rgb[index] >> 16);
3226 color.green = (unsigned)(rgb[index] >> 8) & 255;
3227 color.blue = (unsigned)rgb[index] & 255;
3228 vterm_state_set_palette_color(state, index, &color);
3229 }
3230}
3231
3232/*
3233 * Set the ANSI color palette from a list of colors
3234 */
3235 static int
3236set_ansi_colors_list(VTerm *vterm, list_T *list)
3237{
3238 int n = 0;
3239 long_u rgb[16];
3240 listitem_T *li = list->lv_first;
3241
3242 for (; li != NULL && n < 16; li = li->li_next, n++)
3243 {
3244 char_u *color_name;
3245 guicolor_T guicolor;
3246
3247 color_name = get_tv_string_chk(&li->li_tv);
3248 if (color_name == NULL)
3249 return FAIL;
3250
3251 guicolor = GUI_GET_COLOR(color_name);
3252 if (guicolor == INVALCOLOR)
3253 return FAIL;
3254
3255 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3256 }
3257
3258 if (n != 16 || li != NULL)
3259 return FAIL;
3260
3261 set_vterm_palette(vterm, rgb);
3262
3263 return OK;
3264}
3265
3266/*
3267 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3268 */
3269 static void
3270init_vterm_ansi_colors(VTerm *vterm)
3271{
3272 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3273
3274 if (var != NULL
3275 && (var->di_tv.v_type != VAR_LIST
3276 || var->di_tv.vval.v_list == NULL
3277 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
3278 EMSG2(_(e_invarg2), "g:terminal_ansi_colors");
3279}
3280#endif
3281
Bram Moolenaar52acb112018-03-18 19:20:22 +01003282/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003283 * Handles a "drop" command from the job in the terminal.
3284 * "item" is the file name, "item->li_next" may have options.
3285 */
3286 static void
3287handle_drop_command(listitem_T *item)
3288{
3289 char_u *fname = get_tv_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003290 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003291 int bufnr;
3292 win_T *wp;
3293 tabpage_T *tp;
3294 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003295 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003296
3297 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3298 FOR_ALL_TAB_WINDOWS(tp, wp)
3299 {
3300 if (wp->w_buffer->b_fnum == bufnr)
3301 {
3302 /* buffer is in a window already, go there */
3303 goto_tabpage_win(tp, wp);
3304 return;
3305 }
3306 }
3307
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003308 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003309
3310 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3311 && opt_item->li_tv.vval.v_dict != NULL)
3312 {
3313 dict_T *dict = opt_item->li_tv.vval.v_dict;
3314 char_u *p;
3315
3316 p = get_dict_string(dict, (char_u *)"ff", FALSE);
3317 if (p == NULL)
3318 p = get_dict_string(dict, (char_u *)"fileformat", FALSE);
3319 if (p != NULL)
3320 {
3321 if (check_ff_value(p) == FAIL)
3322 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3323 else
3324 ea.force_ff = *p;
3325 }
3326 p = get_dict_string(dict, (char_u *)"enc", FALSE);
3327 if (p == NULL)
3328 p = get_dict_string(dict, (char_u *)"encoding", FALSE);
3329 if (p != NULL)
3330 {
Bram Moolenaar3aa67fb2018-04-05 21:04:15 +02003331 ea.cmd = alloc((int)STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003332 if (ea.cmd != NULL)
3333 {
3334 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3335 ea.force_enc = 11;
3336 tofree = ea.cmd;
3337 }
3338 }
3339
3340 p = get_dict_string(dict, (char_u *)"bad", FALSE);
3341 if (p != NULL)
3342 get_bad_opt(p, &ea);
3343
3344 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3345 ea.force_bin = FORCE_BIN;
3346 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3347 ea.force_bin = FORCE_BIN;
3348 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3349 ea.force_bin = FORCE_NOBIN;
3350 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3351 ea.force_bin = FORCE_NOBIN;
3352 }
3353
3354 /* open in new window, like ":split fname" */
3355 if (ea.cmd == NULL)
3356 ea.cmd = (char_u *)"split";
3357 ea.arg = fname;
3358 ea.cmdidx = CMD_split;
3359 ex_splitview(&ea);
3360
3361 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003362}
3363
3364/*
3365 * Handles a function call from the job running in a terminal.
3366 * "item" is the function name, "item->li_next" has the arguments.
3367 */
3368 static void
3369handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3370{
3371 char_u *func;
3372 typval_T argvars[2];
3373 typval_T rettv;
3374 int doesrange;
3375
3376 if (item->li_next == NULL)
3377 {
3378 ch_log(channel, "Missing function arguments for call");
3379 return;
3380 }
3381 func = get_tv_string(&item->li_tv);
3382
Bram Moolenaar2a77d212018-03-26 21:38:52 +02003383 if (STRNCMP(func, "Tapi_", 5) != 0)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003384 {
3385 ch_log(channel, "Invalid function name: %s", func);
3386 return;
3387 }
3388
3389 argvars[0].v_type = VAR_NUMBER;
3390 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3391 argvars[1] = item->li_next->li_tv;
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003392 if (call_func(func, (int)STRLEN(func), &rettv,
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003393 2, argvars, /* argv_func */ NULL,
3394 /* firstline */ 1, /* lastline */ 1,
3395 &doesrange, /* evaluate */ TRUE,
3396 /* partial */ NULL, /* selfdict */ NULL) == OK)
3397 {
3398 clear_tv(&rettv);
3399 ch_log(channel, "Function %s called", func);
3400 }
3401 else
3402 ch_log(channel, "Calling function %s failed", func);
3403}
3404
3405/*
3406 * Called by libvterm when it cannot recognize an OSC sequence.
3407 * We recognize a terminal API command.
3408 */
3409 static int
3410parse_osc(const char *command, size_t cmdlen, void *user)
3411{
3412 term_T *term = (term_T *)user;
3413 js_read_T reader;
3414 typval_T tv;
3415 channel_T *channel = term->tl_job == NULL ? NULL
3416 : term->tl_job->jv_channel;
3417
3418 /* We recognize only OSC 5 1 ; {command} */
3419 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3420 return 0; /* not handled */
3421
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003422 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003423 if (reader.js_buf == NULL)
3424 return 1;
3425 reader.js_fill = NULL;
3426 reader.js_used = 0;
3427 if (json_decode(&reader, &tv, 0) == OK
3428 && tv.v_type == VAR_LIST
3429 && tv.vval.v_list != NULL)
3430 {
3431 listitem_T *item = tv.vval.v_list->lv_first;
3432
3433 if (item == NULL)
3434 ch_log(channel, "Missing command");
3435 else
3436 {
3437 char_u *cmd = get_tv_string(&item->li_tv);
3438
Bram Moolenaara997b452018-04-17 23:24:06 +02003439 /* Make sure an invoked command doesn't delete the buffer (and the
3440 * terminal) under our fingers. */
3441 ++term->tl_buffer->b_locked;
3442
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003443 item = item->li_next;
3444 if (item == NULL)
3445 ch_log(channel, "Missing argument for %s", cmd);
3446 else if (STRCMP(cmd, "drop") == 0)
3447 handle_drop_command(item);
3448 else if (STRCMP(cmd, "call") == 0)
3449 handle_call_command(term, channel, item);
3450 else
3451 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003452 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003453 }
3454 }
3455 else
3456 ch_log(channel, "Invalid JSON received");
3457
3458 vim_free(reader.js_buf);
3459 clear_tv(&tv);
3460 return 1;
3461}
3462
3463static VTermParserCallbacks parser_fallbacks = {
3464 NULL, /* text */
3465 NULL, /* control */
3466 NULL, /* escape */
3467 NULL, /* csi */
3468 parse_osc, /* osc */
3469 NULL, /* dcs */
3470 NULL /* resize */
3471};
3472
3473/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02003474 * Use Vim's allocation functions for vterm so profiling works.
3475 */
3476 static void *
3477vterm_malloc(size_t size, void *data UNUSED)
3478{
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02003479 return alloc_clear((unsigned) size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02003480}
3481
3482 static void
3483vterm_memfree(void *ptr, void *data UNUSED)
3484{
3485 vim_free(ptr);
3486}
3487
3488static VTermAllocatorFunctions vterm_allocator = {
3489 &vterm_malloc,
3490 &vterm_memfree
3491};
3492
3493/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003494 * Create a new vterm and initialize it.
3495 */
3496 static void
3497create_vterm(term_T *term, int rows, int cols)
3498{
3499 VTerm *vterm;
3500 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003501 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01003502 VTermValue value;
3503
Bram Moolenaar756ef112018-04-10 12:04:27 +02003504 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01003505 term->tl_vterm = vterm;
3506 screen = vterm_obtain_screen(vterm);
3507 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3508 /* TODO: depends on 'encoding'. */
3509 vterm_set_utf8(vterm, 1);
3510
3511 init_default_colors(term);
3512
3513 vterm_state_set_default_colors(
3514 vterm_obtain_state(vterm),
3515 &term->tl_default_color.fg,
3516 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003517
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003518 if (t_colors >= 16)
3519 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
3520
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003521 /* Required to initialize most things. */
3522 vterm_screen_reset(screen, 1 /* hard */);
3523
3524 /* Allow using alternate screen. */
3525 vterm_screen_enable_altscreen(screen, 1);
3526
3527 /* For unix do not use a blinking cursor. In an xterm this causes the
3528 * cursor to blink if it's blinking in the xterm.
3529 * For Windows we respect the system wide setting. */
3530#ifdef WIN3264
3531 if (GetCaretBlinkTime() == INFINITE)
3532 value.boolean = 0;
3533 else
3534 value.boolean = 1;
3535#else
3536 value.boolean = 0;
3537#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003538 state = vterm_obtain_state(vterm);
3539 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
3540 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003541}
3542
3543/*
3544 * Return the text to show for the buffer name and status.
3545 */
3546 char_u *
3547term_get_status_text(term_T *term)
3548{
3549 if (term->tl_status_text == NULL)
3550 {
3551 char_u *txt;
3552 size_t len;
3553
3554 if (term->tl_normal_mode)
3555 {
3556 if (term_job_running(term))
3557 txt = (char_u *)_("Terminal");
3558 else
3559 txt = (char_u *)_("Terminal-finished");
3560 }
3561 else if (term->tl_title != NULL)
3562 txt = term->tl_title;
3563 else if (term_none_open(term))
3564 txt = (char_u *)_("active");
3565 else if (term_job_running(term))
3566 txt = (char_u *)_("running");
3567 else
3568 txt = (char_u *)_("finished");
3569 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3570 term->tl_status_text = alloc((int)len);
3571 if (term->tl_status_text != NULL)
3572 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3573 term->tl_buffer->b_fname, txt);
3574 }
3575 return term->tl_status_text;
3576}
3577
3578/*
3579 * Mark references in jobs of terminals.
3580 */
3581 int
3582set_ref_in_term(int copyID)
3583{
3584 int abort = FALSE;
3585 term_T *term;
3586 typval_T tv;
3587
3588 for (term = first_term; term != NULL; term = term->tl_next)
3589 if (term->tl_job != NULL)
3590 {
3591 tv.v_type = VAR_JOB;
3592 tv.vval.v_job = term->tl_job;
3593 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3594 }
3595 return abort;
3596}
3597
3598/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003599 * Cache "Terminal" highlight group colors.
3600 */
3601 void
3602set_terminal_default_colors(int cterm_fg, int cterm_bg)
3603{
3604 term_default_cterm_fg = cterm_fg - 1;
3605 term_default_cterm_bg = cterm_bg - 1;
3606}
3607
3608/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003609 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003610 * Returns NULL when the buffer is not for a terminal window and logs a message
3611 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003612 */
3613 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003614term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003615{
3616 buf_T *buf;
3617
3618 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3619 ++emsg_off;
3620 buf = get_buf_tv(&argvars[0], FALSE);
3621 --emsg_off;
3622 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003623 {
3624 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003625 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003626 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003627 return buf;
3628}
3629
Bram Moolenaard96ff162018-02-18 22:13:29 +01003630 static int
3631same_color(VTermColor *a, VTermColor *b)
3632{
3633 return a->red == b->red
3634 && a->green == b->green
3635 && a->blue == b->blue
3636 && a->ansi_index == b->ansi_index;
3637}
3638
3639 static void
3640dump_term_color(FILE *fd, VTermColor *color)
3641{
3642 fprintf(fd, "%02x%02x%02x%d",
3643 (int)color->red, (int)color->green, (int)color->blue,
3644 (int)color->ansi_index);
3645}
3646
3647/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003648 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003649 *
3650 * Each screen cell in full is:
3651 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3652 * {characters} is a space for an empty cell
3653 * For a double-width character "+" is changed to "*" and the next cell is
3654 * skipped.
3655 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3656 * when "&" use the same as the previous cell.
3657 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3658 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3659 * {color-idx} is a number from 0 to 255
3660 *
3661 * Screen cell with same width, attributes and color as the previous one:
3662 * |{characters}
3663 *
3664 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3665 *
3666 * Repeating the previous screen cell:
3667 * @{count}
3668 */
3669 void
3670f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3671{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003672 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003673 term_T *term;
3674 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003675 int max_height = 0;
3676 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003677 stat_T st;
3678 FILE *fd;
3679 VTermPos pos;
3680 VTermScreen *screen;
3681 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003682 VTermState *state;
3683 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003684
3685 if (check_restricted() || check_secure())
3686 return;
3687 if (buf == NULL)
3688 return;
3689 term = buf->b_term;
3690
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003691 if (argvars[2].v_type != VAR_UNKNOWN)
3692 {
3693 dict_T *d;
3694
3695 if (argvars[2].v_type != VAR_DICT)
3696 {
3697 EMSG(_(e_dictreq));
3698 return;
3699 }
3700 d = argvars[2].vval.v_dict;
3701 if (d != NULL)
3702 {
3703 max_height = get_dict_number(d, (char_u *)"rows");
3704 max_width = get_dict_number(d, (char_u *)"columns");
3705 }
3706 }
3707
Bram Moolenaard96ff162018-02-18 22:13:29 +01003708 fname = get_tv_string_chk(&argvars[1]);
3709 if (fname == NULL)
3710 return;
3711 if (mch_stat((char *)fname, &st) >= 0)
3712 {
3713 EMSG2(_("E953: File exists: %s"), fname);
3714 return;
3715 }
3716
Bram Moolenaard96ff162018-02-18 22:13:29 +01003717 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3718 {
3719 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3720 return;
3721 }
3722
3723 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3724
3725 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003726 state = vterm_obtain_state(term->tl_vterm);
3727 vterm_state_get_cursorpos(state, &cursor_pos);
3728
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003729 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3730 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003731 {
3732 int repeat = 0;
3733
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003734 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3735 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003736 {
3737 VTermScreenCell cell;
3738 int same_attr;
3739 int same_chars = TRUE;
3740 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003741 int is_cursor_pos = (pos.col == cursor_pos.col
3742 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003743
3744 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3745 vim_memset(&cell, 0, sizeof(cell));
3746
3747 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3748 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01003749 int c = cell.chars[i];
3750 int pc = prev_cell.chars[i];
3751
3752 /* For the first character NUL is the same as space. */
3753 if (i == 0)
3754 {
3755 c = (c == NUL) ? ' ' : c;
3756 pc = (pc == NUL) ? ' ' : pc;
3757 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003758 if (cell.chars[i] != prev_cell.chars[i])
3759 same_chars = FALSE;
3760 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3761 break;
3762 }
3763 same_attr = vtermAttr2hl(cell.attrs)
3764 == vtermAttr2hl(prev_cell.attrs)
3765 && same_color(&cell.fg, &prev_cell.fg)
3766 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003767 if (same_chars && cell.width == prev_cell.width && same_attr
3768 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003769 {
3770 ++repeat;
3771 }
3772 else
3773 {
3774 if (repeat > 0)
3775 {
3776 fprintf(fd, "@%d", repeat);
3777 repeat = 0;
3778 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003779 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003780
3781 if (cell.chars[0] == NUL)
3782 fputs(" ", fd);
3783 else
3784 {
3785 char_u charbuf[10];
3786 int len;
3787
3788 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3789 && cell.chars[i] != NUL; ++i)
3790 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02003791 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003792 fwrite(charbuf, len, 1, fd);
3793 }
3794 }
3795
3796 /* When only the characters differ we don't write anything, the
3797 * following "|", "@" or NL will indicate using the same
3798 * attributes. */
3799 if (cell.width != prev_cell.width || !same_attr)
3800 {
3801 if (cell.width == 2)
3802 {
3803 fputs("*", fd);
3804 ++pos.col;
3805 }
3806 else
3807 fputs("+", fd);
3808
3809 if (same_attr)
3810 {
3811 fputs("&", fd);
3812 }
3813 else
3814 {
3815 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3816 if (same_color(&cell.fg, &prev_cell.fg))
3817 fputs("&", fd);
3818 else
3819 {
3820 fputs("#", fd);
3821 dump_term_color(fd, &cell.fg);
3822 }
3823 if (same_color(&cell.bg, &prev_cell.bg))
3824 fputs("&", fd);
3825 else
3826 {
3827 fputs("#", fd);
3828 dump_term_color(fd, &cell.bg);
3829 }
3830 }
3831 }
3832
3833 prev_cell = cell;
3834 }
3835 }
3836 if (repeat > 0)
3837 fprintf(fd, "@%d", repeat);
3838 fputs("\n", fd);
3839 }
3840
3841 fclose(fd);
3842}
3843
3844/*
3845 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3846 */
3847 static void
3848dump_is_corrupt(garray_T *gap)
3849{
3850 ga_concat(gap, (char_u *)"CORRUPT");
3851}
3852
3853 static void
3854append_cell(garray_T *gap, cellattr_T *cell)
3855{
3856 if (ga_grow(gap, 1) == OK)
3857 {
3858 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3859 ++gap->ga_len;
3860 }
3861}
3862
3863/*
3864 * Read the dump file from "fd" and append lines to the current buffer.
3865 * Return the cell width of the longest line.
3866 */
3867 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003868read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003869{
3870 int c;
3871 garray_T ga_text;
3872 garray_T ga_cell;
3873 char_u *prev_char = NULL;
3874 int attr = 0;
3875 cellattr_T cell;
3876 term_T *term = curbuf->b_term;
3877 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003878 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003879
3880 ga_init2(&ga_text, 1, 90);
3881 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3882 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003883 cursor_pos->row = -1;
3884 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003885
3886 c = fgetc(fd);
3887 for (;;)
3888 {
3889 if (c == EOF)
3890 break;
3891 if (c == '\n')
3892 {
3893 /* End of a line: append it to the buffer. */
3894 if (ga_text.ga_data == NULL)
3895 dump_is_corrupt(&ga_text);
3896 if (ga_grow(&term->tl_scrollback, 1) == OK)
3897 {
3898 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3899 + term->tl_scrollback.ga_len;
3900
3901 if (max_cells < ga_cell.ga_len)
3902 max_cells = ga_cell.ga_len;
3903 line->sb_cols = ga_cell.ga_len;
3904 line->sb_cells = ga_cell.ga_data;
3905 line->sb_fill_attr = term->tl_default_color;
3906 ++term->tl_scrollback.ga_len;
3907 ga_init(&ga_cell);
3908
3909 ga_append(&ga_text, NUL);
3910 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3911 ga_text.ga_len, FALSE);
3912 }
3913 else
3914 ga_clear(&ga_cell);
3915 ga_text.ga_len = 0;
3916
3917 c = fgetc(fd);
3918 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003919 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003920 {
3921 int prev_len = ga_text.ga_len;
3922
Bram Moolenaar9271d052018-02-25 21:39:46 +01003923 if (c == '>')
3924 {
3925 if (cursor_pos->row != -1)
3926 dump_is_corrupt(&ga_text); /* duplicate cursor */
3927 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3928 cursor_pos->col = ga_cell.ga_len;
3929 }
3930
Bram Moolenaard96ff162018-02-18 22:13:29 +01003931 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3932 c = fgetc(fd);
3933 if (c != EOF)
3934 ga_append(&ga_text, c);
3935 for (;;)
3936 {
3937 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003938 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003939 || c == EOF || c == '\n')
3940 break;
3941 ga_append(&ga_text, c);
3942 }
3943
3944 /* save the character for repeating it */
3945 vim_free(prev_char);
3946 if (ga_text.ga_data != NULL)
3947 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3948 ga_text.ga_len - prev_len);
3949
Bram Moolenaar9271d052018-02-25 21:39:46 +01003950 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003951 {
3952 /* use all attributes from previous cell */
3953 }
3954 else if (c == '+' || c == '*')
3955 {
3956 int is_bg;
3957
3958 cell.width = c == '+' ? 1 : 2;
3959
3960 c = fgetc(fd);
3961 if (c == '&')
3962 {
3963 /* use same attr as previous cell */
3964 c = fgetc(fd);
3965 }
3966 else if (isdigit(c))
3967 {
3968 /* get the decimal attribute */
3969 attr = 0;
3970 while (isdigit(c))
3971 {
3972 attr = attr * 10 + (c - '0');
3973 c = fgetc(fd);
3974 }
3975 hl2vtermAttr(attr, &cell);
3976 }
3977 else
3978 dump_is_corrupt(&ga_text);
3979
3980 /* is_bg == 0: fg, is_bg == 1: bg */
3981 for (is_bg = 0; is_bg <= 1; ++is_bg)
3982 {
3983 if (c == '&')
3984 {
3985 /* use same color as previous cell */
3986 c = fgetc(fd);
3987 }
3988 else if (c == '#')
3989 {
3990 int red, green, blue, index = 0;
3991
3992 c = fgetc(fd);
3993 red = hex2nr(c);
3994 c = fgetc(fd);
3995 red = (red << 4) + hex2nr(c);
3996 c = fgetc(fd);
3997 green = hex2nr(c);
3998 c = fgetc(fd);
3999 green = (green << 4) + hex2nr(c);
4000 c = fgetc(fd);
4001 blue = hex2nr(c);
4002 c = fgetc(fd);
4003 blue = (blue << 4) + hex2nr(c);
4004 c = fgetc(fd);
4005 if (!isdigit(c))
4006 dump_is_corrupt(&ga_text);
4007 while (isdigit(c))
4008 {
4009 index = index * 10 + (c - '0');
4010 c = fgetc(fd);
4011 }
4012
4013 if (is_bg)
4014 {
4015 cell.bg.red = red;
4016 cell.bg.green = green;
4017 cell.bg.blue = blue;
4018 cell.bg.ansi_index = index;
4019 }
4020 else
4021 {
4022 cell.fg.red = red;
4023 cell.fg.green = green;
4024 cell.fg.blue = blue;
4025 cell.fg.ansi_index = index;
4026 }
4027 }
4028 else
4029 dump_is_corrupt(&ga_text);
4030 }
4031 }
4032 else
4033 dump_is_corrupt(&ga_text);
4034
4035 append_cell(&ga_cell, &cell);
4036 }
4037 else if (c == '@')
4038 {
4039 if (prev_char == NULL)
4040 dump_is_corrupt(&ga_text);
4041 else
4042 {
4043 int count = 0;
4044
4045 /* repeat previous character, get the count */
4046 for (;;)
4047 {
4048 c = fgetc(fd);
4049 if (!isdigit(c))
4050 break;
4051 count = count * 10 + (c - '0');
4052 }
4053
4054 while (count-- > 0)
4055 {
4056 ga_concat(&ga_text, prev_char);
4057 append_cell(&ga_cell, &cell);
4058 }
4059 }
4060 }
4061 else
4062 {
4063 dump_is_corrupt(&ga_text);
4064 c = fgetc(fd);
4065 }
4066 }
4067
4068 if (ga_text.ga_len > 0)
4069 {
4070 /* trailing characters after last NL */
4071 dump_is_corrupt(&ga_text);
4072 ga_append(&ga_text, NUL);
4073 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4074 ga_text.ga_len, FALSE);
4075 }
4076
4077 ga_clear(&ga_text);
4078 vim_free(prev_char);
4079
4080 return max_cells;
4081}
4082
4083/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004084 * Return an allocated string with at least "text_width" "=" characters and
4085 * "fname" inserted in the middle.
4086 */
4087 static char_u *
4088get_separator(int text_width, char_u *fname)
4089{
4090 int width = MAX(text_width, curwin->w_width);
4091 char_u *textline;
4092 int fname_size;
4093 char_u *p = fname;
4094 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004095 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004096
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004097 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004098 if (textline == NULL)
4099 return NULL;
4100
4101 fname_size = vim_strsize(fname);
4102 if (fname_size < width - 8)
4103 {
4104 /* enough room, don't use the full window width */
4105 width = MAX(text_width, fname_size + 8);
4106 }
4107 else if (fname_size > width - 8)
4108 {
4109 /* full name doesn't fit, use only the tail */
4110 p = gettail(fname);
4111 fname_size = vim_strsize(p);
4112 }
4113 /* skip characters until the name fits */
4114 while (fname_size > width - 8)
4115 {
4116 p += (*mb_ptr2len)(p);
4117 fname_size = vim_strsize(p);
4118 }
4119
4120 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4121 textline[i] = '=';
4122 textline[i++] = ' ';
4123
4124 STRCPY(textline + i, p);
4125 off = STRLEN(textline);
4126 textline[off] = ' ';
4127 for (i = 1; i < (width - fname_size) / 2; ++i)
4128 textline[off + i] = '=';
4129 textline[off + i] = NUL;
4130
4131 return textline;
4132}
4133
4134/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004135 * Common for "term_dumpdiff()" and "term_dumpload()".
4136 */
4137 static void
4138term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4139{
4140 jobopt_T opt;
4141 buf_T *buf;
4142 char_u buf1[NUMBUFLEN];
4143 char_u buf2[NUMBUFLEN];
4144 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004145 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004146 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004147 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004148 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004149 char_u *textline = NULL;
4150
4151 /* First open the files. If this fails bail out. */
4152 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
4153 if (do_diff)
4154 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
4155 if (fname1 == NULL || (do_diff && fname2 == NULL))
4156 {
4157 EMSG(_(e_invarg));
4158 return;
4159 }
4160 fd1 = mch_fopen((char *)fname1, READBIN);
4161 if (fd1 == NULL)
4162 {
4163 EMSG2(_(e_notread), fname1);
4164 return;
4165 }
4166 if (do_diff)
4167 {
4168 fd2 = mch_fopen((char *)fname2, READBIN);
4169 if (fd2 == NULL)
4170 {
4171 fclose(fd1);
4172 EMSG2(_(e_notread), fname2);
4173 return;
4174 }
4175 }
4176
4177 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004178 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4179 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4180 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4181 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4182 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004183
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004184 if (opt.jo_term_name == NULL)
4185 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004186 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004187
Bram Moolenaarb571c632018-03-21 22:27:59 +01004188 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004189 if (fname_tofree != NULL)
4190 {
4191 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4192 opt.jo_term_name = fname_tofree;
4193 }
4194 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004195
Bram Moolenaar13568252018-03-16 20:46:58 +01004196 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004197 if (buf != NULL && buf->b_term != NULL)
4198 {
4199 int i;
4200 linenr_T bot_lnum;
4201 linenr_T lnum;
4202 term_T *term = buf->b_term;
4203 int width;
4204 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004205 VTermPos cursor_pos1;
4206 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004207
Bram Moolenaar52acb112018-03-18 19:20:22 +01004208 init_default_colors(term);
4209
Bram Moolenaard96ff162018-02-18 22:13:29 +01004210 rettv->vval.v_number = buf->b_fnum;
4211
4212 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004213 width = read_dump_file(fd1, &cursor_pos1);
4214
4215 /* position the cursor */
4216 if (cursor_pos1.row >= 0)
4217 {
4218 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4219 coladvance(cursor_pos1.col);
4220 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004221
4222 /* Delete the empty line that was in the empty buffer. */
4223 ml_delete(1, FALSE);
4224
4225 /* For term_dumpload() we are done here. */
4226 if (!do_diff)
4227 goto theend;
4228
4229 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4230
Bram Moolenaar4a696342018-04-05 18:45:26 +02004231 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004232 if (textline == NULL)
4233 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004234 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4235 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4236 vim_free(textline);
4237
4238 textline = get_separator(width, fname2);
4239 if (textline == NULL)
4240 goto theend;
4241 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4242 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004243 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004244
4245 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004246 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004247 if (width2 > width)
4248 {
4249 vim_free(textline);
4250 textline = alloc(width2 + 1);
4251 if (textline == NULL)
4252 goto theend;
4253 width = width2;
4254 textline[width] = NUL;
4255 }
4256 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4257
4258 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4259 {
4260 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4261 {
4262 /* bottom part has fewer rows, fill with "-" */
4263 for (i = 0; i < width; ++i)
4264 textline[i] = '-';
4265 }
4266 else
4267 {
4268 char_u *line1;
4269 char_u *line2;
4270 char_u *p1;
4271 char_u *p2;
4272 int col;
4273 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4274 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4275 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4276 ->sb_cells;
4277
4278 /* Make a copy, getting the second line will invalidate it. */
4279 line1 = vim_strsave(ml_get(lnum));
4280 if (line1 == NULL)
4281 break;
4282 p1 = line1;
4283
4284 line2 = ml_get(lnum + bot_lnum);
4285 p2 = line2;
4286 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4287 {
4288 int len1 = utfc_ptr2len(p1);
4289 int len2 = utfc_ptr2len(p2);
4290
4291 textline[col] = ' ';
4292 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004293 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004294 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004295 else if (lnum == cursor_pos1.row + 1
4296 && col == cursor_pos1.col
4297 && (cursor_pos1.row != cursor_pos2.row
4298 || cursor_pos1.col != cursor_pos2.col))
4299 /* cursor in first but not in second */
4300 textline[col] = '>';
4301 else if (lnum == cursor_pos2.row + 1
4302 && col == cursor_pos2.col
4303 && (cursor_pos1.row != cursor_pos2.row
4304 || cursor_pos1.col != cursor_pos2.col))
4305 /* cursor in second but not in first */
4306 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004307 else if (cellattr1 != NULL && cellattr2 != NULL)
4308 {
4309 if ((cellattr1 + col)->width
4310 != (cellattr2 + col)->width)
4311 textline[col] = 'w';
4312 else if (!same_color(&(cellattr1 + col)->fg,
4313 &(cellattr2 + col)->fg))
4314 textline[col] = 'f';
4315 else if (!same_color(&(cellattr1 + col)->bg,
4316 &(cellattr2 + col)->bg))
4317 textline[col] = 'b';
4318 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4319 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4320 textline[col] = 'a';
4321 }
4322 p1 += len1;
4323 p2 += len2;
4324 /* TODO: handle different width */
4325 }
4326 vim_free(line1);
4327
4328 while (col < width)
4329 {
4330 if (*p1 == NUL && *p2 == NUL)
4331 textline[col] = '?';
4332 else if (*p1 == NUL)
4333 {
4334 textline[col] = '+';
4335 p2 += utfc_ptr2len(p2);
4336 }
4337 else
4338 {
4339 textline[col] = '-';
4340 p1 += utfc_ptr2len(p1);
4341 }
4342 ++col;
4343 }
4344 }
4345 if (add_empty_scrollback(term, &term->tl_default_color,
4346 term->tl_top_diff_rows) == OK)
4347 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4348 ++bot_lnum;
4349 }
4350
4351 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4352 {
4353 /* bottom part has more rows, fill with "+" */
4354 for (i = 0; i < width; ++i)
4355 textline[i] = '+';
4356 if (add_empty_scrollback(term, &term->tl_default_color,
4357 term->tl_top_diff_rows) == OK)
4358 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4359 ++lnum;
4360 ++bot_lnum;
4361 }
4362
4363 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004364
4365 /* looks better without wrapping */
4366 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004367 }
4368
4369theend:
4370 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004371 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004372 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004373 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004374 fclose(fd2);
4375}
4376
4377/*
4378 * If the current buffer shows the output of term_dumpdiff(), swap the top and
4379 * bottom files.
4380 * Return FAIL when this is not possible.
4381 */
4382 int
4383term_swap_diff()
4384{
4385 term_T *term = curbuf->b_term;
4386 linenr_T line_count;
4387 linenr_T top_rows;
4388 linenr_T bot_rows;
4389 linenr_T bot_start;
4390 linenr_T lnum;
4391 char_u *p;
4392 sb_line_T *sb_line;
4393
4394 if (term == NULL
4395 || !term_is_finished(curbuf)
4396 || term->tl_top_diff_rows == 0
4397 || term->tl_scrollback.ga_len == 0)
4398 return FAIL;
4399
4400 line_count = curbuf->b_ml.ml_line_count;
4401 top_rows = term->tl_top_diff_rows;
4402 bot_rows = term->tl_bot_diff_rows;
4403 bot_start = line_count - bot_rows;
4404 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4405
4406 /* move lines from top to above the bottom part */
4407 for (lnum = 1; lnum <= top_rows; ++lnum)
4408 {
4409 p = vim_strsave(ml_get(1));
4410 if (p == NULL)
4411 return OK;
4412 ml_append(bot_start, p, 0, FALSE);
4413 ml_delete(1, FALSE);
4414 vim_free(p);
4415 }
4416
4417 /* move lines from bottom to the top */
4418 for (lnum = 1; lnum <= bot_rows; ++lnum)
4419 {
4420 p = vim_strsave(ml_get(bot_start + lnum));
4421 if (p == NULL)
4422 return OK;
4423 ml_delete(bot_start + lnum, FALSE);
4424 ml_append(lnum - 1, p, 0, FALSE);
4425 vim_free(p);
4426 }
4427
4428 if (top_rows == bot_rows)
4429 {
4430 /* rows counts are equal, can swap cell properties */
4431 for (lnum = 0; lnum < top_rows; ++lnum)
4432 {
4433 sb_line_T temp;
4434
4435 temp = *(sb_line + lnum);
4436 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4437 *(sb_line + bot_start + lnum) = temp;
4438 }
4439 }
4440 else
4441 {
4442 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4443 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4444
4445 /* need to copy cell properties into temp memory */
4446 if (temp != NULL)
4447 {
4448 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4449 mch_memmove(term->tl_scrollback.ga_data,
4450 temp + bot_start,
4451 sizeof(sb_line_T) * bot_rows);
4452 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4453 temp + top_rows,
4454 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4455 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4456 + line_count - top_rows,
4457 temp,
4458 sizeof(sb_line_T) * top_rows);
4459 vim_free(temp);
4460 }
4461 }
4462
4463 term->tl_top_diff_rows = bot_rows;
4464 term->tl_bot_diff_rows = top_rows;
4465
4466 update_screen(NOT_VALID);
4467 return OK;
4468}
4469
4470/*
4471 * "term_dumpdiff(filename, filename, options)" function
4472 */
4473 void
4474f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4475{
4476 term_load_dump(argvars, rettv, TRUE);
4477}
4478
4479/*
4480 * "term_dumpload(filename, options)" function
4481 */
4482 void
4483f_term_dumpload(typval_T *argvars, typval_T *rettv)
4484{
4485 term_load_dump(argvars, rettv, FALSE);
4486}
4487
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004488/*
4489 * "term_getaltscreen(buf)" function
4490 */
4491 void
4492f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4493{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004494 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004495
4496 if (buf == NULL)
4497 return;
4498 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4499}
4500
4501/*
4502 * "term_getattr(attr, name)" function
4503 */
4504 void
4505f_term_getattr(typval_T *argvars, typval_T *rettv)
4506{
4507 int attr;
4508 size_t i;
4509 char_u *name;
4510
4511 static struct {
4512 char *name;
4513 int attr;
4514 } attrs[] = {
4515 {"bold", HL_BOLD},
4516 {"italic", HL_ITALIC},
4517 {"underline", HL_UNDERLINE},
4518 {"strike", HL_STRIKETHROUGH},
4519 {"reverse", HL_INVERSE},
4520 };
4521
4522 attr = get_tv_number(&argvars[0]);
4523 name = get_tv_string_chk(&argvars[1]);
4524 if (name == NULL)
4525 return;
4526
4527 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4528 if (STRCMP(name, attrs[i].name) == 0)
4529 {
4530 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4531 break;
4532 }
4533}
4534
4535/*
4536 * "term_getcursor(buf)" function
4537 */
4538 void
4539f_term_getcursor(typval_T *argvars, typval_T *rettv)
4540{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004541 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004542 term_T *term;
4543 list_T *l;
4544 dict_T *d;
4545
4546 if (rettv_list_alloc(rettv) == FAIL)
4547 return;
4548 if (buf == NULL)
4549 return;
4550 term = buf->b_term;
4551
4552 l = rettv->vval.v_list;
4553 list_append_number(l, term->tl_cursor_pos.row + 1);
4554 list_append_number(l, term->tl_cursor_pos.col + 1);
4555
4556 d = dict_alloc();
4557 if (d != NULL)
4558 {
4559 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4560 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4561 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4562 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4563 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4564 ? (char_u *)"" : term->tl_cursor_color);
4565 list_append_dict(l, d);
4566 }
4567}
4568
4569/*
4570 * "term_getjob(buf)" function
4571 */
4572 void
4573f_term_getjob(typval_T *argvars, typval_T *rettv)
4574{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004575 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004576
4577 rettv->v_type = VAR_JOB;
4578 rettv->vval.v_job = NULL;
4579 if (buf == NULL)
4580 return;
4581
4582 rettv->vval.v_job = buf->b_term->tl_job;
4583 if (rettv->vval.v_job != NULL)
4584 ++rettv->vval.v_job->jv_refcount;
4585}
4586
4587 static int
4588get_row_number(typval_T *tv, term_T *term)
4589{
4590 if (tv->v_type == VAR_STRING
4591 && tv->vval.v_string != NULL
4592 && STRCMP(tv->vval.v_string, ".") == 0)
4593 return term->tl_cursor_pos.row;
4594 return (int)get_tv_number(tv) - 1;
4595}
4596
4597/*
4598 * "term_getline(buf, row)" function
4599 */
4600 void
4601f_term_getline(typval_T *argvars, typval_T *rettv)
4602{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004603 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004604 term_T *term;
4605 int row;
4606
4607 rettv->v_type = VAR_STRING;
4608 if (buf == NULL)
4609 return;
4610 term = buf->b_term;
4611 row = get_row_number(&argvars[1], term);
4612
4613 if (term->tl_vterm == NULL)
4614 {
4615 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4616
4617 /* vterm is finished, get the text from the buffer */
4618 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4619 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4620 }
4621 else
4622 {
4623 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4624 VTermRect rect;
4625 int len;
4626 char_u *p;
4627
4628 if (row < 0 || row >= term->tl_rows)
4629 return;
4630 len = term->tl_cols * MB_MAXBYTES + 1;
4631 p = alloc(len);
4632 if (p == NULL)
4633 return;
4634 rettv->vval.v_string = p;
4635
4636 rect.start_col = 0;
4637 rect.end_col = term->tl_cols;
4638 rect.start_row = row;
4639 rect.end_row = row + 1;
4640 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4641 }
4642}
4643
4644/*
4645 * "term_getscrolled(buf)" function
4646 */
4647 void
4648f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4649{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004650 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004651
4652 if (buf == NULL)
4653 return;
4654 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4655}
4656
4657/*
4658 * "term_getsize(buf)" function
4659 */
4660 void
4661f_term_getsize(typval_T *argvars, typval_T *rettv)
4662{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004663 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004664 list_T *l;
4665
4666 if (rettv_list_alloc(rettv) == FAIL)
4667 return;
4668 if (buf == NULL)
4669 return;
4670
4671 l = rettv->vval.v_list;
4672 list_append_number(l, buf->b_term->tl_rows);
4673 list_append_number(l, buf->b_term->tl_cols);
4674}
4675
4676/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02004677 * "term_setsize(buf, rows, cols)" function
4678 */
4679 void
4680f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4681{
4682 buf_T *buf = term_get_buf(argvars, "term_setsize()");
4683 term_T *term;
4684 varnumber_T rows, cols;
4685
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02004686 if (buf == NULL)
4687 {
4688 EMSG(_("E955: Not a terminal buffer"));
4689 return;
4690 }
4691 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02004692 return;
4693 term = buf->b_term;
4694 rows = get_tv_number(&argvars[1]);
4695 rows = rows <= 0 ? term->tl_rows : rows;
4696 cols = get_tv_number(&argvars[2]);
4697 cols = cols <= 0 ? term->tl_cols : cols;
4698 vterm_set_size(term->tl_vterm, rows, cols);
4699 /* handle_resize() will resize the windows */
4700
4701 /* Get and remember the size we ended up with. Update the pty. */
4702 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
4703 term_report_winsize(term, term->tl_rows, term->tl_cols);
4704}
4705
4706/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004707 * "term_getstatus(buf)" function
4708 */
4709 void
4710f_term_getstatus(typval_T *argvars, typval_T *rettv)
4711{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004712 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004713 term_T *term;
4714 char_u val[100];
4715
4716 rettv->v_type = VAR_STRING;
4717 if (buf == NULL)
4718 return;
4719 term = buf->b_term;
4720
4721 if (term_job_running(term))
4722 STRCPY(val, "running");
4723 else
4724 STRCPY(val, "finished");
4725 if (term->tl_normal_mode)
4726 STRCAT(val, ",normal");
4727 rettv->vval.v_string = vim_strsave(val);
4728}
4729
4730/*
4731 * "term_gettitle(buf)" function
4732 */
4733 void
4734f_term_gettitle(typval_T *argvars, typval_T *rettv)
4735{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004736 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004737
4738 rettv->v_type = VAR_STRING;
4739 if (buf == NULL)
4740 return;
4741
4742 if (buf->b_term->tl_title != NULL)
4743 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4744}
4745
4746/*
4747 * "term_gettty(buf)" function
4748 */
4749 void
4750f_term_gettty(typval_T *argvars, typval_T *rettv)
4751{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004752 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004753 char_u *p;
4754 int num = 0;
4755
4756 rettv->v_type = VAR_STRING;
4757 if (buf == NULL)
4758 return;
4759 if (argvars[1].v_type != VAR_UNKNOWN)
4760 num = get_tv_number(&argvars[1]);
4761
4762 switch (num)
4763 {
4764 case 0:
4765 if (buf->b_term->tl_job != NULL)
4766 p = buf->b_term->tl_job->jv_tty_out;
4767 else
4768 p = buf->b_term->tl_tty_out;
4769 break;
4770 case 1:
4771 if (buf->b_term->tl_job != NULL)
4772 p = buf->b_term->tl_job->jv_tty_in;
4773 else
4774 p = buf->b_term->tl_tty_in;
4775 break;
4776 default:
4777 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4778 return;
4779 }
4780 if (p != NULL)
4781 rettv->vval.v_string = vim_strsave(p);
4782}
4783
4784/*
4785 * "term_list()" function
4786 */
4787 void
4788f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4789{
4790 term_T *tp;
4791 list_T *l;
4792
4793 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4794 return;
4795
4796 l = rettv->vval.v_list;
4797 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4798 if (tp != NULL && tp->tl_buffer != NULL)
4799 if (list_append_number(l,
4800 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4801 return;
4802}
4803
4804/*
4805 * "term_scrape(buf, row)" function
4806 */
4807 void
4808f_term_scrape(typval_T *argvars, typval_T *rettv)
4809{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004810 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004811 VTermScreen *screen = NULL;
4812 VTermPos pos;
4813 list_T *l;
4814 term_T *term;
4815 char_u *p;
4816 sb_line_T *line;
4817
4818 if (rettv_list_alloc(rettv) == FAIL)
4819 return;
4820 if (buf == NULL)
4821 return;
4822 term = buf->b_term;
4823
4824 l = rettv->vval.v_list;
4825 pos.row = get_row_number(&argvars[1], term);
4826
4827 if (term->tl_vterm != NULL)
4828 {
4829 screen = vterm_obtain_screen(term->tl_vterm);
4830 p = NULL;
4831 line = NULL;
4832 }
4833 else
4834 {
4835 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4836
4837 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4838 return;
4839 p = ml_get_buf(buf, lnum + 1, FALSE);
4840 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4841 }
4842
4843 for (pos.col = 0; pos.col < term->tl_cols; )
4844 {
4845 dict_T *dcell;
4846 int width;
4847 VTermScreenCellAttrs attrs;
4848 VTermColor fg, bg;
4849 char_u rgb[8];
4850 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4851 int off = 0;
4852 int i;
4853
4854 if (screen == NULL)
4855 {
4856 cellattr_T *cellattr;
4857 int len;
4858
4859 /* vterm has finished, get the cell from scrollback */
4860 if (pos.col >= line->sb_cols)
4861 break;
4862 cellattr = line->sb_cells + pos.col;
4863 width = cellattr->width;
4864 attrs = cellattr->attrs;
4865 fg = cellattr->fg;
4866 bg = cellattr->bg;
4867 len = MB_PTR2LEN(p);
4868 mch_memmove(mbs, p, len);
4869 mbs[len] = NUL;
4870 p += len;
4871 }
4872 else
4873 {
4874 VTermScreenCell cell;
4875 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4876 break;
4877 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4878 {
4879 if (cell.chars[i] == 0)
4880 break;
4881 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4882 }
4883 mbs[off] = NUL;
4884 width = cell.width;
4885 attrs = cell.attrs;
4886 fg = cell.fg;
4887 bg = cell.bg;
4888 }
4889 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004890 if (dcell == NULL)
4891 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004892 list_append_dict(l, dcell);
4893
4894 dict_add_nr_str(dcell, "chars", 0, mbs);
4895
4896 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4897 fg.red, fg.green, fg.blue);
4898 dict_add_nr_str(dcell, "fg", 0, rgb);
4899 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4900 bg.red, bg.green, bg.blue);
4901 dict_add_nr_str(dcell, "bg", 0, rgb);
4902
4903 dict_add_nr_str(dcell, "attr",
4904 cell2attr(attrs, fg, bg), NULL);
4905 dict_add_nr_str(dcell, "width", width, NULL);
4906
4907 ++pos.col;
4908 if (width == 2)
4909 ++pos.col;
4910 }
4911}
4912
4913/*
4914 * "term_sendkeys(buf, keys)" function
4915 */
4916 void
4917f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4918{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004919 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004920 char_u *msg;
4921 term_T *term;
4922
4923 rettv->v_type = VAR_UNKNOWN;
4924 if (buf == NULL)
4925 return;
4926
4927 msg = get_tv_string_chk(&argvars[1]);
4928 if (msg == NULL)
4929 return;
4930 term = buf->b_term;
4931 if (term->tl_vterm == NULL)
4932 return;
4933
4934 while (*msg != NUL)
4935 {
4936 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004937 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004938 }
4939}
4940
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004941#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
4942/*
4943 * "term_getansicolors(buf)" function
4944 */
4945 void
4946f_term_getansicolors(typval_T *argvars, typval_T *rettv)
4947{
4948 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
4949 term_T *term;
4950 VTermState *state;
4951 VTermColor color;
4952 char_u hexbuf[10];
4953 int index;
4954 list_T *list;
4955
4956 if (rettv_list_alloc(rettv) == FAIL)
4957 return;
4958
4959 if (buf == NULL)
4960 return;
4961 term = buf->b_term;
4962 if (term->tl_vterm == NULL)
4963 return;
4964
4965 list = rettv->vval.v_list;
4966 state = vterm_obtain_state(term->tl_vterm);
4967 for (index = 0; index < 16; index++)
4968 {
4969 vterm_state_get_palette_color(state, index, &color);
4970 sprintf((char *)hexbuf, "#%02x%02x%02x",
4971 color.red, color.green, color.blue);
4972 if (list_append_string(list, hexbuf, 7) == FAIL)
4973 return;
4974 }
4975}
4976
4977/*
4978 * "term_setansicolors(buf, list)" function
4979 */
4980 void
4981f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
4982{
4983 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
4984 term_T *term;
4985
4986 if (buf == NULL)
4987 return;
4988 term = buf->b_term;
4989 if (term->tl_vterm == NULL)
4990 return;
4991
4992 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
4993 {
4994 EMSG(_(e_listreq));
4995 return;
4996 }
4997
4998 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
4999 EMSG(_(e_invarg));
5000}
5001#endif
5002
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005003/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005004 * "term_setrestore(buf, command)" function
5005 */
5006 void
5007f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5008{
5009#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005010 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005011 term_T *term;
5012 char_u *cmd;
5013
5014 if (buf == NULL)
5015 return;
5016 term = buf->b_term;
5017 vim_free(term->tl_command);
5018 cmd = get_tv_string_chk(&argvars[1]);
5019 if (cmd != NULL)
5020 term->tl_command = vim_strsave(cmd);
5021 else
5022 term->tl_command = NULL;
5023#endif
5024}
5025
5026/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005027 * "term_setkill(buf, how)" function
5028 */
5029 void
5030f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5031{
5032 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5033 term_T *term;
5034 char_u *how;
5035
5036 if (buf == NULL)
5037 return;
5038 term = buf->b_term;
5039 vim_free(term->tl_kill);
5040 how = get_tv_string_chk(&argvars[1]);
5041 if (how != NULL)
5042 term->tl_kill = vim_strsave(how);
5043 else
5044 term->tl_kill = NULL;
5045}
5046
5047/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005048 * "term_start(command, options)" function
5049 */
5050 void
5051f_term_start(typval_T *argvars, typval_T *rettv)
5052{
5053 jobopt_T opt;
5054 buf_T *buf;
5055
5056 init_job_options(&opt);
5057 if (argvars[1].v_type != VAR_UNKNOWN
5058 && get_job_options(&argvars[1], &opt,
5059 JO_TIMEOUT_ALL + JO_STOPONEXIT
5060 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5061 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5062 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5063 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005064 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005065 + JO2_NORESTORE + JO2_TERM_KILL
5066 + JO2_ANSI_COLORS) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005067 return;
5068
Bram Moolenaar13568252018-03-16 20:46:58 +01005069 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005070
5071 if (buf != NULL && buf->b_term != NULL)
5072 rettv->vval.v_number = buf->b_fnum;
5073}
5074
5075/*
5076 * "term_wait" function
5077 */
5078 void
5079f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5080{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005081 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005082
5083 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005084 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005085 if (buf->b_term->tl_job == NULL)
5086 {
5087 ch_log(NULL, "term_wait(): no job to wait for");
5088 return;
5089 }
5090 if (buf->b_term->tl_job->jv_channel == NULL)
5091 /* channel is closed, nothing to do */
5092 return;
5093
5094 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005095 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005096 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5097 {
5098 /* The job is dead, keep reading channel I/O until the channel is
5099 * closed. buf->b_term may become NULL if the terminal was closed while
5100 * waiting. */
5101 ch_log(NULL, "term_wait(): waiting for channel to close");
5102 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5103 {
5104 mch_check_messages();
5105 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01005106 if (!buf_valid(buf))
5107 /* If the terminal is closed when the channel is closed the
5108 * buffer disappears. */
5109 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005110 ui_delay(10L, FALSE);
5111 }
5112 mch_check_messages();
5113 parse_queued_messages();
5114 }
5115 else
5116 {
5117 long wait = 10L;
5118
5119 mch_check_messages();
5120 parse_queued_messages();
5121
5122 /* Wait for some time for any channel I/O. */
5123 if (argvars[1].v_type != VAR_UNKNOWN)
5124 wait = get_tv_number(&argvars[1]);
5125 ui_delay(wait, TRUE);
5126 mch_check_messages();
5127
5128 /* Flushing messages on channels is hopefully sufficient.
5129 * TODO: is there a better way? */
5130 parse_queued_messages();
5131 }
5132}
5133
5134/*
5135 * Called when a channel has sent all the lines to a terminal.
5136 * Send a CTRL-D to mark the end of the text.
5137 */
5138 void
5139term_send_eof(channel_T *ch)
5140{
5141 term_T *term;
5142
5143 for (term = first_term; term != NULL; term = term->tl_next)
5144 if (term->tl_job == ch->ch_job)
5145 {
5146 if (term->tl_eof_chars != NULL)
5147 {
5148 channel_send(ch, PART_IN, term->tl_eof_chars,
5149 (int)STRLEN(term->tl_eof_chars), NULL);
5150 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5151 }
5152# ifdef WIN3264
5153 else
5154 /* Default: CTRL-D */
5155 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5156# endif
5157 }
5158}
5159
5160# if defined(WIN3264) || defined(PROTO)
5161
5162/**************************************
5163 * 2. MS-Windows implementation.
5164 */
5165
5166# ifndef PROTO
5167
5168#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
5169#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01005170#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005171
5172void* (*winpty_config_new)(UINT64, void*);
5173void* (*winpty_open)(void*, void*);
5174void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
5175BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
5176void (*winpty_config_set_mouse_mode)(void*, int);
5177void (*winpty_config_set_initial_size)(void*, int, int);
5178LPCWSTR (*winpty_conin_name)(void*);
5179LPCWSTR (*winpty_conout_name)(void*);
5180LPCWSTR (*winpty_conerr_name)(void*);
5181void (*winpty_free)(void*);
5182void (*winpty_config_free)(void*);
5183void (*winpty_spawn_config_free)(void*);
5184void (*winpty_error_free)(void*);
5185LPCWSTR (*winpty_error_msg)(void*);
5186BOOL (*winpty_set_size)(void*, int, int, void*);
5187HANDLE (*winpty_agent_process)(void*);
5188
5189#define WINPTY_DLL "winpty.dll"
5190
5191static HINSTANCE hWinPtyDLL = NULL;
5192# endif
5193
5194 static int
5195dyn_winpty_init(int verbose)
5196{
5197 int i;
5198 static struct
5199 {
5200 char *name;
5201 FARPROC *ptr;
5202 } winpty_entry[] =
5203 {
5204 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
5205 {"winpty_config_free", (FARPROC*)&winpty_config_free},
5206 {"winpty_config_new", (FARPROC*)&winpty_config_new},
5207 {"winpty_config_set_mouse_mode",
5208 (FARPROC*)&winpty_config_set_mouse_mode},
5209 {"winpty_config_set_initial_size",
5210 (FARPROC*)&winpty_config_set_initial_size},
5211 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
5212 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
5213 {"winpty_error_free", (FARPROC*)&winpty_error_free},
5214 {"winpty_free", (FARPROC*)&winpty_free},
5215 {"winpty_open", (FARPROC*)&winpty_open},
5216 {"winpty_spawn", (FARPROC*)&winpty_spawn},
5217 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
5218 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
5219 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
5220 {"winpty_set_size", (FARPROC*)&winpty_set_size},
5221 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
5222 {NULL, NULL}
5223 };
5224
5225 /* No need to initialize twice. */
5226 if (hWinPtyDLL)
5227 return OK;
5228 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
5229 * winpty.dll. */
5230 if (*p_winptydll != NUL)
5231 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
5232 if (!hWinPtyDLL)
5233 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
5234 if (!hWinPtyDLL)
5235 {
5236 if (verbose)
5237 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
5238 : (char_u *)WINPTY_DLL);
5239 return FAIL;
5240 }
5241 for (i = 0; winpty_entry[i].name != NULL
5242 && winpty_entry[i].ptr != NULL; ++i)
5243 {
5244 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
5245 winpty_entry[i].name)) == NULL)
5246 {
5247 if (verbose)
5248 EMSG2(_(e_loadfunc), winpty_entry[i].name);
5249 return FAIL;
5250 }
5251 }
5252
5253 return OK;
5254}
5255
5256/*
5257 * Create a new terminal of "rows" by "cols" cells.
5258 * Store a reference in "term".
5259 * Return OK or FAIL.
5260 */
5261 static int
5262term_and_job_init(
5263 term_T *term,
5264 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005265 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005266 jobopt_T *opt)
5267{
5268 WCHAR *cmd_wchar = NULL;
5269 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005270 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005271 channel_T *channel = NULL;
5272 job_T *job = NULL;
5273 DWORD error;
5274 HANDLE jo = NULL;
5275 HANDLE child_process_handle;
5276 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01005277 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005278 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005279 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005280 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005281
5282 if (dyn_winpty_init(TRUE) == FAIL)
5283 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005284 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5285 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005286
5287 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005288 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005289 cmd = argvar->vval.v_string;
5290 }
5291 else if (argvar->v_type == VAR_LIST)
5292 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005293 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005294 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005295 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005296 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005297 if (cmd == NULL || *cmd == NUL)
5298 {
5299 EMSG(_(e_invarg));
5300 goto failed;
5301 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005302
5303 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005304 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005305 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005306 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005307 if (opt->jo_cwd != NULL)
5308 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005309
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01005310 win32_build_env(opt->jo_env, &ga_env, TRUE);
5311 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005312
5313 job = job_alloc();
5314 if (job == NULL)
5315 goto failed;
5316
5317 channel = add_channel();
5318 if (channel == NULL)
5319 goto failed;
5320
5321 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
5322 if (term->tl_winpty_config == NULL)
5323 goto failed;
5324
5325 winpty_config_set_mouse_mode(term->tl_winpty_config,
5326 WINPTY_MOUSE_MODE_FORCE);
5327 winpty_config_set_initial_size(term->tl_winpty_config,
5328 term->tl_cols, term->tl_rows);
5329 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
5330 if (term->tl_winpty == NULL)
5331 goto failed;
5332
5333 spawn_config = winpty_spawn_config_new(
5334 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
5335 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
5336 NULL,
5337 cmd_wchar,
5338 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01005339 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005340 &winpty_err);
5341 if (spawn_config == NULL)
5342 goto failed;
5343
5344 channel = add_channel();
5345 if (channel == NULL)
5346 goto failed;
5347
5348 job = job_alloc();
5349 if (job == NULL)
5350 goto failed;
5351
5352 if (opt->jo_set & JO_IN_BUF)
5353 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
5354
5355 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
5356 &child_thread_handle, &error, &winpty_err))
5357 goto failed;
5358
5359 channel_set_pipes(channel,
5360 (sock_T)CreateFileW(
5361 winpty_conin_name(term->tl_winpty),
5362 GENERIC_WRITE, 0, NULL,
5363 OPEN_EXISTING, 0, NULL),
5364 (sock_T)CreateFileW(
5365 winpty_conout_name(term->tl_winpty),
5366 GENERIC_READ, 0, NULL,
5367 OPEN_EXISTING, 0, NULL),
5368 (sock_T)CreateFileW(
5369 winpty_conerr_name(term->tl_winpty),
5370 GENERIC_READ, 0, NULL,
5371 OPEN_EXISTING, 0, NULL));
5372
5373 /* Write lines with CR instead of NL. */
5374 channel->ch_write_text_mode = TRUE;
5375
5376 jo = CreateJobObject(NULL, NULL);
5377 if (jo == NULL)
5378 goto failed;
5379
5380 if (!AssignProcessToJobObject(jo, child_process_handle))
5381 {
5382 /* Failed, switch the way to terminate process with TerminateProcess. */
5383 CloseHandle(jo);
5384 jo = NULL;
5385 }
5386
5387 winpty_spawn_config_free(spawn_config);
5388 vim_free(cmd_wchar);
5389 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005390 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005391
5392 create_vterm(term, term->tl_rows, term->tl_cols);
5393
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005394#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5395 if (opt->jo_set2 & JO2_ANSI_COLORS)
5396 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5397 else
5398 init_vterm_ansi_colors(term->tl_vterm);
5399#endif
5400
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005401 channel_set_job(channel, job, opt);
5402 job_set_options(job, opt);
5403
5404 job->jv_channel = channel;
5405 job->jv_proc_info.hProcess = child_process_handle;
5406 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
5407 job->jv_job_object = jo;
5408 job->jv_status = JOB_STARTED;
5409 job->jv_tty_in = utf16_to_enc(
5410 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
5411 job->jv_tty_out = utf16_to_enc(
5412 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
5413 ++job->jv_refcount;
5414 term->tl_job = job;
5415
5416 return OK;
5417
5418failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01005419 ga_clear(&ga_cmd);
5420 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005421 vim_free(cmd_wchar);
5422 vim_free(cwd_wchar);
5423 if (spawn_config != NULL)
5424 winpty_spawn_config_free(spawn_config);
5425 if (channel != NULL)
5426 channel_clear(channel);
5427 if (job != NULL)
5428 {
5429 job->jv_channel = NULL;
5430 job_cleanup(job);
5431 }
5432 term->tl_job = NULL;
5433 if (jo != NULL)
5434 CloseHandle(jo);
5435 if (term->tl_winpty != NULL)
5436 winpty_free(term->tl_winpty);
5437 term->tl_winpty = NULL;
5438 if (term->tl_winpty_config != NULL)
5439 winpty_config_free(term->tl_winpty_config);
5440 term->tl_winpty_config = NULL;
5441 if (winpty_err != NULL)
5442 {
5443 char_u *msg = utf16_to_enc(
5444 (short_u *)winpty_error_msg(winpty_err), NULL);
5445
5446 EMSG(msg);
5447 winpty_error_free(winpty_err);
5448 }
5449 return FAIL;
5450}
5451
5452 static int
5453create_pty_only(term_T *term, jobopt_T *options)
5454{
5455 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
5456 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
5457 char in_name[80], out_name[80];
5458 channel_T *channel = NULL;
5459
5460 create_vterm(term, term->tl_rows, term->tl_cols);
5461
5462 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
5463 GetCurrentProcessId(),
5464 curbuf->b_fnum);
5465 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
5466 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5467 PIPE_UNLIMITED_INSTANCES,
5468 0, 0, NMPWAIT_NOWAIT, NULL);
5469 if (hPipeIn == INVALID_HANDLE_VALUE)
5470 goto failed;
5471
5472 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
5473 GetCurrentProcessId(),
5474 curbuf->b_fnum);
5475 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
5476 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
5477 PIPE_UNLIMITED_INSTANCES,
5478 0, 0, 0, NULL);
5479 if (hPipeOut == INVALID_HANDLE_VALUE)
5480 goto failed;
5481
5482 ConnectNamedPipe(hPipeIn, NULL);
5483 ConnectNamedPipe(hPipeOut, NULL);
5484
5485 term->tl_job = job_alloc();
5486 if (term->tl_job == NULL)
5487 goto failed;
5488 ++term->tl_job->jv_refcount;
5489
5490 /* behave like the job is already finished */
5491 term->tl_job->jv_status = JOB_FINISHED;
5492
5493 channel = add_channel();
5494 if (channel == NULL)
5495 goto failed;
5496 term->tl_job->jv_channel = channel;
5497 channel->ch_keep_open = TRUE;
5498 channel->ch_named_pipe = TRUE;
5499
5500 channel_set_pipes(channel,
5501 (sock_T)hPipeIn,
5502 (sock_T)hPipeOut,
5503 (sock_T)hPipeOut);
5504 channel_set_job(channel, term->tl_job, options);
5505 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
5506 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
5507
5508 return OK;
5509
5510failed:
5511 if (hPipeIn != NULL)
5512 CloseHandle(hPipeIn);
5513 if (hPipeOut != NULL)
5514 CloseHandle(hPipeOut);
5515 return FAIL;
5516}
5517
5518/*
5519 * Free the terminal emulator part of "term".
5520 */
5521 static void
5522term_free_vterm(term_T *term)
5523{
5524 if (term->tl_winpty != NULL)
5525 winpty_free(term->tl_winpty);
5526 term->tl_winpty = NULL;
5527 if (term->tl_winpty_config != NULL)
5528 winpty_config_free(term->tl_winpty_config);
5529 term->tl_winpty_config = NULL;
5530 if (term->tl_vterm != NULL)
5531 vterm_free(term->tl_vterm);
5532 term->tl_vterm = NULL;
5533}
5534
5535/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005536 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005537 */
5538 static void
5539term_report_winsize(term_T *term, int rows, int cols)
5540{
5541 if (term->tl_winpty)
5542 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5543}
5544
5545 int
5546terminal_enabled(void)
5547{
5548 return dyn_winpty_init(FALSE) == OK;
5549}
5550
5551# else
5552
5553/**************************************
5554 * 3. Unix-like implementation.
5555 */
5556
5557/*
5558 * Create a new terminal of "rows" by "cols" cells.
5559 * Start job for "cmd".
5560 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005561 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005562 * Return OK or FAIL.
5563 */
5564 static int
5565term_and_job_init(
5566 term_T *term,
5567 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005568 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005569 jobopt_T *opt)
5570{
5571 create_vterm(term, term->tl_rows, term->tl_cols);
5572
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005573#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
5574 if (opt->jo_set2 & JO2_ANSI_COLORS)
5575 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
5576 else
5577 init_vterm_ansi_colors(term->tl_vterm);
5578#endif
5579
Bram Moolenaar13568252018-03-16 20:46:58 +01005580 /* This may change a string in "argvar". */
5581 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005582 if (term->tl_job != NULL)
5583 ++term->tl_job->jv_refcount;
5584
5585 return term->tl_job != NULL
5586 && term->tl_job->jv_channel != NULL
5587 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5588}
5589
5590 static int
5591create_pty_only(term_T *term, jobopt_T *opt)
5592{
5593 create_vterm(term, term->tl_rows, term->tl_cols);
5594
5595 term->tl_job = job_alloc();
5596 if (term->tl_job == NULL)
5597 return FAIL;
5598 ++term->tl_job->jv_refcount;
5599
5600 /* behave like the job is already finished */
5601 term->tl_job->jv_status = JOB_FINISHED;
5602
5603 return mch_create_pty_channel(term->tl_job, opt);
5604}
5605
5606/*
5607 * Free the terminal emulator part of "term".
5608 */
5609 static void
5610term_free_vterm(term_T *term)
5611{
5612 if (term->tl_vterm != NULL)
5613 vterm_free(term->tl_vterm);
5614 term->tl_vterm = NULL;
5615}
5616
5617/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005618 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005619 */
5620 static void
5621term_report_winsize(term_T *term, int rows, int cols)
5622{
5623 /* Use an ioctl() to report the new window size to the job. */
5624 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5625 {
5626 int fd = -1;
5627 int part;
5628
5629 for (part = PART_OUT; part < PART_COUNT; ++part)
5630 {
5631 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5632 if (isatty(fd))
5633 break;
5634 }
5635 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5636 mch_signal_job(term->tl_job, (char_u *)"winch");
5637 }
5638}
5639
5640# endif
5641
5642#endif /* FEAT_TERMINAL */