blob: 18da71025640c6e336d9f0f0f82cacef5ec26179 [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 Moolenaar13568252018-03-16 20:46:58 +010041 * - Win32: In the GUI use a terminal emulator for :!cmd.
42 * - Add a way to set the 16 ANSI colors, to be used for 'termguicolors' and in
43 * the GUI.
44 * - Some way for the job running in the terminal to send a :drop command back
45 * to the Vim running the terminal. Should be usable by a simple shell or
46 * python script.
Bram Moolenaarb852c3e2018-03-11 16:55:36 +010047 * - implement term_setsize()
48 * - Copy text in the vterm to the Vim buffer once in a while, so that
49 * completion works.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +010050 * - Adding WinBar to terminal window doesn't display, text isn't shifted down.
Bram Moolenaar46359e12017-11-29 22:33:38 +010051 * a job that uses 16 colors while Vim is using > 256.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020052 * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito
53 * Higashi, 2017 Sep 19)
Bram Moolenaar3a497e12017-09-30 20:40:27 +020054 * - after resizing windows overlap. (Boris Staletic, #2164)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020055 * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file()
56 * is disabled.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020057 * - cursor blinks in terminal on widows with a timer. (xtal8, #2142)
Bram Moolenaarba6febd2017-10-30 21:56:23 +010058 * - Termdebug does not work when Vim build with mzscheme. gdb hangs.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020059 * - MS-Windows GUI: WinBar has tearoff item
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020060 * - MS-Windows GUI: still need to type a key after shell exits? #1924
Bram Moolenaar51b0f372017-11-18 18:52:04 +010061 * - After executing a shell command the status line isn't redraw.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020062 * - add test for giving error for invalid 'termsize' value.
63 * - support minimal size when 'termsize' is "rows*cols".
64 * - support minimal size when 'termsize' is empty?
65 * - GUI: when using tabs, focus in terminal, click on tab does not work.
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020066 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020067 * - For the GUI fill termios with default values, perhaps like pangoterm:
68 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020069 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
70 * conversions.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020071 * - add an optional limit for the scrollback size. When reaching it remove
72 * 10% at the start.
73 */
74
75#include "vim.h"
76
77#if defined(FEAT_TERMINAL) || defined(PROTO)
78
79#ifndef MIN
80# define MIN(x,y) ((x) < (y) ? (x) : (y))
81#endif
82#ifndef MAX
83# define MAX(x,y) ((x) > (y) ? (x) : (y))
84#endif
85
86#include "libvterm/include/vterm.h"
87
88/* This is VTermScreenCell without the characters, thus much smaller. */
89typedef struct {
90 VTermScreenCellAttrs attrs;
91 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010092 VTermColor fg;
93 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020094} cellattr_T;
95
96typedef struct sb_line_S {
97 int sb_cols; /* can differ per line */
98 cellattr_T *sb_cells; /* allocated */
99 cellattr_T sb_fill_attr; /* for short line */
100} sb_line_T;
101
102/* typedef term_T in structs.h */
103struct terminal_S {
104 term_T *tl_next;
105
106 VTerm *tl_vterm;
107 job_T *tl_job;
108 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +0100109#if defined(FEAT_GUI)
110 int tl_system; /* when non-zero used for :!cmd output */
111 int tl_toprow; /* row with first line of system terminal */
112#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200113
114 /* Set when setting the size of a vterm, reset after redrawing. */
115 int tl_vterm_size_changed;
116
117 /* used when tl_job is NULL and only a pty was created */
118 int tl_tty_fd;
119 char_u *tl_tty_in;
120 char_u *tl_tty_out;
121
122 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
123 int tl_channel_closed;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100124 int tl_finish;
125#define TL_FINISH_UNSET NUL
126#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
127#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
128#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200129 char_u *tl_opencmd;
130 char_u *tl_eof_chars;
131
132#ifdef WIN3264
133 void *tl_winpty_config;
134 void *tl_winpty;
135#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100136#if defined(FEAT_SESSION)
137 char_u *tl_command;
138#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100139 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200140
141 /* last known vterm size */
142 int tl_rows;
143 int tl_cols;
144 /* vterm size does not follow window size */
145 int tl_rows_fixed;
146 int tl_cols_fixed;
147
148 char_u *tl_title; /* NULL or allocated */
149 char_u *tl_status_text; /* NULL or allocated */
150
151 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200152 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200153 int tl_dirty_row_end; /* row below last one to update */
154
155 garray_T tl_scrollback;
156 int tl_scrollback_scrolled;
157 cellattr_T tl_default_color;
158
Bram Moolenaard96ff162018-02-18 22:13:29 +0100159 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
160 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
161
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200162 VTermPos tl_cursor_pos;
163 int tl_cursor_visible;
164 int tl_cursor_blink;
165 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
166 char_u *tl_cursor_color; /* NULL or allocated */
167
168 int tl_using_altscreen;
169};
170
171#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
172#define TMODE_LOOP 2 /* CTRL-W N used */
173
174/*
175 * List of all active terminals.
176 */
177static term_T *first_term = NULL;
178
179/* Terminal active in terminal_loop(). */
180static term_T *in_terminal_loop = NULL;
181
182#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
183#define KEY_BUF_LEN 200
184
185/*
186 * Functions with separate implementation for MS-Windows and Unix-like systems.
187 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100188static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200189static int create_pty_only(term_T *term, jobopt_T *opt);
190static void term_report_winsize(term_T *term, int rows, int cols);
191static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100192#ifdef FEAT_GUI
193static void update_system_term(term_T *term);
194#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200195
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100196/* The character that we know (or assume) that the terminal expects for the
197 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200198static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200199
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100200/* "Terminal" highlight group colors. */
201static int term_default_cterm_fg = -1;
202static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200203
Bram Moolenaard317b382018-02-08 22:33:31 +0100204/* Store the last set and the desired cursor properties, so that we only update
205 * them when needed. Doing it unnecessary may result in flicker. */
206static char_u *last_set_cursor_color = (char_u *)"";
207static char_u *desired_cursor_color = (char_u *)"";
208static int last_set_cursor_shape = -1;
209static int desired_cursor_shape = -1;
210static int last_set_cursor_blink = -1;
211static int desired_cursor_blink = -1;
212
213
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200214/**************************************
215 * 1. Generic code for all systems.
216 */
217
218/*
219 * Determine the terminal size from 'termsize' and the current window.
220 * Assumes term->tl_rows and term->tl_cols are zero.
221 */
222 static void
223set_term_and_win_size(term_T *term)
224{
Bram Moolenaar13568252018-03-16 20:46:58 +0100225#ifdef FEAT_GUI
226 if (term->tl_system)
227 {
228 /* Use the whole screen for the system command. However, it will start
229 * at the command line and scroll up as needed, using tl_toprow. */
230 term->tl_rows = Rows;
231 term->tl_cols = Columns;
232 }
233 else
234#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200235 if (*curwin->w_p_tms != NUL)
236 {
237 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
238
239 term->tl_rows = atoi((char *)curwin->w_p_tms);
240 term->tl_cols = atoi((char *)p);
241 }
242 if (term->tl_rows == 0)
243 term->tl_rows = curwin->w_height;
244 else
245 {
246 win_setheight_win(term->tl_rows, curwin);
247 term->tl_rows_fixed = TRUE;
248 }
249 if (term->tl_cols == 0)
250 term->tl_cols = curwin->w_width;
251 else
252 {
253 win_setwidth_win(term->tl_cols, curwin);
254 term->tl_cols_fixed = TRUE;
255 }
256}
257
258/*
259 * Initialize job options for a terminal job.
260 * Caller may overrule some of them.
261 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100262 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200263init_job_options(jobopt_T *opt)
264{
265 clear_job_options(opt);
266
267 opt->jo_mode = MODE_RAW;
268 opt->jo_out_mode = MODE_RAW;
269 opt->jo_err_mode = MODE_RAW;
270 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
271}
272
273/*
274 * Set job options mandatory for a terminal job.
275 */
276 static void
277setup_job_options(jobopt_T *opt, int rows, int cols)
278{
279 if (!(opt->jo_set & JO_OUT_IO))
280 {
281 /* Connect stdout to the terminal. */
282 opt->jo_io[PART_OUT] = JIO_BUFFER;
283 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
284 opt->jo_modifiable[PART_OUT] = 0;
285 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
286 }
287
288 if (!(opt->jo_set & JO_ERR_IO))
289 {
290 /* Connect stderr to the terminal. */
291 opt->jo_io[PART_ERR] = JIO_BUFFER;
292 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
293 opt->jo_modifiable[PART_ERR] = 0;
294 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
295 }
296
297 opt->jo_pty = TRUE;
298 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
299 opt->jo_term_rows = rows;
300 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
301 opt->jo_term_cols = cols;
302}
303
304/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100305 * Close a terminal buffer (and its window). Used when creating the terminal
306 * fails.
307 */
308 static void
309term_close_buffer(buf_T *buf, buf_T *old_curbuf)
310{
311 free_terminal(buf);
312 if (old_curbuf != NULL)
313 {
314 --curbuf->b_nwindows;
315 curbuf = old_curbuf;
316 curwin->w_buffer = curbuf;
317 ++curbuf->b_nwindows;
318 }
319
320 /* Wiping out the buffer will also close the window and call
321 * free_terminal(). */
322 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
323}
324
325/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200326 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100327 * Use either "argvar" or "argv", the other must be NULL.
328 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
329 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200330 * Returns NULL when failed.
331 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100332 buf_T *
333term_start(
334 typval_T *argvar,
335 char **argv,
336 jobopt_T *opt,
337 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200338{
339 exarg_T split_ea;
340 win_T *old_curwin = curwin;
341 term_T *term;
342 buf_T *old_curbuf = NULL;
343 int res;
344 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100345 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200346
347 if (check_restricted() || check_secure())
348 return NULL;
349
350 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
351 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
352 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
353 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
354 {
355 EMSG(_(e_invarg));
356 return NULL;
357 }
358
359 term = (term_T *)alloc_clear(sizeof(term_T));
360 if (term == NULL)
361 return NULL;
362 term->tl_dirty_row_end = MAX_ROW;
363 term->tl_cursor_visible = TRUE;
364 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
365 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100366#ifdef FEAT_GUI
367 term->tl_system = (flags & TERM_START_SYSTEM);
368#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200369 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
370
371 vim_memset(&split_ea, 0, sizeof(split_ea));
372 if (opt->jo_curwin)
373 {
374 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100375 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200376 {
377 no_write_message();
378 vim_free(term);
379 return NULL;
380 }
381 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100382 ECMD_HIDE
383 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
384 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200385 {
386 vim_free(term);
387 return NULL;
388 }
389 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100390 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200391 {
392 buf_T *buf;
393
394 /* Create a new buffer without a window. Make it the current buffer for
395 * a moment to be able to do the initialisations. */
396 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
397 BLN_NEW | BLN_LISTED);
398 if (buf == NULL || ml_open(buf) == FAIL)
399 {
400 vim_free(term);
401 return NULL;
402 }
403 old_curbuf = curbuf;
404 --curbuf->b_nwindows;
405 curbuf = buf;
406 curwin->w_buffer = buf;
407 ++curbuf->b_nwindows;
408 }
409 else
410 {
411 /* Open a new window or tab. */
412 split_ea.cmdidx = CMD_new;
413 split_ea.cmd = (char_u *)"new";
414 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100415 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200416 {
417 split_ea.line2 = opt->jo_term_rows;
418 split_ea.addr_count = 1;
419 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100420 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200421 {
422 split_ea.line2 = opt->jo_term_cols;
423 split_ea.addr_count = 1;
424 }
425
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100426 if (vertical)
427 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200428 ex_splitview(&split_ea);
429 if (curwin == old_curwin)
430 {
431 /* split failed */
432 vim_free(term);
433 return NULL;
434 }
435 }
436 term->tl_buffer = curbuf;
437 curbuf->b_term = term;
438
439 if (!opt->jo_hidden)
440 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100441 /* Only one size was taken care of with :new, do the other one. With
442 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100443 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200444 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100445 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200446 win_setwidth(opt->jo_term_cols);
447 }
448
449 /* Link the new terminal in the list of active terminals. */
450 term->tl_next = first_term;
451 first_term = term;
452
453 if (opt->jo_term_name != NULL)
454 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100455 else if (argv != NULL)
456 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200457 else
458 {
459 int i;
460 size_t len;
461 char_u *cmd, *p;
462
463 if (argvar->v_type == VAR_STRING)
464 {
465 cmd = argvar->vval.v_string;
466 if (cmd == NULL)
467 cmd = (char_u *)"";
468 else if (STRCMP(cmd, "NONE") == 0)
469 cmd = (char_u *)"pty";
470 }
471 else if (argvar->v_type != VAR_LIST
472 || argvar->vval.v_list == NULL
473 || argvar->vval.v_list->lv_len < 1
474 || (cmd = get_tv_string_chk(
475 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
476 cmd = (char_u*)"";
477
478 len = STRLEN(cmd) + 10;
479 p = alloc((int)len);
480
481 for (i = 0; p != NULL; ++i)
482 {
483 /* Prepend a ! to the command name to avoid the buffer name equals
484 * the executable, otherwise ":w!" would overwrite it. */
485 if (i == 0)
486 vim_snprintf((char *)p, len, "!%s", cmd);
487 else
488 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
489 if (buflist_findname(p) == NULL)
490 {
491 vim_free(curbuf->b_ffname);
492 curbuf->b_ffname = p;
493 break;
494 }
495 }
496 }
497 curbuf->b_fname = curbuf->b_ffname;
498
499 if (opt->jo_term_opencmd != NULL)
500 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
501
502 if (opt->jo_eof_chars != NULL)
503 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
504
505 set_string_option_direct((char_u *)"buftype", -1,
506 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
507
508 /* Mark the buffer as not modifiable. It can only be made modifiable after
509 * the job finished. */
510 curbuf->b_p_ma = FALSE;
511
512 set_term_and_win_size(term);
513 setup_job_options(opt, term->tl_rows, term->tl_cols);
514
Bram Moolenaar13568252018-03-16 20:46:58 +0100515 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100516 return curbuf;
517
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100518#if defined(FEAT_SESSION)
519 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100520 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100521 {
522 term->tl_command = vim_strsave((char_u *)"NONE");
523 }
524 else if (argvar->v_type == VAR_STRING)
525 {
526 char_u *cmd = argvar->vval.v_string;
527
528 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
529 term->tl_command = vim_strsave(cmd);
530 }
531 else if (argvar->v_type == VAR_LIST
532 && argvar->vval.v_list != NULL
533 && argvar->vval.v_list->lv_len > 0)
534 {
535 garray_T ga;
536 listitem_T *item;
537
538 ga_init2(&ga, 1, 100);
539 for (item = argvar->vval.v_list->lv_first;
540 item != NULL; item = item->li_next)
541 {
542 char_u *s = get_tv_string_chk(&item->li_tv);
543 char_u *p;
544
545 if (s == NULL)
546 break;
547 p = vim_strsave_fnameescape(s, FALSE);
548 if (p == NULL)
549 break;
550 ga_concat(&ga, p);
551 vim_free(p);
552 ga_append(&ga, ' ');
553 }
554 if (item == NULL)
555 {
556 ga_append(&ga, NUL);
557 term->tl_command = ga.ga_data;
558 }
559 else
560 ga_clear(&ga);
561 }
562#endif
563
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100564 if (opt->jo_term_kill != NULL)
565 {
566 char_u *p = skiptowhite(opt->jo_term_kill);
567
568 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
569 }
570
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200571 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100572 if (argv == NULL
573 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200574 && argvar->vval.v_string != NULL
575 && STRCMP(argvar->vval.v_string, "NONE") == 0)
576 res = create_pty_only(term, opt);
577 else
Bram Moolenaar13568252018-03-16 20:46:58 +0100578 res = term_and_job_init(term, argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200579
580 newbuf = curbuf;
581 if (res == OK)
582 {
583 /* Get and remember the size we ended up with. Update the pty. */
584 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
585 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100586#ifdef FEAT_GUI
587 if (term->tl_system)
588 {
589 /* display first line below typed command */
590 term->tl_toprow = msg_row + 1;
591 term->tl_dirty_row_end = 0;
592 }
593#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200594
595 /* Make sure we don't get stuck on sending keys to the job, it leads to
596 * a deadlock if the job is waiting for Vim to read. */
597 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
598
Bram Moolenaar13568252018-03-16 20:46:58 +0100599 if (old_curbuf == NULL)
Bram Moolenaarab5e7c32018-02-13 14:07:18 +0100600 {
601 ++curbuf->b_locked;
602 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
603 --curbuf->b_locked;
604 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100605 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200606 {
607 --curbuf->b_nwindows;
608 curbuf = old_curbuf;
609 curwin->w_buffer = curbuf;
610 ++curbuf->b_nwindows;
611 }
612 }
613 else
614 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100615 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200616 return NULL;
617 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100618
Bram Moolenaar13568252018-03-16 20:46:58 +0100619 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200620 return newbuf;
621}
622
623/*
624 * ":terminal": open a terminal window and execute a job in it.
625 */
626 void
627ex_terminal(exarg_T *eap)
628{
629 typval_T argvar[2];
630 jobopt_T opt;
631 char_u *cmd;
632 char_u *tofree = NULL;
633
634 init_job_options(&opt);
635
636 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100637 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200638 {
639 char_u *p, *ep;
640
641 cmd += 2;
642 p = skiptowhite(cmd);
643 ep = vim_strchr(cmd, '=');
644 if (ep != NULL && ep < p)
645 p = ep;
646
647 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
648 opt.jo_term_finish = 'c';
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100649 else if ((int)(p - cmd) == 7 && STRNICMP(cmd, "noclose", 7) == 0)
650 opt.jo_term_finish = 'n';
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200651 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
652 opt.jo_term_finish = 'o';
653 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
654 opt.jo_curwin = 1;
655 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
656 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100657 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
658 opt.jo_term_norestore = 1;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100659 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "kill", 4) == 0
660 && ep != NULL)
661 {
662 opt.jo_set2 |= JO2_TERM_KILL;
663 opt.jo_term_kill = ep + 1;
664 p = skiptowhite(cmd);
665 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200666 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
667 && ep != NULL && isdigit(ep[1]))
668 {
669 opt.jo_set2 |= JO2_TERM_ROWS;
670 opt.jo_term_rows = atoi((char *)ep + 1);
671 p = skiptowhite(cmd);
672 }
673 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
674 && ep != NULL && isdigit(ep[1]))
675 {
676 opt.jo_set2 |= JO2_TERM_COLS;
677 opt.jo_term_cols = atoi((char *)ep + 1);
678 p = skiptowhite(cmd);
679 }
680 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
681 && ep != NULL)
682 {
683 char_u *buf = NULL;
684 char_u *keys;
685
686 p = skiptowhite(cmd);
687 *p = NUL;
688 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
689 opt.jo_set2 |= JO2_EOF_CHARS;
690 opt.jo_eof_chars = vim_strsave(keys);
691 vim_free(buf);
692 *p = ' ';
693 }
694 else
695 {
696 if (*p)
697 *p = NUL;
698 EMSG2(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100699 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200700 }
701 cmd = skipwhite(p);
702 }
703 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100704 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200705 /* Make a copy of 'shell', an autocommand may change the option. */
706 tofree = cmd = vim_strsave(p_sh);
707
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100708 /* default to close when the shell exits */
709 if (opt.jo_term_finish == NUL)
710 opt.jo_term_finish = 'c';
711 }
712
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200713 if (eap->addr_count > 0)
714 {
715 /* Write lines from current buffer to the job. */
716 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
717 opt.jo_io[PART_IN] = JIO_BUFFER;
718 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
719 opt.jo_in_top = eap->line1;
720 opt.jo_in_bot = eap->line2;
721 }
722
723 argvar[0].v_type = VAR_STRING;
724 argvar[0].vval.v_string = cmd;
725 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaar13568252018-03-16 20:46:58 +0100726 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200727 vim_free(tofree);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100728
729theend:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200730 vim_free(opt.jo_eof_chars);
731}
732
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100733#if defined(FEAT_SESSION) || defined(PROTO)
734/*
735 * Write a :terminal command to the session file to restore the terminal in
736 * window "wp".
737 * Return FAIL if writing fails.
738 */
739 int
740term_write_session(FILE *fd, win_T *wp)
741{
742 term_T *term = wp->w_buffer->b_term;
743
744 /* Create the terminal and run the command. This is not without
745 * risk, but let's assume the user only creates a session when this
746 * will be OK. */
747 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
748 term->tl_cols, term->tl_rows) < 0)
749 return FAIL;
750 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
751 return FAIL;
752
753 return put_eol(fd);
754}
755
756/*
757 * Return TRUE if "buf" has a terminal that should be restored.
758 */
759 int
760term_should_restore(buf_T *buf)
761{
762 term_T *term = buf->b_term;
763
764 return term != NULL && (term->tl_command == NULL
765 || STRCMP(term->tl_command, "NONE") != 0);
766}
767#endif
768
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200769/*
770 * Free the scrollback buffer for "term".
771 */
772 static void
773free_scrollback(term_T *term)
774{
775 int i;
776
777 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
778 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
779 ga_clear(&term->tl_scrollback);
780}
781
782/*
783 * Free a terminal and everything it refers to.
784 * Kills the job if there is one.
785 * Called when wiping out a buffer.
786 */
787 void
788free_terminal(buf_T *buf)
789{
790 term_T *term = buf->b_term;
791 term_T *tp;
792
793 if (term == NULL)
794 return;
795 if (first_term == term)
796 first_term = term->tl_next;
797 else
798 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
799 if (tp->tl_next == term)
800 {
801 tp->tl_next = term->tl_next;
802 break;
803 }
804
805 if (term->tl_job != NULL)
806 {
807 if (term->tl_job->jv_status != JOB_ENDED
808 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100809 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200810 job_stop(term->tl_job, NULL, "kill");
811 job_unref(term->tl_job);
812 }
813
814 free_scrollback(term);
815
816 term_free_vterm(term);
817 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100818#ifdef FEAT_SESSION
819 vim_free(term->tl_command);
820#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100821 vim_free(term->tl_kill);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200822 vim_free(term->tl_status_text);
823 vim_free(term->tl_opencmd);
824 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100825 if (desired_cursor_color == term->tl_cursor_color)
826 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200827 vim_free(term->tl_cursor_color);
828 vim_free(term);
829 buf->b_term = NULL;
830 if (in_terminal_loop == term)
831 in_terminal_loop = NULL;
832}
833
834/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100835 * Get the part that is connected to the tty. Normally this is PART_IN, but
836 * when writing buffer lines to the job it can be another. This makes it
837 * possible to do "1,5term vim -".
838 */
839 static ch_part_T
840get_tty_part(term_T *term)
841{
842#ifdef UNIX
843 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
844 int i;
845
846 for (i = 0; i < 3; ++i)
847 {
848 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
849
850 if (isatty(fd))
851 return parts[i];
852 }
853#endif
854 return PART_IN;
855}
856
857/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200858 * Write job output "msg[len]" to the vterm.
859 */
860 static void
861term_write_job_output(term_T *term, char_u *msg, size_t len)
862{
863 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100864 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200865
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100866 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200867
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100868 /* flush vterm buffer when vterm responded to control sequence */
869 if (prevlen != vterm_output_get_buffer_current(vterm))
870 {
871 char buf[KEY_BUF_LEN];
872 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
873
874 if (curlen > 0)
875 channel_send(term->tl_job->jv_channel, get_tty_part(term),
876 (char_u *)buf, (int)curlen, NULL);
877 }
878
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200879 /* this invokes the damage callbacks */
880 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
881}
882
883 static void
884update_cursor(term_T *term, int redraw)
885{
886 if (term->tl_normal_mode)
887 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100888#ifdef FEAT_GUI
889 if (term->tl_system)
890 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
891 term->tl_cursor_pos.col);
892 else
893#endif
894 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200895 if (redraw)
896 {
897 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
898 cursor_on();
899 out_flush();
900#ifdef FEAT_GUI
901 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100902 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200903 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100904 gui_mch_flush();
905 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200906#endif
907 }
908}
909
910/*
911 * Invoked when "msg" output from a job was received. Write it to the terminal
912 * of "buffer".
913 */
914 void
915write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
916{
917 size_t len = STRLEN(msg);
918 term_T *term = buffer->b_term;
919
920 if (term->tl_vterm == NULL)
921 {
922 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
923 return;
924 }
925 ch_log(channel, "writing %d bytes to terminal", (int)len);
926 term_write_job_output(term, msg, len);
927
Bram Moolenaar13568252018-03-16 20:46:58 +0100928#ifdef FEAT_GUI
929 if (term->tl_system)
930 {
931 /* show system output, scrolling up the screen as needed */
932 update_system_term(term);
933 update_cursor(term, TRUE);
934 }
935 else
936#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200937 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
938 * contents, thus no screen update is needed. */
939 if (!term->tl_normal_mode)
940 {
941 /* TODO: only update once in a while. */
942 ch_log(term->tl_job->jv_channel, "updating screen");
943 if (buffer == curbuf)
944 {
945 update_screen(0);
946 update_cursor(term, TRUE);
947 }
948 else
949 redraw_after_callback(TRUE);
950 }
951}
952
953/*
954 * Send a mouse position and click to the vterm
955 */
956 static int
957term_send_mouse(VTerm *vterm, int button, int pressed)
958{
959 VTermModifier mod = VTERM_MOD_NONE;
960
961 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200962 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100963 if (button != 0)
964 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200965 return TRUE;
966}
967
Bram Moolenaarc48369c2018-03-11 19:30:45 +0100968static int enter_mouse_col = -1;
969static int enter_mouse_row = -1;
970
971/*
972 * Handle a mouse click, drag or release.
973 * Return TRUE when a mouse event is sent to the terminal.
974 */
975 static int
976term_mouse_click(VTerm *vterm, int key)
977{
978#if defined(FEAT_CLIPBOARD)
979 /* For modeless selection mouse drag and release events are ignored, unless
980 * they are preceded with a mouse down event */
981 static int ignore_drag_release = TRUE;
982 VTermMouseState mouse_state;
983
984 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
985 if (mouse_state.flags == 0)
986 {
987 /* Terminal is not using the mouse, use modeless selection. */
988 switch (key)
989 {
990 case K_LEFTDRAG:
991 case K_LEFTRELEASE:
992 case K_RIGHTDRAG:
993 case K_RIGHTRELEASE:
994 /* Ignore drag and release events when the button-down wasn't
995 * seen before. */
996 if (ignore_drag_release)
997 {
998 int save_mouse_col, save_mouse_row;
999
1000 if (enter_mouse_col < 0)
1001 break;
1002
1003 /* mouse click in the window gave us focus, handle that
1004 * click now */
1005 save_mouse_col = mouse_col;
1006 save_mouse_row = mouse_row;
1007 mouse_col = enter_mouse_col;
1008 mouse_row = enter_mouse_row;
1009 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1010 mouse_col = save_mouse_col;
1011 mouse_row = save_mouse_row;
1012 }
1013 /* FALLTHROUGH */
1014 case K_LEFTMOUSE:
1015 case K_RIGHTMOUSE:
1016 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1017 ignore_drag_release = TRUE;
1018 else
1019 ignore_drag_release = FALSE;
1020 /* Should we call mouse_has() here? */
1021 if (clip_star.available)
1022 {
1023 int button, is_click, is_drag;
1024
1025 button = get_mouse_button(KEY2TERMCAP1(key),
1026 &is_click, &is_drag);
1027 if (mouse_model_popup() && button == MOUSE_LEFT
1028 && (mod_mask & MOD_MASK_SHIFT))
1029 {
1030 /* Translate shift-left to right button. */
1031 button = MOUSE_RIGHT;
1032 mod_mask &= ~MOD_MASK_SHIFT;
1033 }
1034 clip_modeless(button, is_click, is_drag);
1035 }
1036 break;
1037
1038 case K_MIDDLEMOUSE:
1039 if (clip_star.available)
1040 insert_reg('*', TRUE);
1041 break;
1042 }
1043 enter_mouse_col = -1;
1044 return FALSE;
1045 }
1046#endif
1047 enter_mouse_col = -1;
1048
1049 switch (key)
1050 {
1051 case K_LEFTMOUSE:
1052 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1053 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1054 case K_LEFTRELEASE:
1055 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1056 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1057 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1058 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1059 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1060 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1061 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1062 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1063 }
1064 return TRUE;
1065}
1066
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001067/*
1068 * Convert typed key "c" into bytes to send to the job.
1069 * Return the number of bytes in "buf".
1070 */
1071 static int
1072term_convert_key(term_T *term, int c, char *buf)
1073{
1074 VTerm *vterm = term->tl_vterm;
1075 VTermKey key = VTERM_KEY_NONE;
1076 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001077 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001078
1079 switch (c)
1080 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001081 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1082
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001083 /* don't use VTERM_KEY_BACKSPACE, it always
1084 * becomes 0x7f DEL */
1085 case K_BS: c = term_backspace_char; break;
1086
1087 case ESC: key = VTERM_KEY_ESCAPE; break;
1088 case K_DEL: key = VTERM_KEY_DEL; break;
1089 case K_DOWN: key = VTERM_KEY_DOWN; break;
1090 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1091 key = VTERM_KEY_DOWN; break;
1092 case K_END: key = VTERM_KEY_END; break;
1093 case K_S_END: mod = VTERM_MOD_SHIFT;
1094 key = VTERM_KEY_END; break;
1095 case K_C_END: mod = VTERM_MOD_CTRL;
1096 key = VTERM_KEY_END; break;
1097 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1098 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1099 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1100 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1101 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1102 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1103 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1104 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1105 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1106 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1107 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1108 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1109 case K_HOME: key = VTERM_KEY_HOME; break;
1110 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1111 key = VTERM_KEY_HOME; break;
1112 case K_C_HOME: mod = VTERM_MOD_CTRL;
1113 key = VTERM_KEY_HOME; break;
1114 case K_INS: key = VTERM_KEY_INS; break;
1115 case K_K0: key = VTERM_KEY_KP_0; break;
1116 case K_K1: key = VTERM_KEY_KP_1; break;
1117 case K_K2: key = VTERM_KEY_KP_2; break;
1118 case K_K3: key = VTERM_KEY_KP_3; break;
1119 case K_K4: key = VTERM_KEY_KP_4; break;
1120 case K_K5: key = VTERM_KEY_KP_5; break;
1121 case K_K6: key = VTERM_KEY_KP_6; break;
1122 case K_K7: key = VTERM_KEY_KP_7; break;
1123 case K_K8: key = VTERM_KEY_KP_8; break;
1124 case K_K9: key = VTERM_KEY_KP_9; break;
1125 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1126 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1127 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1128 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1129 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1130 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1131 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1132 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1133 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1134 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1135 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1136 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1137 case K_LEFT: key = VTERM_KEY_LEFT; break;
1138 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1139 key = VTERM_KEY_LEFT; break;
1140 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1141 key = VTERM_KEY_LEFT; break;
1142 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1143 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1144 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1145 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1146 key = VTERM_KEY_RIGHT; break;
1147 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1148 key = VTERM_KEY_RIGHT; break;
1149 case K_UP: key = VTERM_KEY_UP; break;
1150 case K_S_UP: mod = VTERM_MOD_SHIFT;
1151 key = VTERM_KEY_UP; break;
1152 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001153 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1154 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001155
Bram Moolenaara42ad572017-11-16 13:08:04 +01001156 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1157 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001158 case K_MOUSELEFT: /* TODO */ return 0;
1159 case K_MOUSERIGHT: /* TODO */ return 0;
1160
1161 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001162 case K_LEFTMOUSE_NM:
1163 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001164 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001165 case K_LEFTRELEASE_NM:
1166 case K_MOUSEMOVE:
1167 case K_MIDDLEMOUSE:
1168 case K_MIDDLEDRAG:
1169 case K_MIDDLERELEASE:
1170 case K_RIGHTMOUSE:
1171 case K_RIGHTDRAG:
1172 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1173 return 0;
1174 other = TRUE;
1175 break;
1176
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001177 case K_X1MOUSE: /* TODO */ return 0;
1178 case K_X1DRAG: /* TODO */ return 0;
1179 case K_X1RELEASE: /* TODO */ return 0;
1180 case K_X2MOUSE: /* TODO */ return 0;
1181 case K_X2DRAG: /* TODO */ return 0;
1182 case K_X2RELEASE: /* TODO */ return 0;
1183
1184 case K_IGNORE: return 0;
1185 case K_NOP: return 0;
1186 case K_UNDO: return 0;
1187 case K_HELP: return 0;
1188 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1189 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1190 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1191 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1192 case K_SELECT: return 0;
1193#ifdef FEAT_GUI
1194 case K_VER_SCROLLBAR: return 0;
1195 case K_HOR_SCROLLBAR: return 0;
1196#endif
1197#ifdef FEAT_GUI_TABLINE
1198 case K_TABLINE: return 0;
1199 case K_TABMENU: return 0;
1200#endif
1201#ifdef FEAT_NETBEANS_INTG
1202 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1203#endif
1204#ifdef FEAT_DND
1205 case K_DROP: return 0;
1206#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001207 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001208 case K_PS: vterm_keyboard_start_paste(vterm);
1209 other = TRUE;
1210 break;
1211 case K_PE: vterm_keyboard_end_paste(vterm);
1212 other = TRUE;
1213 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001214 }
1215
1216 /*
1217 * Convert special keys to vterm keys:
1218 * - Write keys to vterm: vterm_keyboard_key()
1219 * - Write output to channel.
1220 * TODO: use mod_mask
1221 */
1222 if (key != VTERM_KEY_NONE)
1223 /* Special key, let vterm convert it. */
1224 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001225 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001226 /* Normal character, let vterm convert it. */
1227 vterm_keyboard_unichar(vterm, c, mod);
1228
1229 /* Read back the converted escape sequence. */
1230 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1231}
1232
1233/*
1234 * Return TRUE if the job for "term" is still running.
1235 */
1236 int
1237term_job_running(term_T *term)
1238{
1239 /* Also consider the job finished when the channel is closed, to avoid a
1240 * race condition when updating the title. */
1241 return term != NULL
1242 && term->tl_job != NULL
1243 && channel_is_open(term->tl_job->jv_channel)
1244 && (term->tl_job->jv_status == JOB_STARTED
1245 || term->tl_job->jv_channel->ch_keep_open);
1246}
1247
1248/*
1249 * Return TRUE if "term" has an active channel and used ":term NONE".
1250 */
1251 int
1252term_none_open(term_T *term)
1253{
1254 /* Also consider the job finished when the channel is closed, to avoid a
1255 * race condition when updating the title. */
1256 return term != NULL
1257 && term->tl_job != NULL
1258 && channel_is_open(term->tl_job->jv_channel)
1259 && term->tl_job->jv_channel->ch_keep_open;
1260}
1261
1262/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001263 * Used when exiting: kill the job in "buf" if so desired.
1264 * Return OK when the job finished.
1265 * Return FAIL when the job is still running.
1266 */
1267 int
1268term_try_stop_job(buf_T *buf)
1269{
1270 int count;
1271 char *how = (char *)buf->b_term->tl_kill;
1272
1273#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1274 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1275 {
1276 char_u buff[DIALOG_MSG_SIZE];
1277 int ret;
1278
1279 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1280 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1281 if (ret == VIM_YES)
1282 how = "kill";
1283 else if (ret == VIM_CANCEL)
1284 return FAIL;
1285 }
1286#endif
1287 if (how == NULL || *how == NUL)
1288 return FAIL;
1289
1290 job_stop(buf->b_term->tl_job, NULL, how);
1291
1292 /* wait for up to a second for the job to die */
1293 for (count = 0; count < 100; ++count)
1294 {
1295 /* buffer, terminal and job may be cleaned up while waiting */
1296 if (!buf_valid(buf)
1297 || buf->b_term == NULL
1298 || buf->b_term->tl_job == NULL)
1299 return OK;
1300
1301 /* call job_status() to update jv_status */
1302 job_status(buf->b_term->tl_job);
1303 if (buf->b_term->tl_job->jv_status >= JOB_ENDED)
1304 return OK;
1305 ui_delay(10L, FALSE);
1306 mch_check_messages();
1307 parse_queued_messages();
1308 }
1309 return FAIL;
1310}
1311
1312/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001313 * Add the last line of the scrollback buffer to the buffer in the window.
1314 */
1315 static void
1316add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1317{
1318 buf_T *buf = term->tl_buffer;
1319 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1320 linenr_T lnum = buf->b_ml.ml_line_count;
1321
1322#ifdef WIN3264
1323 if (!enc_utf8 && enc_codepage > 0)
1324 {
1325 WCHAR *ret = NULL;
1326 int length = 0;
1327
1328 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1329 &ret, &length);
1330 if (ret != NULL)
1331 {
1332 WideCharToMultiByte_alloc(enc_codepage, 0,
1333 ret, length, (char **)&text, &len, 0, 0);
1334 vim_free(ret);
1335 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1336 vim_free(text);
1337 }
1338 }
1339 else
1340#endif
1341 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1342 if (empty)
1343 {
1344 /* Delete the empty line that was in the empty buffer. */
1345 curbuf = buf;
1346 ml_delete(1, FALSE);
1347 curbuf = curwin->w_buffer;
1348 }
1349}
1350
1351 static void
1352cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1353{
1354 attr->width = cell->width;
1355 attr->attrs = cell->attrs;
1356 attr->fg = cell->fg;
1357 attr->bg = cell->bg;
1358}
1359
1360 static int
1361equal_celattr(cellattr_T *a, cellattr_T *b)
1362{
1363 /* Comparing the colors should be sufficient. */
1364 return a->fg.red == b->fg.red
1365 && a->fg.green == b->fg.green
1366 && a->fg.blue == b->fg.blue
1367 && a->bg.red == b->bg.red
1368 && a->bg.green == b->bg.green
1369 && a->bg.blue == b->bg.blue;
1370}
1371
Bram Moolenaard96ff162018-02-18 22:13:29 +01001372/*
1373 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1374 * line at this position. Otherwise at the end.
1375 */
1376 static int
1377add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1378{
1379 if (ga_grow(&term->tl_scrollback, 1) == OK)
1380 {
1381 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1382 + term->tl_scrollback.ga_len;
1383
1384 if (lnum > 0)
1385 {
1386 int i;
1387
1388 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1389 {
1390 *line = *(line - 1);
1391 --line;
1392 }
1393 }
1394 line->sb_cols = 0;
1395 line->sb_cells = NULL;
1396 line->sb_fill_attr = *fill_attr;
1397 ++term->tl_scrollback.ga_len;
1398 return OK;
1399 }
1400 return FALSE;
1401}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001402
1403/*
1404 * Add the current lines of the terminal to scrollback and to the buffer.
1405 * Called after the job has ended and when switching to Terminal-Normal mode.
1406 */
1407 static void
1408move_terminal_to_buffer(term_T *term)
1409{
1410 win_T *wp;
1411 int len;
1412 int lines_skipped = 0;
1413 VTermPos pos;
1414 VTermScreenCell cell;
1415 cellattr_T fill_attr, new_fill_attr;
1416 cellattr_T *p;
1417 VTermScreen *screen;
1418
1419 if (term->tl_vterm == NULL)
1420 return;
1421 screen = vterm_obtain_screen(term->tl_vterm);
1422 fill_attr = new_fill_attr = term->tl_default_color;
1423
1424 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1425 {
1426 len = 0;
1427 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1428 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1429 && cell.chars[0] != NUL)
1430 {
1431 len = pos.col + 1;
1432 new_fill_attr = term->tl_default_color;
1433 }
1434 else
1435 /* Assume the last attr is the filler attr. */
1436 cell2cellattr(&cell, &new_fill_attr);
1437
1438 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1439 ++lines_skipped;
1440 else
1441 {
1442 while (lines_skipped > 0)
1443 {
1444 /* Line was skipped, add an empty line. */
1445 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001446 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001447 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001448 }
1449
1450 if (len == 0)
1451 p = NULL;
1452 else
1453 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1454 if ((p != NULL || len == 0)
1455 && ga_grow(&term->tl_scrollback, 1) == OK)
1456 {
1457 garray_T ga;
1458 int width;
1459 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1460 + term->tl_scrollback.ga_len;
1461
1462 ga_init2(&ga, 1, 100);
1463 for (pos.col = 0; pos.col < len; pos.col += width)
1464 {
1465 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1466 {
1467 width = 1;
1468 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1469 if (ga_grow(&ga, 1) == OK)
1470 ga.ga_len += utf_char2bytes(' ',
1471 (char_u *)ga.ga_data + ga.ga_len);
1472 }
1473 else
1474 {
1475 width = cell.width;
1476
1477 cell2cellattr(&cell, &p[pos.col]);
1478
1479 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1480 {
1481 int i;
1482 int c;
1483
1484 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1485 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1486 (char_u *)ga.ga_data + ga.ga_len);
1487 }
1488 }
1489 }
1490 line->sb_cols = len;
1491 line->sb_cells = p;
1492 line->sb_fill_attr = new_fill_attr;
1493 fill_attr = new_fill_attr;
1494 ++term->tl_scrollback.ga_len;
1495
1496 if (ga_grow(&ga, 1) == FAIL)
1497 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1498 else
1499 {
1500 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1501 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1502 }
1503 ga_clear(&ga);
1504 }
1505 else
1506 vim_free(p);
1507 }
1508 }
1509
1510 /* Obtain the current background color. */
1511 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1512 &term->tl_default_color.fg, &term->tl_default_color.bg);
1513
1514 FOR_ALL_WINDOWS(wp)
1515 {
1516 if (wp->w_buffer == term->tl_buffer)
1517 {
1518 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1519 wp->w_cursor.col = 0;
1520 wp->w_valid = 0;
1521 if (wp->w_cursor.lnum >= wp->w_height)
1522 {
1523 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1524
1525 if (wp->w_topline < min_topline)
1526 wp->w_topline = min_topline;
1527 }
1528 redraw_win_later(wp, NOT_VALID);
1529 }
1530 }
1531}
1532
1533 static void
1534set_terminal_mode(term_T *term, int normal_mode)
1535{
1536 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001537 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001538 if (term->tl_buffer == curbuf)
1539 maketitle();
1540}
1541
1542/*
1543 * Called after the job if finished and Terminal mode is not active:
1544 * Move the vterm contents into the scrollback buffer and free the vterm.
1545 */
1546 static void
1547cleanup_vterm(term_T *term)
1548{
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001549 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001550 move_terminal_to_buffer(term);
1551 term_free_vterm(term);
1552 set_terminal_mode(term, FALSE);
1553}
1554
1555/*
1556 * Switch from Terminal-Job mode to Terminal-Normal mode.
1557 * Suspends updating the terminal window.
1558 */
1559 static void
1560term_enter_normal_mode(void)
1561{
1562 term_T *term = curbuf->b_term;
1563
1564 /* Append the current terminal contents to the buffer. */
1565 move_terminal_to_buffer(term);
1566
1567 set_terminal_mode(term, TRUE);
1568
1569 /* Move the window cursor to the position of the cursor in the
1570 * terminal. */
1571 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1572 + term->tl_cursor_pos.row + 1;
1573 check_cursor();
1574 coladvance(term->tl_cursor_pos.col);
1575
1576 /* Display the same lines as in the terminal. */
1577 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1578}
1579
1580/*
1581 * Returns TRUE if the current window contains a terminal and we are in
1582 * Terminal-Normal mode.
1583 */
1584 int
1585term_in_normal_mode(void)
1586{
1587 term_T *term = curbuf->b_term;
1588
1589 return term != NULL && term->tl_normal_mode;
1590}
1591
1592/*
1593 * Switch from Terminal-Normal mode to Terminal-Job mode.
1594 * Restores updating the terminal window.
1595 */
1596 void
1597term_enter_job_mode()
1598{
1599 term_T *term = curbuf->b_term;
1600 sb_line_T *line;
1601 garray_T *gap;
1602
1603 /* Remove the terminal contents from the scrollback and the buffer. */
1604 gap = &term->tl_scrollback;
1605 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1606 && gap->ga_len > 0)
1607 {
1608 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1609 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1610 vim_free(line->sb_cells);
1611 --gap->ga_len;
1612 }
1613 check_cursor();
1614
1615 set_terminal_mode(term, FALSE);
1616
1617 if (term->tl_channel_closed)
1618 cleanup_vterm(term);
1619 redraw_buf_and_status_later(curbuf, NOT_VALID);
1620}
1621
1622/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001623 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001624 * Note: while waiting a terminal may be closed and freed if the channel is
1625 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001626 */
1627 static int
1628term_vgetc()
1629{
1630 int c;
1631 int save_State = State;
1632
1633 State = TERMINAL;
1634 got_int = FALSE;
1635#ifdef WIN3264
1636 ctrl_break_was_pressed = FALSE;
1637#endif
1638 c = vgetc();
1639 got_int = FALSE;
1640 State = save_State;
1641 return c;
1642}
1643
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001644static int mouse_was_outside = FALSE;
1645
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001646/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001647 * Send keys to terminal.
1648 * Return FAIL when the key needs to be handled in Normal mode.
1649 * Return OK when the key was dropped or sent to the terminal.
1650 */
1651 int
1652send_keys_to_term(term_T *term, int c, int typed)
1653{
1654 char msg[KEY_BUF_LEN];
1655 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001656 int dragging_outside = FALSE;
1657
1658 /* Catch keys that need to be handled as in Normal mode. */
1659 switch (c)
1660 {
1661 case NUL:
1662 case K_ZERO:
1663 if (typed)
1664 stuffcharReadbuff(c);
1665 return FAIL;
1666
1667 case K_IGNORE:
1668 return FAIL;
1669
1670 case K_LEFTDRAG:
1671 case K_MIDDLEDRAG:
1672 case K_RIGHTDRAG:
1673 case K_X1DRAG:
1674 case K_X2DRAG:
1675 dragging_outside = mouse_was_outside;
1676 /* FALLTHROUGH */
1677 case K_LEFTMOUSE:
1678 case K_LEFTMOUSE_NM:
1679 case K_LEFTRELEASE:
1680 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001681 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001682 case K_MIDDLEMOUSE:
1683 case K_MIDDLERELEASE:
1684 case K_RIGHTMOUSE:
1685 case K_RIGHTRELEASE:
1686 case K_X1MOUSE:
1687 case K_X1RELEASE:
1688 case K_X2MOUSE:
1689 case K_X2RELEASE:
1690
1691 case K_MOUSEUP:
1692 case K_MOUSEDOWN:
1693 case K_MOUSELEFT:
1694 case K_MOUSERIGHT:
1695 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001696 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001697 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001698 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001699 || dragging_outside)
1700 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001701 /* click or scroll outside the current window or on status line
1702 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001703 if (typed)
1704 {
1705 stuffcharReadbuff(c);
1706 mouse_was_outside = TRUE;
1707 }
1708 return FAIL;
1709 }
1710 }
1711 if (typed)
1712 mouse_was_outside = FALSE;
1713
1714 /* Convert the typed key to a sequence of bytes for the job. */
1715 len = term_convert_key(term, c, msg);
1716 if (len > 0)
1717 /* TODO: if FAIL is returned, stop? */
1718 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1719 (char_u *)msg, (int)len, NULL);
1720
1721 return OK;
1722}
1723
1724 static void
1725position_cursor(win_T *wp, VTermPos *pos)
1726{
1727 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1728 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1729 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1730}
1731
1732/*
1733 * Handle CTRL-W "": send register contents to the job.
1734 */
1735 static void
1736term_paste_register(int prev_c UNUSED)
1737{
1738 int c;
1739 list_T *l;
1740 listitem_T *item;
1741 long reglen = 0;
1742 int type;
1743
1744#ifdef FEAT_CMDL_INFO
1745 if (add_to_showcmd(prev_c))
1746 if (add_to_showcmd('"'))
1747 out_flush();
1748#endif
1749 c = term_vgetc();
1750#ifdef FEAT_CMDL_INFO
1751 clear_showcmd();
1752#endif
1753 if (!term_use_loop())
1754 /* job finished while waiting for a character */
1755 return;
1756
1757 /* CTRL-W "= prompt for expression to evaluate. */
1758 if (c == '=' && get_expr_register() != '=')
1759 return;
1760 if (!term_use_loop())
1761 /* job finished while waiting for a character */
1762 return;
1763
1764 l = (list_T *)get_reg_contents(c, GREG_LIST);
1765 if (l != NULL)
1766 {
1767 type = get_reg_type(c, &reglen);
1768 for (item = l->lv_first; item != NULL; item = item->li_next)
1769 {
1770 char_u *s = get_tv_string(&item->li_tv);
1771#ifdef WIN3264
1772 char_u *tmp = s;
1773
1774 if (!enc_utf8 && enc_codepage > 0)
1775 {
1776 WCHAR *ret = NULL;
1777 int length = 0;
1778
1779 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1780 (int)STRLEN(s), &ret, &length);
1781 if (ret != NULL)
1782 {
1783 WideCharToMultiByte_alloc(CP_UTF8, 0,
1784 ret, length, (char **)&s, &length, 0, 0);
1785 vim_free(ret);
1786 }
1787 }
1788#endif
1789 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1790 s, (int)STRLEN(s), NULL);
1791#ifdef WIN3264
1792 if (tmp != s)
1793 vim_free(s);
1794#endif
1795
1796 if (item->li_next != NULL || type == MLINE)
1797 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1798 (char_u *)"\r", 1, NULL);
1799 }
1800 list_free(l);
1801 }
1802}
1803
1804#if defined(FEAT_GUI) || defined(PROTO)
1805/*
1806 * Return TRUE when the cursor of the terminal should be displayed.
1807 */
1808 int
1809terminal_is_active()
1810{
1811 return in_terminal_loop != NULL;
1812}
1813
1814 cursorentry_T *
1815term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1816{
1817 term_T *term = in_terminal_loop;
1818 static cursorentry_T entry;
1819
1820 vim_memset(&entry, 0, sizeof(entry));
1821 entry.shape = entry.mshape =
1822 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1823 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1824 SHAPE_BLOCK;
1825 entry.percentage = 20;
1826 if (term->tl_cursor_blink)
1827 {
1828 entry.blinkwait = 700;
1829 entry.blinkon = 400;
1830 entry.blinkoff = 250;
1831 }
1832 *fg = gui.back_pixel;
1833 if (term->tl_cursor_color == NULL)
1834 *bg = gui.norm_pixel;
1835 else
1836 *bg = color_name2handle(term->tl_cursor_color);
1837 entry.name = "n";
1838 entry.used_for = SHAPE_CURSOR;
1839
1840 return &entry;
1841}
1842#endif
1843
Bram Moolenaard317b382018-02-08 22:33:31 +01001844 static void
1845may_output_cursor_props(void)
1846{
1847 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1848 || last_set_cursor_shape != desired_cursor_shape
1849 || last_set_cursor_blink != desired_cursor_blink)
1850 {
1851 last_set_cursor_color = desired_cursor_color;
1852 last_set_cursor_shape = desired_cursor_shape;
1853 last_set_cursor_blink = desired_cursor_blink;
1854 term_cursor_color(desired_cursor_color);
1855 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1856 /* this will restore the initial cursor style, if possible */
1857 ui_cursor_shape_forced(TRUE);
1858 else
1859 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1860 }
1861}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001862
Bram Moolenaard317b382018-02-08 22:33:31 +01001863/*
1864 * Set the cursor color and shape, if not last set to these.
1865 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001866 static void
1867may_set_cursor_props(term_T *term)
1868{
1869#ifdef FEAT_GUI
1870 /* For the GUI the cursor properties are obtained with
1871 * term_get_cursor_shape(). */
1872 if (gui.in_use)
1873 return;
1874#endif
1875 if (in_terminal_loop == term)
1876 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001877 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001878 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001879 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001880 desired_cursor_color = (char_u *)"";
1881 desired_cursor_shape = term->tl_cursor_shape;
1882 desired_cursor_blink = term->tl_cursor_blink;
1883 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001884 }
1885}
1886
Bram Moolenaard317b382018-02-08 22:33:31 +01001887/*
1888 * Reset the desired cursor properties and restore them when needed.
1889 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001890 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001891prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001892{
1893#ifdef FEAT_GUI
1894 if (gui.in_use)
1895 return;
1896#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001897 desired_cursor_color = (char_u *)"";
1898 desired_cursor_shape = -1;
1899 desired_cursor_blink = -1;
1900 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001901}
1902
1903/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001904 * Called when entering a window with the mouse. If this is a terminal window
1905 * we may want to change state.
1906 */
1907 void
1908term_win_entered()
1909{
1910 term_T *term = curbuf->b_term;
1911
1912 if (term != NULL)
1913 {
1914 if (term_use_loop())
1915 {
1916 reset_VIsual_and_resel();
1917 if (State & INSERT)
1918 stop_insert_mode = TRUE;
1919 }
1920 mouse_was_outside = FALSE;
1921 enter_mouse_col = mouse_col;
1922 enter_mouse_row = mouse_row;
1923 }
1924}
1925
1926/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001927 * Returns TRUE if the current window contains a terminal and we are sending
1928 * keys to the job.
1929 */
1930 int
1931term_use_loop(void)
1932{
1933 term_T *term = curbuf->b_term;
1934
1935 return term != NULL
1936 && !term->tl_normal_mode
1937 && term->tl_vterm != NULL
1938 && term_job_running(term);
1939}
1940
1941/*
1942 * Wait for input and send it to the job.
1943 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1944 * when there is no more typahead.
1945 * Return when the start of a CTRL-W command is typed or anything else that
1946 * should be handled as a Normal mode command.
1947 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1948 * the terminal was closed.
1949 */
1950 int
1951terminal_loop(int blocking)
1952{
1953 int c;
1954 int termkey = 0;
1955 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001956#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001957 int tty_fd = curbuf->b_term->tl_job->jv_channel
1958 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001959#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001960 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001961
1962 /* Remember the terminal we are sending keys to. However, the terminal
1963 * might be closed while waiting for a character, e.g. typing "exit" in a
1964 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1965 * stored reference. */
1966 in_terminal_loop = curbuf->b_term;
1967
1968 if (*curwin->w_p_tk != NUL)
1969 termkey = string_to_key(curwin->w_p_tk, TRUE);
1970 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
1971 may_set_cursor_props(curbuf->b_term);
1972
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001973 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001974 {
Bram Moolenaar13568252018-03-16 20:46:58 +01001975#ifdef FEAT_GUI
1976 if (!curbuf->b_term->tl_system)
1977#endif
1978 /* TODO: skip screen update when handling a sequence of keys. */
1979 /* Repeat redrawing in case a message is received while redrawing.
1980 */
1981 while (must_redraw != 0)
1982 if (update_screen(0) == FAIL)
1983 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001984 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01001985 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001986
1987 c = term_vgetc();
1988 if (!term_use_loop())
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001989 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001990 /* Job finished while waiting for a character. Push back the
1991 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001992 if (c != K_IGNORE)
1993 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001994 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001995 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001996 if (c == K_IGNORE)
1997 continue;
1998
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001999#ifdef UNIX
2000 /*
2001 * The shell or another program may change the tty settings. Getting
2002 * them for every typed character is a bit of overhead, but it's needed
2003 * for the first character typed, e.g. when Vim starts in a shell.
2004 */
2005 if (isatty(tty_fd))
2006 {
2007 ttyinfo_T info;
2008
2009 /* Get the current backspace character of the pty. */
2010 if (get_tty_info(tty_fd, &info) == OK)
2011 term_backspace_char = info.backspace;
2012 }
2013#endif
2014
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002015#ifdef WIN3264
2016 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2017 * Use CTRL-BREAK to kill the job. */
2018 if (ctrl_break_was_pressed)
2019 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2020#endif
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002021 /* Was either CTRL-W (termkey) or CTRL-\ pressed?
2022 * Not in a system terminal. */
2023 if ((c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
2024#ifdef FEAT_GUI
2025 && !curbuf->b_term->tl_system
2026#endif
2027 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002028 {
2029 int prev_c = c;
2030
2031#ifdef FEAT_CMDL_INFO
2032 if (add_to_showcmd(c))
2033 out_flush();
2034#endif
2035 c = term_vgetc();
2036#ifdef FEAT_CMDL_INFO
2037 clear_showcmd();
2038#endif
2039 if (!term_use_loop())
2040 /* job finished while waiting for a character */
2041 break;
2042
2043 if (prev_c == Ctrl_BSL)
2044 {
2045 if (c == Ctrl_N)
2046 {
2047 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2048 term_enter_normal_mode();
2049 ret = FAIL;
2050 goto theend;
2051 }
2052 /* Send both keys to the terminal. */
2053 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
2054 }
2055 else if (c == Ctrl_C)
2056 {
2057 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
2058 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2059 }
2060 else if (termkey == 0 && c == '.')
2061 {
2062 /* "CTRL-W .": send CTRL-W to the job */
2063 c = Ctrl_W;
2064 }
2065 else if (c == 'N')
2066 {
2067 /* CTRL-W N : go to Terminal-Normal mode. */
2068 term_enter_normal_mode();
2069 ret = FAIL;
2070 goto theend;
2071 }
2072 else if (c == '"')
2073 {
2074 term_paste_register(prev_c);
2075 continue;
2076 }
2077 else if (termkey == 0 || c != termkey)
2078 {
2079 stuffcharReadbuff(Ctrl_W);
2080 stuffcharReadbuff(c);
2081 ret = OK;
2082 goto theend;
2083 }
2084 }
2085# ifdef WIN3264
2086 if (!enc_utf8 && has_mbyte && c >= 0x80)
2087 {
2088 WCHAR wc;
2089 char_u mb[3];
2090
2091 mb[0] = (unsigned)c >> 8;
2092 mb[1] = c;
2093 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
2094 c = wc;
2095 }
2096# endif
2097 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
2098 {
Bram Moolenaard317b382018-02-08 22:33:31 +01002099 if (c == K_MOUSEMOVE)
2100 /* We are sure to come back here, don't reset the cursor color
2101 * and shape to avoid flickering. */
2102 restore_cursor = FALSE;
2103
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002104 ret = OK;
2105 goto theend;
2106 }
2107 }
2108 ret = FAIL;
2109
2110theend:
2111 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002112 if (restore_cursor)
2113 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002114 return ret;
2115}
2116
2117/*
2118 * Called when a job has finished.
2119 * This updates the title and status, but does not close the vterm, because
2120 * there might still be pending output in the channel.
2121 */
2122 void
2123term_job_ended(job_T *job)
2124{
2125 term_T *term;
2126 int did_one = FALSE;
2127
2128 for (term = first_term; term != NULL; term = term->tl_next)
2129 if (term->tl_job == job)
2130 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01002131 VIM_CLEAR(term->tl_title);
2132 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002133 redraw_buf_and_status_later(term->tl_buffer, VALID);
2134 did_one = TRUE;
2135 }
2136 if (did_one)
2137 redraw_statuslines();
2138 if (curbuf->b_term != NULL)
2139 {
2140 if (curbuf->b_term->tl_job == job)
2141 maketitle();
2142 update_cursor(curbuf->b_term, TRUE);
2143 }
2144}
2145
2146 static void
2147may_toggle_cursor(term_T *term)
2148{
2149 if (in_terminal_loop == term)
2150 {
2151 if (term->tl_cursor_visible)
2152 cursor_on();
2153 else
2154 cursor_off();
2155 }
2156}
2157
2158/*
2159 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002160 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002161 */
2162 static int
2163color2index(VTermColor *color, int fg, int *boldp)
2164{
2165 int red = color->red;
2166 int blue = color->blue;
2167 int green = color->green;
2168
Bram Moolenaar46359e12017-11-29 22:33:38 +01002169 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002170 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002171 /* First 16 colors and default: use the ANSI index, because these
2172 * colors can be redefined. */
2173 if (t_colors >= 16)
2174 return color->ansi_index;
2175 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002176 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002177 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002178 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002179 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2180 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2181 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
2182 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue*/
2183 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2184 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2185 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2186 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2187 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2188 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2189 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2190 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2191 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2192 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2193 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002194 }
2195 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002196
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002197 if (t_colors >= 256)
2198 {
2199 if (red == blue && red == green)
2200 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002201 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002202 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002203 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2204 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2205 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002206 int i;
2207
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002208 if (red < 5)
2209 return 17; /* 00/00/00 */
2210 if (red > 245) /* ff/ff/ff */
2211 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002212 for (i = 0; i < 23; ++i)
2213 if (red < cutoff[i])
2214 return i + 233;
2215 return 256;
2216 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002217 {
2218 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2219 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002220
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002221 /* 216-color cube */
2222 for (ri = 0; ri < 5; ++ri)
2223 if (red < cutoff[ri])
2224 break;
2225 for (gi = 0; gi < 5; ++gi)
2226 if (green < cutoff[gi])
2227 break;
2228 for (bi = 0; bi < 5; ++bi)
2229 if (blue < cutoff[bi])
2230 break;
2231 return 17 + ri * 36 + gi * 6 + bi;
2232 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002233 }
2234 return 0;
2235}
2236
2237/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002238 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002239 */
2240 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002241vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002242{
2243 int attr = 0;
2244
2245 if (cellattrs.bold)
2246 attr |= HL_BOLD;
2247 if (cellattrs.underline)
2248 attr |= HL_UNDERLINE;
2249 if (cellattrs.italic)
2250 attr |= HL_ITALIC;
2251 if (cellattrs.strike)
2252 attr |= HL_STRIKETHROUGH;
2253 if (cellattrs.reverse)
2254 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002255 return attr;
2256}
2257
2258/*
2259 * Store Vterm attributes in "cell" from highlight flags.
2260 */
2261 static void
2262hl2vtermAttr(int attr, cellattr_T *cell)
2263{
2264 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2265 if (attr & HL_BOLD)
2266 cell->attrs.bold = 1;
2267 if (attr & HL_UNDERLINE)
2268 cell->attrs.underline = 1;
2269 if (attr & HL_ITALIC)
2270 cell->attrs.italic = 1;
2271 if (attr & HL_STRIKETHROUGH)
2272 cell->attrs.strike = 1;
2273 if (attr & HL_INVERSE)
2274 cell->attrs.reverse = 1;
2275}
2276
2277/*
2278 * Convert the attributes of a vterm cell into an attribute index.
2279 */
2280 static int
2281cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2282{
2283 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002284
2285#ifdef FEAT_GUI
2286 if (gui.in_use)
2287 {
2288 guicolor_T fg, bg;
2289
2290 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2291 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2292 return get_gui_attr_idx(attr, fg, bg);
2293 }
2294 else
2295#endif
2296#ifdef FEAT_TERMGUICOLORS
2297 if (p_tgc)
2298 {
2299 guicolor_T fg, bg;
2300
2301 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2302 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2303
2304 return get_tgc_attr_idx(attr, fg, bg);
2305 }
2306 else
2307#endif
2308 {
2309 int bold = MAYBE;
2310 int fg = color2index(&cellfg, TRUE, &bold);
2311 int bg = color2index(&cellbg, FALSE, &bold);
2312
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002313 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002314 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002315 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002316 if (fg == 0 && term_default_cterm_fg >= 0)
2317 fg = term_default_cterm_fg + 1;
2318 if (bg == 0 && term_default_cterm_bg >= 0)
2319 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002320 }
2321
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002322 /* with 8 colors set the bold attribute to get a bright foreground */
2323 if (bold == TRUE)
2324 attr |= HL_BOLD;
2325 return get_cterm_attr_idx(attr, fg, bg);
2326 }
2327 return 0;
2328}
2329
2330 static int
2331handle_damage(VTermRect rect, void *user)
2332{
2333 term_T *term = (term_T *)user;
2334
2335 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2336 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2337 redraw_buf_later(term->tl_buffer, NOT_VALID);
2338 return 1;
2339}
2340
2341 static int
2342handle_moverect(VTermRect dest, VTermRect src, void *user)
2343{
2344 term_T *term = (term_T *)user;
2345
2346 /* Scrolling up is done much more efficiently by deleting lines instead of
2347 * redrawing the text. */
2348 if (dest.start_col == src.start_col
2349 && dest.end_col == src.end_col
2350 && dest.start_row < src.start_row)
2351 {
2352 win_T *wp;
2353 VTermColor fg, bg;
2354 VTermScreenCellAttrs attr;
2355 int clear_attr;
2356
2357 /* Set the color to clear lines with. */
2358 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2359 &fg, &bg);
2360 vim_memset(&attr, 0, sizeof(attr));
2361 clear_attr = cell2attr(attr, fg, bg);
2362
2363 FOR_ALL_WINDOWS(wp)
2364 {
2365 if (wp->w_buffer == term->tl_buffer)
2366 win_del_lines(wp, dest.start_row,
2367 src.start_row - dest.start_row, FALSE, FALSE,
2368 clear_attr);
2369 }
2370 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002371
2372 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2373 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2374
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002375 redraw_buf_later(term->tl_buffer, NOT_VALID);
2376 return 1;
2377}
2378
2379 static int
2380handle_movecursor(
2381 VTermPos pos,
2382 VTermPos oldpos UNUSED,
2383 int visible,
2384 void *user)
2385{
2386 term_T *term = (term_T *)user;
2387 win_T *wp;
2388
2389 term->tl_cursor_pos = pos;
2390 term->tl_cursor_visible = visible;
2391
2392 FOR_ALL_WINDOWS(wp)
2393 {
2394 if (wp->w_buffer == term->tl_buffer)
2395 position_cursor(wp, &pos);
2396 }
2397 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2398 {
2399 may_toggle_cursor(term);
2400 update_cursor(term, term->tl_cursor_visible);
2401 }
2402
2403 return 1;
2404}
2405
2406 static int
2407handle_settermprop(
2408 VTermProp prop,
2409 VTermValue *value,
2410 void *user)
2411{
2412 term_T *term = (term_T *)user;
2413
2414 switch (prop)
2415 {
2416 case VTERM_PROP_TITLE:
2417 vim_free(term->tl_title);
2418 /* a blank title isn't useful, make it empty, so that "running" is
2419 * displayed */
2420 if (*skipwhite((char_u *)value->string) == NUL)
2421 term->tl_title = NULL;
2422#ifdef WIN3264
2423 else if (!enc_utf8 && enc_codepage > 0)
2424 {
2425 WCHAR *ret = NULL;
2426 int length = 0;
2427
2428 MultiByteToWideChar_alloc(CP_UTF8, 0,
2429 (char*)value->string, (int)STRLEN(value->string),
2430 &ret, &length);
2431 if (ret != NULL)
2432 {
2433 WideCharToMultiByte_alloc(enc_codepage, 0,
2434 ret, length, (char**)&term->tl_title,
2435 &length, 0, 0);
2436 vim_free(ret);
2437 }
2438 }
2439#endif
2440 else
2441 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002442 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002443 if (term == curbuf->b_term)
2444 maketitle();
2445 break;
2446
2447 case VTERM_PROP_CURSORVISIBLE:
2448 term->tl_cursor_visible = value->boolean;
2449 may_toggle_cursor(term);
2450 out_flush();
2451 break;
2452
2453 case VTERM_PROP_CURSORBLINK:
2454 term->tl_cursor_blink = value->boolean;
2455 may_set_cursor_props(term);
2456 break;
2457
2458 case VTERM_PROP_CURSORSHAPE:
2459 term->tl_cursor_shape = value->number;
2460 may_set_cursor_props(term);
2461 break;
2462
2463 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002464 if (desired_cursor_color == term->tl_cursor_color)
2465 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002466 vim_free(term->tl_cursor_color);
2467 if (*value->string == NUL)
2468 term->tl_cursor_color = NULL;
2469 else
2470 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2471 may_set_cursor_props(term);
2472 break;
2473
2474 case VTERM_PROP_ALTSCREEN:
2475 /* TODO: do anything else? */
2476 term->tl_using_altscreen = value->boolean;
2477 break;
2478
2479 default:
2480 break;
2481 }
2482 /* Always return 1, otherwise vterm doesn't store the value internally. */
2483 return 1;
2484}
2485
2486/*
2487 * The job running in the terminal resized the terminal.
2488 */
2489 static int
2490handle_resize(int rows, int cols, void *user)
2491{
2492 term_T *term = (term_T *)user;
2493 win_T *wp;
2494
2495 term->tl_rows = rows;
2496 term->tl_cols = cols;
2497 if (term->tl_vterm_size_changed)
2498 /* Size was set by vterm_set_size(), don't set the window size. */
2499 term->tl_vterm_size_changed = FALSE;
2500 else
2501 {
2502 FOR_ALL_WINDOWS(wp)
2503 {
2504 if (wp->w_buffer == term->tl_buffer)
2505 {
2506 win_setheight_win(rows, wp);
2507 win_setwidth_win(cols, wp);
2508 }
2509 }
2510 redraw_buf_later(term->tl_buffer, NOT_VALID);
2511 }
2512 return 1;
2513}
2514
2515/*
2516 * Handle a line that is pushed off the top of the screen.
2517 */
2518 static int
2519handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2520{
2521 term_T *term = (term_T *)user;
2522
2523 /* TODO: Limit the number of lines that are stored. */
2524 if (ga_grow(&term->tl_scrollback, 1) == OK)
2525 {
2526 cellattr_T *p = NULL;
2527 int len = 0;
2528 int i;
2529 int c;
2530 int col;
2531 sb_line_T *line;
2532 garray_T ga;
2533 cellattr_T fill_attr = term->tl_default_color;
2534
2535 /* do not store empty cells at the end */
2536 for (i = 0; i < cols; ++i)
2537 if (cells[i].chars[0] != 0)
2538 len = i + 1;
2539 else
2540 cell2cellattr(&cells[i], &fill_attr);
2541
2542 ga_init2(&ga, 1, 100);
2543 if (len > 0)
2544 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2545 if (p != NULL)
2546 {
2547 for (col = 0; col < len; col += cells[col].width)
2548 {
2549 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2550 {
2551 ga.ga_len = 0;
2552 break;
2553 }
2554 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2555 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2556 (char_u *)ga.ga_data + ga.ga_len);
2557 cell2cellattr(&cells[col], &p[col]);
2558 }
2559 }
2560 if (ga_grow(&ga, 1) == FAIL)
2561 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2562 else
2563 {
2564 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2565 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2566 }
2567 ga_clear(&ga);
2568
2569 line = (sb_line_T *)term->tl_scrollback.ga_data
2570 + term->tl_scrollback.ga_len;
2571 line->sb_cols = len;
2572 line->sb_cells = p;
2573 line->sb_fill_attr = fill_attr;
2574 ++term->tl_scrollback.ga_len;
2575 ++term->tl_scrollback_scrolled;
2576 }
2577 return 0; /* ignored */
2578}
2579
2580static VTermScreenCallbacks screen_callbacks = {
2581 handle_damage, /* damage */
2582 handle_moverect, /* moverect */
2583 handle_movecursor, /* movecursor */
2584 handle_settermprop, /* settermprop */
2585 NULL, /* bell */
2586 handle_resize, /* resize */
2587 handle_pushline, /* sb_pushline */
2588 NULL /* sb_popline */
2589};
2590
2591/*
2592 * Called when a channel has been closed.
2593 * If this was a channel for a terminal window then finish it up.
2594 */
2595 void
2596term_channel_closed(channel_T *ch)
2597{
2598 term_T *term;
2599 int did_one = FALSE;
2600
2601 for (term = first_term; term != NULL; term = term->tl_next)
2602 if (term->tl_job == ch->ch_job)
2603 {
2604 term->tl_channel_closed = TRUE;
2605 did_one = TRUE;
2606
Bram Moolenaard23a8232018-02-10 18:45:26 +01002607 VIM_CLEAR(term->tl_title);
2608 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002609
2610 /* Unless in Terminal-Normal mode: clear the vterm. */
2611 if (!term->tl_normal_mode)
2612 {
2613 int fnum = term->tl_buffer->b_fnum;
2614
2615 cleanup_vterm(term);
2616
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002617 if (term->tl_finish == TL_FINISH_CLOSE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002618 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002619 aco_save_T aco;
2620
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002621 /* ++close or term_finish == "close" */
2622 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002623 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002624 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002625 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002626 break;
2627 }
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002628 if (term->tl_finish == TL_FINISH_OPEN
2629 && term->tl_buffer->b_nwindows == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002630 {
2631 char buf[50];
2632
2633 /* TODO: use term_opencmd */
2634 ch_log(NULL, "terminal job finished, opening window");
2635 vim_snprintf(buf, sizeof(buf),
2636 term->tl_opencmd == NULL
2637 ? "botright sbuf %d"
2638 : (char *)term->tl_opencmd, fnum);
2639 do_cmdline_cmd((char_u *)buf);
2640 }
2641 else
2642 ch_log(NULL, "terminal job finished");
2643 }
2644
2645 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2646 }
2647 if (did_one)
2648 {
2649 redraw_statuslines();
2650
2651 /* Need to break out of vgetc(). */
2652 ins_char_typebuf(K_IGNORE);
2653 typebuf_was_filled = TRUE;
2654
2655 term = curbuf->b_term;
2656 if (term != NULL)
2657 {
2658 if (term->tl_job == ch->ch_job)
2659 maketitle();
2660 update_cursor(term, term->tl_cursor_visible);
2661 }
2662 }
2663}
2664
2665/*
Bram Moolenaar13568252018-03-16 20:46:58 +01002666 * Fill one screen line from a line of the terminal.
2667 * Advances "pos" to past the last column.
2668 */
2669 static void
2670term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
2671{
2672 int off = screen_get_current_line_off();
2673
2674 for (pos->col = 0; pos->col < max_col; )
2675 {
2676 VTermScreenCell cell;
2677 int c;
2678
2679 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
2680 vim_memset(&cell, 0, sizeof(cell));
2681
2682 c = cell.chars[0];
2683 if (c == NUL)
2684 {
2685 ScreenLines[off] = ' ';
2686 if (enc_utf8)
2687 ScreenLinesUC[off] = NUL;
2688 }
2689 else
2690 {
2691 if (enc_utf8)
2692 {
2693 int i;
2694
2695 /* composing chars */
2696 for (i = 0; i < Screen_mco
2697 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2698 {
2699 ScreenLinesC[i][off] = cell.chars[i + 1];
2700 if (cell.chars[i + 1] == 0)
2701 break;
2702 }
2703 if (c >= 0x80 || (Screen_mco > 0
2704 && ScreenLinesC[0][off] != 0))
2705 {
2706 ScreenLines[off] = ' ';
2707 ScreenLinesUC[off] = c;
2708 }
2709 else
2710 {
2711 ScreenLines[off] = c;
2712 ScreenLinesUC[off] = NUL;
2713 }
2714 }
2715#ifdef WIN3264
2716 else if (has_mbyte && c >= 0x80)
2717 {
2718 char_u mb[MB_MAXBYTES+1];
2719 WCHAR wc = c;
2720
2721 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2722 (char*)mb, 2, 0, 0) > 1)
2723 {
2724 ScreenLines[off] = mb[0];
2725 ScreenLines[off + 1] = mb[1];
2726 cell.width = mb_ptr2cells(mb);
2727 }
2728 else
2729 ScreenLines[off] = c;
2730 }
2731#endif
2732 else
2733 ScreenLines[off] = c;
2734 }
2735 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2736
2737 ++pos->col;
2738 ++off;
2739 if (cell.width == 2)
2740 {
2741 if (enc_utf8)
2742 ScreenLinesUC[off] = NUL;
2743
2744 /* don't set the second byte to NUL for a DBCS encoding, it
2745 * has been set above */
2746 if (enc_utf8 || !has_mbyte)
2747 ScreenLines[off] = NUL;
2748
2749 ++pos->col;
2750 ++off;
2751 }
2752 }
2753}
2754
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002755#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01002756 static void
2757update_system_term(term_T *term)
2758{
2759 VTermPos pos;
2760 VTermScreen *screen;
2761
2762 if (term->tl_vterm == NULL)
2763 return;
2764 screen = vterm_obtain_screen(term->tl_vterm);
2765
2766 /* Scroll up to make more room for terminal lines if needed. */
2767 while (term->tl_toprow > 0
2768 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
2769 {
2770 int save_p_more = p_more;
2771
2772 p_more = FALSE;
2773 msg_row = Rows - 1;
2774 msg_puts((char_u *)"\n");
2775 p_more = save_p_more;
2776 --term->tl_toprow;
2777 }
2778
2779 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2780 && pos.row < Rows; ++pos.row)
2781 {
2782 if (pos.row < term->tl_rows)
2783 {
2784 int max_col = MIN(Columns, term->tl_cols);
2785
2786 term_line2screenline(screen, &pos, max_col);
2787 }
2788 else
2789 pos.col = 0;
2790
2791 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, FALSE);
2792 }
2793
2794 term->tl_dirty_row_start = MAX_ROW;
2795 term->tl_dirty_row_end = 0;
2796 update_cursor(term, TRUE);
2797}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01002798#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01002799
2800/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002801 * Called to update a window that contains an active terminal.
2802 * Returns FAIL when there is no terminal running in this window or in
2803 * Terminal-Normal mode.
2804 */
2805 int
2806term_update_window(win_T *wp)
2807{
2808 term_T *term = wp->w_buffer->b_term;
2809 VTerm *vterm;
2810 VTermScreen *screen;
2811 VTermState *state;
2812 VTermPos pos;
2813
2814 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2815 return FAIL;
2816
2817 vterm = term->tl_vterm;
2818 screen = vterm_obtain_screen(vterm);
2819 state = vterm_obtain_state(vterm);
2820
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002821 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002822 {
2823 term->tl_dirty_row_start = 0;
2824 term->tl_dirty_row_end = MAX_ROW;
2825 }
2826
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002827 /*
2828 * If the window was resized a redraw will be triggered and we get here.
2829 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2830 */
2831 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
2832 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
2833 {
2834 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
2835 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
2836 win_T *twp;
2837
2838 FOR_ALL_WINDOWS(twp)
2839 {
2840 /* When more than one window shows the same terminal, use the
2841 * smallest size. */
2842 if (twp->w_buffer == term->tl_buffer)
2843 {
2844 if (!term->tl_rows_fixed && rows > twp->w_height)
2845 rows = twp->w_height;
2846 if (!term->tl_cols_fixed && cols > twp->w_width)
2847 cols = twp->w_width;
2848 }
2849 }
2850
2851 term->tl_vterm_size_changed = TRUE;
2852 vterm_set_size(vterm, rows, cols);
2853 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
2854 rows);
2855 term_report_winsize(term, rows, cols);
2856 }
2857
2858 /* The cursor may have been moved when resizing. */
2859 vterm_state_get_cursorpos(state, &pos);
2860 position_cursor(wp, &pos);
2861
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002862 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2863 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002864 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002865 if (pos.row < term->tl_rows)
2866 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002867 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002868
Bram Moolenaar13568252018-03-16 20:46:58 +01002869 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002870 }
2871 else
2872 pos.col = 0;
2873
Bram Moolenaarf118d482018-03-13 13:14:00 +01002874 screen_line(wp->w_winrow + pos.row
2875#ifdef FEAT_MENU
2876 + winbar_height(wp)
2877#endif
2878 , wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002879 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002880 term->tl_dirty_row_start = MAX_ROW;
2881 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002882
2883 return OK;
2884}
2885
2886/*
2887 * Return TRUE if "wp" is a terminal window where the job has finished.
2888 */
2889 int
2890term_is_finished(buf_T *buf)
2891{
2892 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2893}
2894
2895/*
2896 * Return TRUE if "wp" is a terminal window where the job has finished or we
2897 * are in Terminal-Normal mode, thus we show the buffer contents.
2898 */
2899 int
2900term_show_buffer(buf_T *buf)
2901{
2902 term_T *term = buf->b_term;
2903
2904 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2905}
2906
2907/*
2908 * The current buffer is going to be changed. If there is terminal
2909 * highlighting remove it now.
2910 */
2911 void
2912term_change_in_curbuf(void)
2913{
2914 term_T *term = curbuf->b_term;
2915
2916 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2917 {
2918 free_scrollback(term);
2919 redraw_buf_later(term->tl_buffer, NOT_VALID);
2920
2921 /* The buffer is now like a normal buffer, it cannot be easily
2922 * abandoned when changed. */
2923 set_string_option_direct((char_u *)"buftype", -1,
2924 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2925 }
2926}
2927
2928/*
2929 * Get the screen attribute for a position in the buffer.
2930 * Use a negative "col" to get the filler background color.
2931 */
2932 int
2933term_get_attr(buf_T *buf, linenr_T lnum, int col)
2934{
2935 term_T *term = buf->b_term;
2936 sb_line_T *line;
2937 cellattr_T *cellattr;
2938
2939 if (lnum > term->tl_scrollback.ga_len)
2940 cellattr = &term->tl_default_color;
2941 else
2942 {
2943 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
2944 if (col < 0 || col >= line->sb_cols)
2945 cellattr = &line->sb_fill_attr;
2946 else
2947 cellattr = line->sb_cells + col;
2948 }
2949 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
2950}
2951
2952static VTermColor ansi_table[16] = {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002953 { 0, 0, 0, 1}, /* black */
2954 {224, 0, 0, 2}, /* dark red */
2955 { 0, 224, 0, 3}, /* dark green */
2956 {224, 224, 0, 4}, /* dark yellow / brown */
2957 { 0, 0, 224, 5}, /* dark blue */
2958 {224, 0, 224, 6}, /* dark magenta */
2959 { 0, 224, 224, 7}, /* dark cyan */
2960 {224, 224, 224, 8}, /* light grey */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002961
Bram Moolenaar46359e12017-11-29 22:33:38 +01002962 {128, 128, 128, 9}, /* dark grey */
2963 {255, 64, 64, 10}, /* light red */
2964 { 64, 255, 64, 11}, /* light green */
2965 {255, 255, 64, 12}, /* yellow */
2966 { 64, 64, 255, 13}, /* light blue */
2967 {255, 64, 255, 14}, /* light magenta */
2968 { 64, 255, 255, 15}, /* light cyan */
2969 {255, 255, 255, 16}, /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002970};
2971
2972static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002973 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002974};
2975
2976static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002977 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
2978 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002979};
2980
2981/*
2982 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002983 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002984 */
2985 static void
2986cterm_color2rgb(int nr, VTermColor *rgb)
2987{
2988 int idx;
2989
2990 if (nr < 16)
2991 {
2992 *rgb = ansi_table[nr];
2993 }
2994 else if (nr < 232)
2995 {
2996 /* 216 color cube */
2997 idx = nr - 16;
2998 rgb->blue = cube_value[idx % 6];
2999 rgb->green = cube_value[idx / 6 % 6];
3000 rgb->red = cube_value[idx / 36 % 6];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003001 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003002 }
3003 else if (nr < 256)
3004 {
3005 /* 24 grey scale ramp */
3006 idx = nr - 232;
3007 rgb->blue = grey_ramp[idx];
3008 rgb->green = grey_ramp[idx];
3009 rgb->red = grey_ramp[idx];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003010 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003011 }
3012}
3013
3014/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003015 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003016 */
3017 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003018init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003019{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003020 VTermColor *fg, *bg;
3021 int fgval, bgval;
3022 int id;
3023
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003024 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3025 term->tl_default_color.width = 1;
3026 fg = &term->tl_default_color.fg;
3027 bg = &term->tl_default_color.bg;
3028
3029 /* Vterm uses a default black background. Set it to white when
3030 * 'background' is "light". */
3031 if (*p_bg == 'l')
3032 {
3033 fgval = 0;
3034 bgval = 255;
3035 }
3036 else
3037 {
3038 fgval = 255;
3039 bgval = 0;
3040 }
3041 fg->red = fg->green = fg->blue = fgval;
3042 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003043 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003044
3045 /* The "Terminal" highlight group overrules the defaults. */
3046 id = syn_name2id((char_u *)"Terminal");
3047
Bram Moolenaar46359e12017-11-29 22:33:38 +01003048 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003049#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3050 if (0
3051# ifdef FEAT_GUI
3052 || gui.in_use
3053# endif
3054# ifdef FEAT_TERMGUICOLORS
3055 || p_tgc
3056# endif
3057 )
3058 {
3059 guicolor_T fg_rgb = INVALCOLOR;
3060 guicolor_T bg_rgb = INVALCOLOR;
3061
3062 if (id != 0)
3063 syn_id2colors(id, &fg_rgb, &bg_rgb);
3064
3065# ifdef FEAT_GUI
3066 if (gui.in_use)
3067 {
3068 if (fg_rgb == INVALCOLOR)
3069 fg_rgb = gui.norm_pixel;
3070 if (bg_rgb == INVALCOLOR)
3071 bg_rgb = gui.back_pixel;
3072 }
3073# ifdef FEAT_TERMGUICOLORS
3074 else
3075# endif
3076# endif
3077# ifdef FEAT_TERMGUICOLORS
3078 {
3079 if (fg_rgb == INVALCOLOR)
3080 fg_rgb = cterm_normal_fg_gui_color;
3081 if (bg_rgb == INVALCOLOR)
3082 bg_rgb = cterm_normal_bg_gui_color;
3083 }
3084# endif
3085 if (fg_rgb != INVALCOLOR)
3086 {
3087 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3088
3089 fg->red = (unsigned)(rgb >> 16);
3090 fg->green = (unsigned)(rgb >> 8) & 255;
3091 fg->blue = (unsigned)rgb & 255;
3092 }
3093 if (bg_rgb != INVALCOLOR)
3094 {
3095 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3096
3097 bg->red = (unsigned)(rgb >> 16);
3098 bg->green = (unsigned)(rgb >> 8) & 255;
3099 bg->blue = (unsigned)rgb & 255;
3100 }
3101 }
3102 else
3103#endif
3104 if (id != 0 && t_colors >= 16)
3105 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003106 if (term_default_cterm_fg >= 0)
3107 cterm_color2rgb(term_default_cterm_fg, fg);
3108 if (term_default_cterm_bg >= 0)
3109 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003110 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003111 else
3112 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003113#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003114 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003115#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003116
3117 /* In an MS-Windows console we know the normal colors. */
3118 if (cterm_normal_fg_color > 0)
3119 {
3120 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003121# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003122 tmp = fg->red;
3123 fg->red = fg->blue;
3124 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003125# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003126 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003127# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003128 else
3129 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003130# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003131
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003132 if (cterm_normal_bg_color > 0)
3133 {
3134 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003135# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003136 tmp = bg->red;
3137 bg->red = bg->blue;
3138 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003139# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003140 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003141# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003142 else
3143 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003144# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003145 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003146}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003147
Bram Moolenaar52acb112018-03-18 19:20:22 +01003148/*
3149 * Create a new vterm and initialize it.
3150 */
3151 static void
3152create_vterm(term_T *term, int rows, int cols)
3153{
3154 VTerm *vterm;
3155 VTermScreen *screen;
3156 VTermValue value;
3157
3158 vterm = vterm_new(rows, cols);
3159 term->tl_vterm = vterm;
3160 screen = vterm_obtain_screen(vterm);
3161 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
3162 /* TODO: depends on 'encoding'. */
3163 vterm_set_utf8(vterm, 1);
3164
3165 init_default_colors(term);
3166
3167 vterm_state_set_default_colors(
3168 vterm_obtain_state(vterm),
3169 &term->tl_default_color.fg,
3170 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003171
3172 /* Required to initialize most things. */
3173 vterm_screen_reset(screen, 1 /* hard */);
3174
3175 /* Allow using alternate screen. */
3176 vterm_screen_enable_altscreen(screen, 1);
3177
3178 /* For unix do not use a blinking cursor. In an xterm this causes the
3179 * cursor to blink if it's blinking in the xterm.
3180 * For Windows we respect the system wide setting. */
3181#ifdef WIN3264
3182 if (GetCaretBlinkTime() == INFINITE)
3183 value.boolean = 0;
3184 else
3185 value.boolean = 1;
3186#else
3187 value.boolean = 0;
3188#endif
3189 vterm_state_set_termprop(vterm_obtain_state(vterm),
3190 VTERM_PROP_CURSORBLINK, &value);
3191}
3192
3193/*
3194 * Return the text to show for the buffer name and status.
3195 */
3196 char_u *
3197term_get_status_text(term_T *term)
3198{
3199 if (term->tl_status_text == NULL)
3200 {
3201 char_u *txt;
3202 size_t len;
3203
3204 if (term->tl_normal_mode)
3205 {
3206 if (term_job_running(term))
3207 txt = (char_u *)_("Terminal");
3208 else
3209 txt = (char_u *)_("Terminal-finished");
3210 }
3211 else if (term->tl_title != NULL)
3212 txt = term->tl_title;
3213 else if (term_none_open(term))
3214 txt = (char_u *)_("active");
3215 else if (term_job_running(term))
3216 txt = (char_u *)_("running");
3217 else
3218 txt = (char_u *)_("finished");
3219 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
3220 term->tl_status_text = alloc((int)len);
3221 if (term->tl_status_text != NULL)
3222 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
3223 term->tl_buffer->b_fname, txt);
3224 }
3225 return term->tl_status_text;
3226}
3227
3228/*
3229 * Mark references in jobs of terminals.
3230 */
3231 int
3232set_ref_in_term(int copyID)
3233{
3234 int abort = FALSE;
3235 term_T *term;
3236 typval_T tv;
3237
3238 for (term = first_term; term != NULL; term = term->tl_next)
3239 if (term->tl_job != NULL)
3240 {
3241 tv.v_type = VAR_JOB;
3242 tv.vval.v_job = term->tl_job;
3243 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
3244 }
3245 return abort;
3246}
3247
3248/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003249 * Cache "Terminal" highlight group colors.
3250 */
3251 void
3252set_terminal_default_colors(int cterm_fg, int cterm_bg)
3253{
3254 term_default_cterm_fg = cterm_fg - 1;
3255 term_default_cterm_bg = cterm_bg - 1;
3256}
3257
3258/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003259 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003260 * Returns NULL when the buffer is not for a terminal window and logs a message
3261 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003262 */
3263 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003264term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003265{
3266 buf_T *buf;
3267
3268 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
3269 ++emsg_off;
3270 buf = get_buf_tv(&argvars[0], FALSE);
3271 --emsg_off;
3272 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003273 {
3274 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003275 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003276 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003277 return buf;
3278}
3279
Bram Moolenaard96ff162018-02-18 22:13:29 +01003280 static int
3281same_color(VTermColor *a, VTermColor *b)
3282{
3283 return a->red == b->red
3284 && a->green == b->green
3285 && a->blue == b->blue
3286 && a->ansi_index == b->ansi_index;
3287}
3288
3289 static void
3290dump_term_color(FILE *fd, VTermColor *color)
3291{
3292 fprintf(fd, "%02x%02x%02x%d",
3293 (int)color->red, (int)color->green, (int)color->blue,
3294 (int)color->ansi_index);
3295}
3296
3297/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003298 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01003299 *
3300 * Each screen cell in full is:
3301 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
3302 * {characters} is a space for an empty cell
3303 * For a double-width character "+" is changed to "*" and the next cell is
3304 * skipped.
3305 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
3306 * when "&" use the same as the previous cell.
3307 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
3308 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
3309 * {color-idx} is a number from 0 to 255
3310 *
3311 * Screen cell with same width, attributes and color as the previous one:
3312 * |{characters}
3313 *
3314 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
3315 *
3316 * Repeating the previous screen cell:
3317 * @{count}
3318 */
3319 void
3320f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
3321{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01003322 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01003323 term_T *term;
3324 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003325 int max_height = 0;
3326 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003327 stat_T st;
3328 FILE *fd;
3329 VTermPos pos;
3330 VTermScreen *screen;
3331 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003332 VTermState *state;
3333 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003334
3335 if (check_restricted() || check_secure())
3336 return;
3337 if (buf == NULL)
3338 return;
3339 term = buf->b_term;
3340
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003341 if (argvars[2].v_type != VAR_UNKNOWN)
3342 {
3343 dict_T *d;
3344
3345 if (argvars[2].v_type != VAR_DICT)
3346 {
3347 EMSG(_(e_dictreq));
3348 return;
3349 }
3350 d = argvars[2].vval.v_dict;
3351 if (d != NULL)
3352 {
3353 max_height = get_dict_number(d, (char_u *)"rows");
3354 max_width = get_dict_number(d, (char_u *)"columns");
3355 }
3356 }
3357
Bram Moolenaard96ff162018-02-18 22:13:29 +01003358 fname = get_tv_string_chk(&argvars[1]);
3359 if (fname == NULL)
3360 return;
3361 if (mch_stat((char *)fname, &st) >= 0)
3362 {
3363 EMSG2(_("E953: File exists: %s"), fname);
3364 return;
3365 }
3366
Bram Moolenaard96ff162018-02-18 22:13:29 +01003367 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3368 {
3369 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3370 return;
3371 }
3372
3373 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3374
3375 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003376 state = vterm_obtain_state(term->tl_vterm);
3377 vterm_state_get_cursorpos(state, &cursor_pos);
3378
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003379 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3380 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003381 {
3382 int repeat = 0;
3383
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003384 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3385 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003386 {
3387 VTermScreenCell cell;
3388 int same_attr;
3389 int same_chars = TRUE;
3390 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003391 int is_cursor_pos = (pos.col == cursor_pos.col
3392 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003393
3394 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3395 vim_memset(&cell, 0, sizeof(cell));
3396
3397 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3398 {
3399 if (cell.chars[i] != prev_cell.chars[i])
3400 same_chars = FALSE;
3401 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3402 break;
3403 }
3404 same_attr = vtermAttr2hl(cell.attrs)
3405 == vtermAttr2hl(prev_cell.attrs)
3406 && same_color(&cell.fg, &prev_cell.fg)
3407 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003408 if (same_chars && cell.width == prev_cell.width && same_attr
3409 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003410 {
3411 ++repeat;
3412 }
3413 else
3414 {
3415 if (repeat > 0)
3416 {
3417 fprintf(fd, "@%d", repeat);
3418 repeat = 0;
3419 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003420 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003421
3422 if (cell.chars[0] == NUL)
3423 fputs(" ", fd);
3424 else
3425 {
3426 char_u charbuf[10];
3427 int len;
3428
3429 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3430 && cell.chars[i] != NUL; ++i)
3431 {
3432 len = utf_char2bytes(cell.chars[0], charbuf);
3433 fwrite(charbuf, len, 1, fd);
3434 }
3435 }
3436
3437 /* When only the characters differ we don't write anything, the
3438 * following "|", "@" or NL will indicate using the same
3439 * attributes. */
3440 if (cell.width != prev_cell.width || !same_attr)
3441 {
3442 if (cell.width == 2)
3443 {
3444 fputs("*", fd);
3445 ++pos.col;
3446 }
3447 else
3448 fputs("+", fd);
3449
3450 if (same_attr)
3451 {
3452 fputs("&", fd);
3453 }
3454 else
3455 {
3456 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3457 if (same_color(&cell.fg, &prev_cell.fg))
3458 fputs("&", fd);
3459 else
3460 {
3461 fputs("#", fd);
3462 dump_term_color(fd, &cell.fg);
3463 }
3464 if (same_color(&cell.bg, &prev_cell.bg))
3465 fputs("&", fd);
3466 else
3467 {
3468 fputs("#", fd);
3469 dump_term_color(fd, &cell.bg);
3470 }
3471 }
3472 }
3473
3474 prev_cell = cell;
3475 }
3476 }
3477 if (repeat > 0)
3478 fprintf(fd, "@%d", repeat);
3479 fputs("\n", fd);
3480 }
3481
3482 fclose(fd);
3483}
3484
3485/*
3486 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3487 */
3488 static void
3489dump_is_corrupt(garray_T *gap)
3490{
3491 ga_concat(gap, (char_u *)"CORRUPT");
3492}
3493
3494 static void
3495append_cell(garray_T *gap, cellattr_T *cell)
3496{
3497 if (ga_grow(gap, 1) == OK)
3498 {
3499 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3500 ++gap->ga_len;
3501 }
3502}
3503
3504/*
3505 * Read the dump file from "fd" and append lines to the current buffer.
3506 * Return the cell width of the longest line.
3507 */
3508 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003509read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003510{
3511 int c;
3512 garray_T ga_text;
3513 garray_T ga_cell;
3514 char_u *prev_char = NULL;
3515 int attr = 0;
3516 cellattr_T cell;
3517 term_T *term = curbuf->b_term;
3518 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003519 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003520
3521 ga_init2(&ga_text, 1, 90);
3522 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3523 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003524 cursor_pos->row = -1;
3525 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003526
3527 c = fgetc(fd);
3528 for (;;)
3529 {
3530 if (c == EOF)
3531 break;
3532 if (c == '\n')
3533 {
3534 /* End of a line: append it to the buffer. */
3535 if (ga_text.ga_data == NULL)
3536 dump_is_corrupt(&ga_text);
3537 if (ga_grow(&term->tl_scrollback, 1) == OK)
3538 {
3539 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3540 + term->tl_scrollback.ga_len;
3541
3542 if (max_cells < ga_cell.ga_len)
3543 max_cells = ga_cell.ga_len;
3544 line->sb_cols = ga_cell.ga_len;
3545 line->sb_cells = ga_cell.ga_data;
3546 line->sb_fill_attr = term->tl_default_color;
3547 ++term->tl_scrollback.ga_len;
3548 ga_init(&ga_cell);
3549
3550 ga_append(&ga_text, NUL);
3551 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3552 ga_text.ga_len, FALSE);
3553 }
3554 else
3555 ga_clear(&ga_cell);
3556 ga_text.ga_len = 0;
3557
3558 c = fgetc(fd);
3559 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003560 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003561 {
3562 int prev_len = ga_text.ga_len;
3563
Bram Moolenaar9271d052018-02-25 21:39:46 +01003564 if (c == '>')
3565 {
3566 if (cursor_pos->row != -1)
3567 dump_is_corrupt(&ga_text); /* duplicate cursor */
3568 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3569 cursor_pos->col = ga_cell.ga_len;
3570 }
3571
Bram Moolenaard96ff162018-02-18 22:13:29 +01003572 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3573 c = fgetc(fd);
3574 if (c != EOF)
3575 ga_append(&ga_text, c);
3576 for (;;)
3577 {
3578 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003579 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003580 || c == EOF || c == '\n')
3581 break;
3582 ga_append(&ga_text, c);
3583 }
3584
3585 /* save the character for repeating it */
3586 vim_free(prev_char);
3587 if (ga_text.ga_data != NULL)
3588 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3589 ga_text.ga_len - prev_len);
3590
Bram Moolenaar9271d052018-02-25 21:39:46 +01003591 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003592 {
3593 /* use all attributes from previous cell */
3594 }
3595 else if (c == '+' || c == '*')
3596 {
3597 int is_bg;
3598
3599 cell.width = c == '+' ? 1 : 2;
3600
3601 c = fgetc(fd);
3602 if (c == '&')
3603 {
3604 /* use same attr as previous cell */
3605 c = fgetc(fd);
3606 }
3607 else if (isdigit(c))
3608 {
3609 /* get the decimal attribute */
3610 attr = 0;
3611 while (isdigit(c))
3612 {
3613 attr = attr * 10 + (c - '0');
3614 c = fgetc(fd);
3615 }
3616 hl2vtermAttr(attr, &cell);
3617 }
3618 else
3619 dump_is_corrupt(&ga_text);
3620
3621 /* is_bg == 0: fg, is_bg == 1: bg */
3622 for (is_bg = 0; is_bg <= 1; ++is_bg)
3623 {
3624 if (c == '&')
3625 {
3626 /* use same color as previous cell */
3627 c = fgetc(fd);
3628 }
3629 else if (c == '#')
3630 {
3631 int red, green, blue, index = 0;
3632
3633 c = fgetc(fd);
3634 red = hex2nr(c);
3635 c = fgetc(fd);
3636 red = (red << 4) + hex2nr(c);
3637 c = fgetc(fd);
3638 green = hex2nr(c);
3639 c = fgetc(fd);
3640 green = (green << 4) + hex2nr(c);
3641 c = fgetc(fd);
3642 blue = hex2nr(c);
3643 c = fgetc(fd);
3644 blue = (blue << 4) + hex2nr(c);
3645 c = fgetc(fd);
3646 if (!isdigit(c))
3647 dump_is_corrupt(&ga_text);
3648 while (isdigit(c))
3649 {
3650 index = index * 10 + (c - '0');
3651 c = fgetc(fd);
3652 }
3653
3654 if (is_bg)
3655 {
3656 cell.bg.red = red;
3657 cell.bg.green = green;
3658 cell.bg.blue = blue;
3659 cell.bg.ansi_index = index;
3660 }
3661 else
3662 {
3663 cell.fg.red = red;
3664 cell.fg.green = green;
3665 cell.fg.blue = blue;
3666 cell.fg.ansi_index = index;
3667 }
3668 }
3669 else
3670 dump_is_corrupt(&ga_text);
3671 }
3672 }
3673 else
3674 dump_is_corrupt(&ga_text);
3675
3676 append_cell(&ga_cell, &cell);
3677 }
3678 else if (c == '@')
3679 {
3680 if (prev_char == NULL)
3681 dump_is_corrupt(&ga_text);
3682 else
3683 {
3684 int count = 0;
3685
3686 /* repeat previous character, get the count */
3687 for (;;)
3688 {
3689 c = fgetc(fd);
3690 if (!isdigit(c))
3691 break;
3692 count = count * 10 + (c - '0');
3693 }
3694
3695 while (count-- > 0)
3696 {
3697 ga_concat(&ga_text, prev_char);
3698 append_cell(&ga_cell, &cell);
3699 }
3700 }
3701 }
3702 else
3703 {
3704 dump_is_corrupt(&ga_text);
3705 c = fgetc(fd);
3706 }
3707 }
3708
3709 if (ga_text.ga_len > 0)
3710 {
3711 /* trailing characters after last NL */
3712 dump_is_corrupt(&ga_text);
3713 ga_append(&ga_text, NUL);
3714 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3715 ga_text.ga_len, FALSE);
3716 }
3717
3718 ga_clear(&ga_text);
3719 vim_free(prev_char);
3720
3721 return max_cells;
3722}
3723
3724/*
3725 * Common for "term_dumpdiff()" and "term_dumpload()".
3726 */
3727 static void
3728term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
3729{
3730 jobopt_T opt;
3731 buf_T *buf;
3732 char_u buf1[NUMBUFLEN];
3733 char_u buf2[NUMBUFLEN];
3734 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003735 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003736 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003737 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003738 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003739 char_u *textline = NULL;
3740
3741 /* First open the files. If this fails bail out. */
3742 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
3743 if (do_diff)
3744 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
3745 if (fname1 == NULL || (do_diff && fname2 == NULL))
3746 {
3747 EMSG(_(e_invarg));
3748 return;
3749 }
3750 fd1 = mch_fopen((char *)fname1, READBIN);
3751 if (fd1 == NULL)
3752 {
3753 EMSG2(_(e_notread), fname1);
3754 return;
3755 }
3756 if (do_diff)
3757 {
3758 fd2 = mch_fopen((char *)fname2, READBIN);
3759 if (fd2 == NULL)
3760 {
3761 fclose(fd1);
3762 EMSG2(_(e_notread), fname2);
3763 return;
3764 }
3765 }
3766
3767 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003768 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
3769 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
3770 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
3771 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
3772 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003773
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003774 if (opt.jo_term_name == NULL)
3775 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01003776 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003777
Bram Moolenaarb571c632018-03-21 22:27:59 +01003778 fname_tofree = alloc((int)len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003779 if (fname_tofree != NULL)
3780 {
3781 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
3782 opt.jo_term_name = fname_tofree;
3783 }
3784 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003785
Bram Moolenaar13568252018-03-16 20:46:58 +01003786 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003787 if (buf != NULL && buf->b_term != NULL)
3788 {
3789 int i;
3790 linenr_T bot_lnum;
3791 linenr_T lnum;
3792 term_T *term = buf->b_term;
3793 int width;
3794 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003795 VTermPos cursor_pos1;
3796 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003797
Bram Moolenaar52acb112018-03-18 19:20:22 +01003798 init_default_colors(term);
3799
Bram Moolenaard96ff162018-02-18 22:13:29 +01003800 rettv->vval.v_number = buf->b_fnum;
3801
3802 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01003803 width = read_dump_file(fd1, &cursor_pos1);
3804
3805 /* position the cursor */
3806 if (cursor_pos1.row >= 0)
3807 {
3808 curwin->w_cursor.lnum = cursor_pos1.row + 1;
3809 coladvance(cursor_pos1.col);
3810 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003811
3812 /* Delete the empty line that was in the empty buffer. */
3813 ml_delete(1, FALSE);
3814
3815 /* For term_dumpload() we are done here. */
3816 if (!do_diff)
3817 goto theend;
3818
3819 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
3820
3821 textline = alloc(width + 1);
3822 if (textline == NULL)
3823 goto theend;
3824 for (i = 0; i < width; ++i)
3825 textline[i] = '=';
3826 textline[width] = NUL;
3827 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3828 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3829 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3830 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3831
3832 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003833 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003834 if (width2 > width)
3835 {
3836 vim_free(textline);
3837 textline = alloc(width2 + 1);
3838 if (textline == NULL)
3839 goto theend;
3840 width = width2;
3841 textline[width] = NUL;
3842 }
3843 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
3844
3845 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
3846 {
3847 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
3848 {
3849 /* bottom part has fewer rows, fill with "-" */
3850 for (i = 0; i < width; ++i)
3851 textline[i] = '-';
3852 }
3853 else
3854 {
3855 char_u *line1;
3856 char_u *line2;
3857 char_u *p1;
3858 char_u *p2;
3859 int col;
3860 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3861 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
3862 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
3863 ->sb_cells;
3864
3865 /* Make a copy, getting the second line will invalidate it. */
3866 line1 = vim_strsave(ml_get(lnum));
3867 if (line1 == NULL)
3868 break;
3869 p1 = line1;
3870
3871 line2 = ml_get(lnum + bot_lnum);
3872 p2 = line2;
3873 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
3874 {
3875 int len1 = utfc_ptr2len(p1);
3876 int len2 = utfc_ptr2len(p2);
3877
3878 textline[col] = ' ';
3879 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01003880 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01003881 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01003882 else if (lnum == cursor_pos1.row + 1
3883 && col == cursor_pos1.col
3884 && (cursor_pos1.row != cursor_pos2.row
3885 || cursor_pos1.col != cursor_pos2.col))
3886 /* cursor in first but not in second */
3887 textline[col] = '>';
3888 else if (lnum == cursor_pos2.row + 1
3889 && col == cursor_pos2.col
3890 && (cursor_pos1.row != cursor_pos2.row
3891 || cursor_pos1.col != cursor_pos2.col))
3892 /* cursor in second but not in first */
3893 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01003894 else if (cellattr1 != NULL && cellattr2 != NULL)
3895 {
3896 if ((cellattr1 + col)->width
3897 != (cellattr2 + col)->width)
3898 textline[col] = 'w';
3899 else if (!same_color(&(cellattr1 + col)->fg,
3900 &(cellattr2 + col)->fg))
3901 textline[col] = 'f';
3902 else if (!same_color(&(cellattr1 + col)->bg,
3903 &(cellattr2 + col)->bg))
3904 textline[col] = 'b';
3905 else if (vtermAttr2hl((cellattr1 + col)->attrs)
3906 != vtermAttr2hl(((cellattr2 + col)->attrs)))
3907 textline[col] = 'a';
3908 }
3909 p1 += len1;
3910 p2 += len2;
3911 /* TODO: handle different width */
3912 }
3913 vim_free(line1);
3914
3915 while (col < width)
3916 {
3917 if (*p1 == NUL && *p2 == NUL)
3918 textline[col] = '?';
3919 else if (*p1 == NUL)
3920 {
3921 textline[col] = '+';
3922 p2 += utfc_ptr2len(p2);
3923 }
3924 else
3925 {
3926 textline[col] = '-';
3927 p1 += utfc_ptr2len(p1);
3928 }
3929 ++col;
3930 }
3931 }
3932 if (add_empty_scrollback(term, &term->tl_default_color,
3933 term->tl_top_diff_rows) == OK)
3934 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3935 ++bot_lnum;
3936 }
3937
3938 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
3939 {
3940 /* bottom part has more rows, fill with "+" */
3941 for (i = 0; i < width; ++i)
3942 textline[i] = '+';
3943 if (add_empty_scrollback(term, &term->tl_default_color,
3944 term->tl_top_diff_rows) == OK)
3945 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3946 ++lnum;
3947 ++bot_lnum;
3948 }
3949
3950 term->tl_cols = width;
3951 }
3952
3953theend:
3954 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01003955 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003956 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003957 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003958 fclose(fd2);
3959}
3960
3961/*
3962 * If the current buffer shows the output of term_dumpdiff(), swap the top and
3963 * bottom files.
3964 * Return FAIL when this is not possible.
3965 */
3966 int
3967term_swap_diff()
3968{
3969 term_T *term = curbuf->b_term;
3970 linenr_T line_count;
3971 linenr_T top_rows;
3972 linenr_T bot_rows;
3973 linenr_T bot_start;
3974 linenr_T lnum;
3975 char_u *p;
3976 sb_line_T *sb_line;
3977
3978 if (term == NULL
3979 || !term_is_finished(curbuf)
3980 || term->tl_top_diff_rows == 0
3981 || term->tl_scrollback.ga_len == 0)
3982 return FAIL;
3983
3984 line_count = curbuf->b_ml.ml_line_count;
3985 top_rows = term->tl_top_diff_rows;
3986 bot_rows = term->tl_bot_diff_rows;
3987 bot_start = line_count - bot_rows;
3988 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3989
3990 /* move lines from top to above the bottom part */
3991 for (lnum = 1; lnum <= top_rows; ++lnum)
3992 {
3993 p = vim_strsave(ml_get(1));
3994 if (p == NULL)
3995 return OK;
3996 ml_append(bot_start, p, 0, FALSE);
3997 ml_delete(1, FALSE);
3998 vim_free(p);
3999 }
4000
4001 /* move lines from bottom to the top */
4002 for (lnum = 1; lnum <= bot_rows; ++lnum)
4003 {
4004 p = vim_strsave(ml_get(bot_start + lnum));
4005 if (p == NULL)
4006 return OK;
4007 ml_delete(bot_start + lnum, FALSE);
4008 ml_append(lnum - 1, p, 0, FALSE);
4009 vim_free(p);
4010 }
4011
4012 if (top_rows == bot_rows)
4013 {
4014 /* rows counts are equal, can swap cell properties */
4015 for (lnum = 0; lnum < top_rows; ++lnum)
4016 {
4017 sb_line_T temp;
4018
4019 temp = *(sb_line + lnum);
4020 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
4021 *(sb_line + bot_start + lnum) = temp;
4022 }
4023 }
4024 else
4025 {
4026 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
4027 sb_line_T *temp = (sb_line_T *)alloc((int)size);
4028
4029 /* need to copy cell properties into temp memory */
4030 if (temp != NULL)
4031 {
4032 mch_memmove(temp, term->tl_scrollback.ga_data, size);
4033 mch_memmove(term->tl_scrollback.ga_data,
4034 temp + bot_start,
4035 sizeof(sb_line_T) * bot_rows);
4036 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
4037 temp + top_rows,
4038 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
4039 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
4040 + line_count - top_rows,
4041 temp,
4042 sizeof(sb_line_T) * top_rows);
4043 vim_free(temp);
4044 }
4045 }
4046
4047 term->tl_top_diff_rows = bot_rows;
4048 term->tl_bot_diff_rows = top_rows;
4049
4050 update_screen(NOT_VALID);
4051 return OK;
4052}
4053
4054/*
4055 * "term_dumpdiff(filename, filename, options)" function
4056 */
4057 void
4058f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
4059{
4060 term_load_dump(argvars, rettv, TRUE);
4061}
4062
4063/*
4064 * "term_dumpload(filename, options)" function
4065 */
4066 void
4067f_term_dumpload(typval_T *argvars, typval_T *rettv)
4068{
4069 term_load_dump(argvars, rettv, FALSE);
4070}
4071
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004072/*
4073 * "term_getaltscreen(buf)" function
4074 */
4075 void
4076f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
4077{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004078 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004079
4080 if (buf == NULL)
4081 return;
4082 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
4083}
4084
4085/*
4086 * "term_getattr(attr, name)" function
4087 */
4088 void
4089f_term_getattr(typval_T *argvars, typval_T *rettv)
4090{
4091 int attr;
4092 size_t i;
4093 char_u *name;
4094
4095 static struct {
4096 char *name;
4097 int attr;
4098 } attrs[] = {
4099 {"bold", HL_BOLD},
4100 {"italic", HL_ITALIC},
4101 {"underline", HL_UNDERLINE},
4102 {"strike", HL_STRIKETHROUGH},
4103 {"reverse", HL_INVERSE},
4104 };
4105
4106 attr = get_tv_number(&argvars[0]);
4107 name = get_tv_string_chk(&argvars[1]);
4108 if (name == NULL)
4109 return;
4110
4111 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
4112 if (STRCMP(name, attrs[i].name) == 0)
4113 {
4114 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
4115 break;
4116 }
4117}
4118
4119/*
4120 * "term_getcursor(buf)" function
4121 */
4122 void
4123f_term_getcursor(typval_T *argvars, typval_T *rettv)
4124{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004125 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004126 term_T *term;
4127 list_T *l;
4128 dict_T *d;
4129
4130 if (rettv_list_alloc(rettv) == FAIL)
4131 return;
4132 if (buf == NULL)
4133 return;
4134 term = buf->b_term;
4135
4136 l = rettv->vval.v_list;
4137 list_append_number(l, term->tl_cursor_pos.row + 1);
4138 list_append_number(l, term->tl_cursor_pos.col + 1);
4139
4140 d = dict_alloc();
4141 if (d != NULL)
4142 {
4143 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
4144 dict_add_nr_str(d, "blink", blink_state_is_inverted()
4145 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
4146 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
4147 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
4148 ? (char_u *)"" : term->tl_cursor_color);
4149 list_append_dict(l, d);
4150 }
4151}
4152
4153/*
4154 * "term_getjob(buf)" function
4155 */
4156 void
4157f_term_getjob(typval_T *argvars, typval_T *rettv)
4158{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004159 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004160
4161 rettv->v_type = VAR_JOB;
4162 rettv->vval.v_job = NULL;
4163 if (buf == NULL)
4164 return;
4165
4166 rettv->vval.v_job = buf->b_term->tl_job;
4167 if (rettv->vval.v_job != NULL)
4168 ++rettv->vval.v_job->jv_refcount;
4169}
4170
4171 static int
4172get_row_number(typval_T *tv, term_T *term)
4173{
4174 if (tv->v_type == VAR_STRING
4175 && tv->vval.v_string != NULL
4176 && STRCMP(tv->vval.v_string, ".") == 0)
4177 return term->tl_cursor_pos.row;
4178 return (int)get_tv_number(tv) - 1;
4179}
4180
4181/*
4182 * "term_getline(buf, row)" function
4183 */
4184 void
4185f_term_getline(typval_T *argvars, typval_T *rettv)
4186{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004187 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004188 term_T *term;
4189 int row;
4190
4191 rettv->v_type = VAR_STRING;
4192 if (buf == NULL)
4193 return;
4194 term = buf->b_term;
4195 row = get_row_number(&argvars[1], term);
4196
4197 if (term->tl_vterm == NULL)
4198 {
4199 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
4200
4201 /* vterm is finished, get the text from the buffer */
4202 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
4203 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
4204 }
4205 else
4206 {
4207 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
4208 VTermRect rect;
4209 int len;
4210 char_u *p;
4211
4212 if (row < 0 || row >= term->tl_rows)
4213 return;
4214 len = term->tl_cols * MB_MAXBYTES + 1;
4215 p = alloc(len);
4216 if (p == NULL)
4217 return;
4218 rettv->vval.v_string = p;
4219
4220 rect.start_col = 0;
4221 rect.end_col = term->tl_cols;
4222 rect.start_row = row;
4223 rect.end_row = row + 1;
4224 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
4225 }
4226}
4227
4228/*
4229 * "term_getscrolled(buf)" function
4230 */
4231 void
4232f_term_getscrolled(typval_T *argvars, typval_T *rettv)
4233{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004234 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004235
4236 if (buf == NULL)
4237 return;
4238 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
4239}
4240
4241/*
4242 * "term_getsize(buf)" function
4243 */
4244 void
4245f_term_getsize(typval_T *argvars, typval_T *rettv)
4246{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004247 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004248 list_T *l;
4249
4250 if (rettv_list_alloc(rettv) == FAIL)
4251 return;
4252 if (buf == NULL)
4253 return;
4254
4255 l = rettv->vval.v_list;
4256 list_append_number(l, buf->b_term->tl_rows);
4257 list_append_number(l, buf->b_term->tl_cols);
4258}
4259
4260/*
4261 * "term_getstatus(buf)" function
4262 */
4263 void
4264f_term_getstatus(typval_T *argvars, typval_T *rettv)
4265{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004266 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004267 term_T *term;
4268 char_u val[100];
4269
4270 rettv->v_type = VAR_STRING;
4271 if (buf == NULL)
4272 return;
4273 term = buf->b_term;
4274
4275 if (term_job_running(term))
4276 STRCPY(val, "running");
4277 else
4278 STRCPY(val, "finished");
4279 if (term->tl_normal_mode)
4280 STRCAT(val, ",normal");
4281 rettv->vval.v_string = vim_strsave(val);
4282}
4283
4284/*
4285 * "term_gettitle(buf)" function
4286 */
4287 void
4288f_term_gettitle(typval_T *argvars, typval_T *rettv)
4289{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004290 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004291
4292 rettv->v_type = VAR_STRING;
4293 if (buf == NULL)
4294 return;
4295
4296 if (buf->b_term->tl_title != NULL)
4297 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
4298}
4299
4300/*
4301 * "term_gettty(buf)" function
4302 */
4303 void
4304f_term_gettty(typval_T *argvars, typval_T *rettv)
4305{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004306 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004307 char_u *p;
4308 int num = 0;
4309
4310 rettv->v_type = VAR_STRING;
4311 if (buf == NULL)
4312 return;
4313 if (argvars[1].v_type != VAR_UNKNOWN)
4314 num = get_tv_number(&argvars[1]);
4315
4316 switch (num)
4317 {
4318 case 0:
4319 if (buf->b_term->tl_job != NULL)
4320 p = buf->b_term->tl_job->jv_tty_out;
4321 else
4322 p = buf->b_term->tl_tty_out;
4323 break;
4324 case 1:
4325 if (buf->b_term->tl_job != NULL)
4326 p = buf->b_term->tl_job->jv_tty_in;
4327 else
4328 p = buf->b_term->tl_tty_in;
4329 break;
4330 default:
4331 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
4332 return;
4333 }
4334 if (p != NULL)
4335 rettv->vval.v_string = vim_strsave(p);
4336}
4337
4338/*
4339 * "term_list()" function
4340 */
4341 void
4342f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
4343{
4344 term_T *tp;
4345 list_T *l;
4346
4347 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
4348 return;
4349
4350 l = rettv->vval.v_list;
4351 for (tp = first_term; tp != NULL; tp = tp->tl_next)
4352 if (tp != NULL && tp->tl_buffer != NULL)
4353 if (list_append_number(l,
4354 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
4355 return;
4356}
4357
4358/*
4359 * "term_scrape(buf, row)" function
4360 */
4361 void
4362f_term_scrape(typval_T *argvars, typval_T *rettv)
4363{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004364 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004365 VTermScreen *screen = NULL;
4366 VTermPos pos;
4367 list_T *l;
4368 term_T *term;
4369 char_u *p;
4370 sb_line_T *line;
4371
4372 if (rettv_list_alloc(rettv) == FAIL)
4373 return;
4374 if (buf == NULL)
4375 return;
4376 term = buf->b_term;
4377
4378 l = rettv->vval.v_list;
4379 pos.row = get_row_number(&argvars[1], term);
4380
4381 if (term->tl_vterm != NULL)
4382 {
4383 screen = vterm_obtain_screen(term->tl_vterm);
4384 p = NULL;
4385 line = NULL;
4386 }
4387 else
4388 {
4389 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4390
4391 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4392 return;
4393 p = ml_get_buf(buf, lnum + 1, FALSE);
4394 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4395 }
4396
4397 for (pos.col = 0; pos.col < term->tl_cols; )
4398 {
4399 dict_T *dcell;
4400 int width;
4401 VTermScreenCellAttrs attrs;
4402 VTermColor fg, bg;
4403 char_u rgb[8];
4404 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4405 int off = 0;
4406 int i;
4407
4408 if (screen == NULL)
4409 {
4410 cellattr_T *cellattr;
4411 int len;
4412
4413 /* vterm has finished, get the cell from scrollback */
4414 if (pos.col >= line->sb_cols)
4415 break;
4416 cellattr = line->sb_cells + pos.col;
4417 width = cellattr->width;
4418 attrs = cellattr->attrs;
4419 fg = cellattr->fg;
4420 bg = cellattr->bg;
4421 len = MB_PTR2LEN(p);
4422 mch_memmove(mbs, p, len);
4423 mbs[len] = NUL;
4424 p += len;
4425 }
4426 else
4427 {
4428 VTermScreenCell cell;
4429 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4430 break;
4431 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4432 {
4433 if (cell.chars[i] == 0)
4434 break;
4435 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4436 }
4437 mbs[off] = NUL;
4438 width = cell.width;
4439 attrs = cell.attrs;
4440 fg = cell.fg;
4441 bg = cell.bg;
4442 }
4443 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004444 if (dcell == NULL)
4445 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004446 list_append_dict(l, dcell);
4447
4448 dict_add_nr_str(dcell, "chars", 0, mbs);
4449
4450 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4451 fg.red, fg.green, fg.blue);
4452 dict_add_nr_str(dcell, "fg", 0, rgb);
4453 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4454 bg.red, bg.green, bg.blue);
4455 dict_add_nr_str(dcell, "bg", 0, rgb);
4456
4457 dict_add_nr_str(dcell, "attr",
4458 cell2attr(attrs, fg, bg), NULL);
4459 dict_add_nr_str(dcell, "width", width, NULL);
4460
4461 ++pos.col;
4462 if (width == 2)
4463 ++pos.col;
4464 }
4465}
4466
4467/*
4468 * "term_sendkeys(buf, keys)" function
4469 */
4470 void
4471f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4472{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004473 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004474 char_u *msg;
4475 term_T *term;
4476
4477 rettv->v_type = VAR_UNKNOWN;
4478 if (buf == NULL)
4479 return;
4480
4481 msg = get_tv_string_chk(&argvars[1]);
4482 if (msg == NULL)
4483 return;
4484 term = buf->b_term;
4485 if (term->tl_vterm == NULL)
4486 return;
4487
4488 while (*msg != NUL)
4489 {
4490 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004491 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004492 }
4493}
4494
4495/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004496 * "term_setrestore(buf, command)" function
4497 */
4498 void
4499f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4500{
4501#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004502 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004503 term_T *term;
4504 char_u *cmd;
4505
4506 if (buf == NULL)
4507 return;
4508 term = buf->b_term;
4509 vim_free(term->tl_command);
4510 cmd = get_tv_string_chk(&argvars[1]);
4511 if (cmd != NULL)
4512 term->tl_command = vim_strsave(cmd);
4513 else
4514 term->tl_command = NULL;
4515#endif
4516}
4517
4518/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004519 * "term_setkill(buf, how)" function
4520 */
4521 void
4522f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4523{
4524 buf_T *buf = term_get_buf(argvars, "term_setkill()");
4525 term_T *term;
4526 char_u *how;
4527
4528 if (buf == NULL)
4529 return;
4530 term = buf->b_term;
4531 vim_free(term->tl_kill);
4532 how = get_tv_string_chk(&argvars[1]);
4533 if (how != NULL)
4534 term->tl_kill = vim_strsave(how);
4535 else
4536 term->tl_kill = NULL;
4537}
4538
4539/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004540 * "term_start(command, options)" function
4541 */
4542 void
4543f_term_start(typval_T *argvars, typval_T *rettv)
4544{
4545 jobopt_T opt;
4546 buf_T *buf;
4547
4548 init_job_options(&opt);
4549 if (argvars[1].v_type != VAR_UNKNOWN
4550 && get_job_options(&argvars[1], &opt,
4551 JO_TIMEOUT_ALL + JO_STOPONEXIT
4552 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
4553 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
4554 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
4555 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004556 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004557 + JO2_NORESTORE + JO2_TERM_KILL) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004558 return;
4559
Bram Moolenaar13568252018-03-16 20:46:58 +01004560 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004561
4562 if (buf != NULL && buf->b_term != NULL)
4563 rettv->vval.v_number = buf->b_fnum;
4564}
4565
4566/*
4567 * "term_wait" function
4568 */
4569 void
4570f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
4571{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004572 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004573
4574 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004575 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004576 if (buf->b_term->tl_job == NULL)
4577 {
4578 ch_log(NULL, "term_wait(): no job to wait for");
4579 return;
4580 }
4581 if (buf->b_term->tl_job->jv_channel == NULL)
4582 /* channel is closed, nothing to do */
4583 return;
4584
4585 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01004586 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004587 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
4588 {
4589 /* The job is dead, keep reading channel I/O until the channel is
4590 * closed. buf->b_term may become NULL if the terminal was closed while
4591 * waiting. */
4592 ch_log(NULL, "term_wait(): waiting for channel to close");
4593 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
4594 {
4595 mch_check_messages();
4596 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01004597 if (!buf_valid(buf))
4598 /* If the terminal is closed when the channel is closed the
4599 * buffer disappears. */
4600 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004601 ui_delay(10L, FALSE);
4602 }
4603 mch_check_messages();
4604 parse_queued_messages();
4605 }
4606 else
4607 {
4608 long wait = 10L;
4609
4610 mch_check_messages();
4611 parse_queued_messages();
4612
4613 /* Wait for some time for any channel I/O. */
4614 if (argvars[1].v_type != VAR_UNKNOWN)
4615 wait = get_tv_number(&argvars[1]);
4616 ui_delay(wait, TRUE);
4617 mch_check_messages();
4618
4619 /* Flushing messages on channels is hopefully sufficient.
4620 * TODO: is there a better way? */
4621 parse_queued_messages();
4622 }
4623}
4624
4625/*
4626 * Called when a channel has sent all the lines to a terminal.
4627 * Send a CTRL-D to mark the end of the text.
4628 */
4629 void
4630term_send_eof(channel_T *ch)
4631{
4632 term_T *term;
4633
4634 for (term = first_term; term != NULL; term = term->tl_next)
4635 if (term->tl_job == ch->ch_job)
4636 {
4637 if (term->tl_eof_chars != NULL)
4638 {
4639 channel_send(ch, PART_IN, term->tl_eof_chars,
4640 (int)STRLEN(term->tl_eof_chars), NULL);
4641 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
4642 }
4643# ifdef WIN3264
4644 else
4645 /* Default: CTRL-D */
4646 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
4647# endif
4648 }
4649}
4650
4651# if defined(WIN3264) || defined(PROTO)
4652
4653/**************************************
4654 * 2. MS-Windows implementation.
4655 */
4656
4657# ifndef PROTO
4658
4659#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
4660#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01004661#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004662
4663void* (*winpty_config_new)(UINT64, void*);
4664void* (*winpty_open)(void*, void*);
4665void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
4666BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
4667void (*winpty_config_set_mouse_mode)(void*, int);
4668void (*winpty_config_set_initial_size)(void*, int, int);
4669LPCWSTR (*winpty_conin_name)(void*);
4670LPCWSTR (*winpty_conout_name)(void*);
4671LPCWSTR (*winpty_conerr_name)(void*);
4672void (*winpty_free)(void*);
4673void (*winpty_config_free)(void*);
4674void (*winpty_spawn_config_free)(void*);
4675void (*winpty_error_free)(void*);
4676LPCWSTR (*winpty_error_msg)(void*);
4677BOOL (*winpty_set_size)(void*, int, int, void*);
4678HANDLE (*winpty_agent_process)(void*);
4679
4680#define WINPTY_DLL "winpty.dll"
4681
4682static HINSTANCE hWinPtyDLL = NULL;
4683# endif
4684
4685 static int
4686dyn_winpty_init(int verbose)
4687{
4688 int i;
4689 static struct
4690 {
4691 char *name;
4692 FARPROC *ptr;
4693 } winpty_entry[] =
4694 {
4695 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
4696 {"winpty_config_free", (FARPROC*)&winpty_config_free},
4697 {"winpty_config_new", (FARPROC*)&winpty_config_new},
4698 {"winpty_config_set_mouse_mode",
4699 (FARPROC*)&winpty_config_set_mouse_mode},
4700 {"winpty_config_set_initial_size",
4701 (FARPROC*)&winpty_config_set_initial_size},
4702 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
4703 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
4704 {"winpty_error_free", (FARPROC*)&winpty_error_free},
4705 {"winpty_free", (FARPROC*)&winpty_free},
4706 {"winpty_open", (FARPROC*)&winpty_open},
4707 {"winpty_spawn", (FARPROC*)&winpty_spawn},
4708 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
4709 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
4710 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
4711 {"winpty_set_size", (FARPROC*)&winpty_set_size},
4712 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
4713 {NULL, NULL}
4714 };
4715
4716 /* No need to initialize twice. */
4717 if (hWinPtyDLL)
4718 return OK;
4719 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
4720 * winpty.dll. */
4721 if (*p_winptydll != NUL)
4722 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
4723 if (!hWinPtyDLL)
4724 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
4725 if (!hWinPtyDLL)
4726 {
4727 if (verbose)
4728 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
4729 : (char_u *)WINPTY_DLL);
4730 return FAIL;
4731 }
4732 for (i = 0; winpty_entry[i].name != NULL
4733 && winpty_entry[i].ptr != NULL; ++i)
4734 {
4735 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
4736 winpty_entry[i].name)) == NULL)
4737 {
4738 if (verbose)
4739 EMSG2(_(e_loadfunc), winpty_entry[i].name);
4740 return FAIL;
4741 }
4742 }
4743
4744 return OK;
4745}
4746
4747/*
4748 * Create a new terminal of "rows" by "cols" cells.
4749 * Store a reference in "term".
4750 * Return OK or FAIL.
4751 */
4752 static int
4753term_and_job_init(
4754 term_T *term,
4755 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01004756 char **argv UNUSED,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004757 jobopt_T *opt)
4758{
4759 WCHAR *cmd_wchar = NULL;
4760 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004761 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004762 channel_T *channel = NULL;
4763 job_T *job = NULL;
4764 DWORD error;
4765 HANDLE jo = NULL;
4766 HANDLE child_process_handle;
4767 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01004768 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004769 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004770 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004771 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004772
4773 if (dyn_winpty_init(TRUE) == FAIL)
4774 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004775 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
4776 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004777
4778 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004779 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004780 cmd = argvar->vval.v_string;
4781 }
4782 else if (argvar->v_type == VAR_LIST)
4783 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004784 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004785 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004786 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004787 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004788 if (cmd == NULL || *cmd == NUL)
4789 {
4790 EMSG(_(e_invarg));
4791 goto failed;
4792 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004793
4794 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004795 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004796 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004797 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004798 if (opt->jo_cwd != NULL)
4799 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004800
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004801 win32_build_env(opt->jo_env, &ga_env, TRUE);
4802 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004803
4804 job = job_alloc();
4805 if (job == NULL)
4806 goto failed;
4807
4808 channel = add_channel();
4809 if (channel == NULL)
4810 goto failed;
4811
4812 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
4813 if (term->tl_winpty_config == NULL)
4814 goto failed;
4815
4816 winpty_config_set_mouse_mode(term->tl_winpty_config,
4817 WINPTY_MOUSE_MODE_FORCE);
4818 winpty_config_set_initial_size(term->tl_winpty_config,
4819 term->tl_cols, term->tl_rows);
4820 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
4821 if (term->tl_winpty == NULL)
4822 goto failed;
4823
4824 spawn_config = winpty_spawn_config_new(
4825 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
4826 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
4827 NULL,
4828 cmd_wchar,
4829 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004830 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004831 &winpty_err);
4832 if (spawn_config == NULL)
4833 goto failed;
4834
4835 channel = add_channel();
4836 if (channel == NULL)
4837 goto failed;
4838
4839 job = job_alloc();
4840 if (job == NULL)
4841 goto failed;
4842
4843 if (opt->jo_set & JO_IN_BUF)
4844 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
4845
4846 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
4847 &child_thread_handle, &error, &winpty_err))
4848 goto failed;
4849
4850 channel_set_pipes(channel,
4851 (sock_T)CreateFileW(
4852 winpty_conin_name(term->tl_winpty),
4853 GENERIC_WRITE, 0, NULL,
4854 OPEN_EXISTING, 0, NULL),
4855 (sock_T)CreateFileW(
4856 winpty_conout_name(term->tl_winpty),
4857 GENERIC_READ, 0, NULL,
4858 OPEN_EXISTING, 0, NULL),
4859 (sock_T)CreateFileW(
4860 winpty_conerr_name(term->tl_winpty),
4861 GENERIC_READ, 0, NULL,
4862 OPEN_EXISTING, 0, NULL));
4863
4864 /* Write lines with CR instead of NL. */
4865 channel->ch_write_text_mode = TRUE;
4866
4867 jo = CreateJobObject(NULL, NULL);
4868 if (jo == NULL)
4869 goto failed;
4870
4871 if (!AssignProcessToJobObject(jo, child_process_handle))
4872 {
4873 /* Failed, switch the way to terminate process with TerminateProcess. */
4874 CloseHandle(jo);
4875 jo = NULL;
4876 }
4877
4878 winpty_spawn_config_free(spawn_config);
4879 vim_free(cmd_wchar);
4880 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004881 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004882
4883 create_vterm(term, term->tl_rows, term->tl_cols);
4884
4885 channel_set_job(channel, job, opt);
4886 job_set_options(job, opt);
4887
4888 job->jv_channel = channel;
4889 job->jv_proc_info.hProcess = child_process_handle;
4890 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
4891 job->jv_job_object = jo;
4892 job->jv_status = JOB_STARTED;
4893 job->jv_tty_in = utf16_to_enc(
4894 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
4895 job->jv_tty_out = utf16_to_enc(
4896 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
4897 ++job->jv_refcount;
4898 term->tl_job = job;
4899
4900 return OK;
4901
4902failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004903 ga_clear(&ga_cmd);
4904 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004905 vim_free(cmd_wchar);
4906 vim_free(cwd_wchar);
4907 if (spawn_config != NULL)
4908 winpty_spawn_config_free(spawn_config);
4909 if (channel != NULL)
4910 channel_clear(channel);
4911 if (job != NULL)
4912 {
4913 job->jv_channel = NULL;
4914 job_cleanup(job);
4915 }
4916 term->tl_job = NULL;
4917 if (jo != NULL)
4918 CloseHandle(jo);
4919 if (term->tl_winpty != NULL)
4920 winpty_free(term->tl_winpty);
4921 term->tl_winpty = NULL;
4922 if (term->tl_winpty_config != NULL)
4923 winpty_config_free(term->tl_winpty_config);
4924 term->tl_winpty_config = NULL;
4925 if (winpty_err != NULL)
4926 {
4927 char_u *msg = utf16_to_enc(
4928 (short_u *)winpty_error_msg(winpty_err), NULL);
4929
4930 EMSG(msg);
4931 winpty_error_free(winpty_err);
4932 }
4933 return FAIL;
4934}
4935
4936 static int
4937create_pty_only(term_T *term, jobopt_T *options)
4938{
4939 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
4940 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
4941 char in_name[80], out_name[80];
4942 channel_T *channel = NULL;
4943
4944 create_vterm(term, term->tl_rows, term->tl_cols);
4945
4946 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
4947 GetCurrentProcessId(),
4948 curbuf->b_fnum);
4949 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
4950 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4951 PIPE_UNLIMITED_INSTANCES,
4952 0, 0, NMPWAIT_NOWAIT, NULL);
4953 if (hPipeIn == INVALID_HANDLE_VALUE)
4954 goto failed;
4955
4956 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
4957 GetCurrentProcessId(),
4958 curbuf->b_fnum);
4959 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
4960 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4961 PIPE_UNLIMITED_INSTANCES,
4962 0, 0, 0, NULL);
4963 if (hPipeOut == INVALID_HANDLE_VALUE)
4964 goto failed;
4965
4966 ConnectNamedPipe(hPipeIn, NULL);
4967 ConnectNamedPipe(hPipeOut, NULL);
4968
4969 term->tl_job = job_alloc();
4970 if (term->tl_job == NULL)
4971 goto failed;
4972 ++term->tl_job->jv_refcount;
4973
4974 /* behave like the job is already finished */
4975 term->tl_job->jv_status = JOB_FINISHED;
4976
4977 channel = add_channel();
4978 if (channel == NULL)
4979 goto failed;
4980 term->tl_job->jv_channel = channel;
4981 channel->ch_keep_open = TRUE;
4982 channel->ch_named_pipe = TRUE;
4983
4984 channel_set_pipes(channel,
4985 (sock_T)hPipeIn,
4986 (sock_T)hPipeOut,
4987 (sock_T)hPipeOut);
4988 channel_set_job(channel, term->tl_job, options);
4989 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
4990 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
4991
4992 return OK;
4993
4994failed:
4995 if (hPipeIn != NULL)
4996 CloseHandle(hPipeIn);
4997 if (hPipeOut != NULL)
4998 CloseHandle(hPipeOut);
4999 return FAIL;
5000}
5001
5002/*
5003 * Free the terminal emulator part of "term".
5004 */
5005 static void
5006term_free_vterm(term_T *term)
5007{
5008 if (term->tl_winpty != NULL)
5009 winpty_free(term->tl_winpty);
5010 term->tl_winpty = NULL;
5011 if (term->tl_winpty_config != NULL)
5012 winpty_config_free(term->tl_winpty_config);
5013 term->tl_winpty_config = NULL;
5014 if (term->tl_vterm != NULL)
5015 vterm_free(term->tl_vterm);
5016 term->tl_vterm = NULL;
5017}
5018
5019/*
5020 * Request size to terminal.
5021 */
5022 static void
5023term_report_winsize(term_T *term, int rows, int cols)
5024{
5025 if (term->tl_winpty)
5026 winpty_set_size(term->tl_winpty, cols, rows, NULL);
5027}
5028
5029 int
5030terminal_enabled(void)
5031{
5032 return dyn_winpty_init(FALSE) == OK;
5033}
5034
5035# else
5036
5037/**************************************
5038 * 3. Unix-like implementation.
5039 */
5040
5041/*
5042 * Create a new terminal of "rows" by "cols" cells.
5043 * Start job for "cmd".
5044 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01005045 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005046 * Return OK or FAIL.
5047 */
5048 static int
5049term_and_job_init(
5050 term_T *term,
5051 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01005052 char **argv,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005053 jobopt_T *opt)
5054{
5055 create_vterm(term, term->tl_rows, term->tl_cols);
5056
Bram Moolenaar13568252018-03-16 20:46:58 +01005057 /* This may change a string in "argvar". */
5058 term->tl_job = job_start(argvar, argv, opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005059 if (term->tl_job != NULL)
5060 ++term->tl_job->jv_refcount;
5061
5062 return term->tl_job != NULL
5063 && term->tl_job->jv_channel != NULL
5064 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
5065}
5066
5067 static int
5068create_pty_only(term_T *term, jobopt_T *opt)
5069{
5070 create_vterm(term, term->tl_rows, term->tl_cols);
5071
5072 term->tl_job = job_alloc();
5073 if (term->tl_job == NULL)
5074 return FAIL;
5075 ++term->tl_job->jv_refcount;
5076
5077 /* behave like the job is already finished */
5078 term->tl_job->jv_status = JOB_FINISHED;
5079
5080 return mch_create_pty_channel(term->tl_job, opt);
5081}
5082
5083/*
5084 * Free the terminal emulator part of "term".
5085 */
5086 static void
5087term_free_vterm(term_T *term)
5088{
5089 if (term->tl_vterm != NULL)
5090 vterm_free(term->tl_vterm);
5091 term->tl_vterm = NULL;
5092}
5093
5094/*
5095 * Request size to terminal.
5096 */
5097 static void
5098term_report_winsize(term_T *term, int rows, int cols)
5099{
5100 /* Use an ioctl() to report the new window size to the job. */
5101 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
5102 {
5103 int fd = -1;
5104 int part;
5105
5106 for (part = PART_OUT; part < PART_COUNT; ++part)
5107 {
5108 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
5109 if (isatty(fd))
5110 break;
5111 }
5112 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
5113 mch_signal_job(term->tl_job, (char_u *)"winch");
5114 }
5115}
5116
5117# endif
5118
5119#endif /* FEAT_TERMINAL */