blob: 547897fcfac49e1beedbd9619684635172c9aa6c [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 Moolenaar4d8bac82018-03-09 21:33:34 +010041 * - Add a flag to kill the job when Vim is exiting. Useful when it's showing
42 * a logfile. Or send keys there to make it quit: "exit\r" for a shell.
Bram Moolenaar46359e12017-11-29 22:33:38 +010043 * - When using 'termguicolors' still use the 16 ANSI colors as-is. Helps for
Bram Moolenaar4d8bac82018-03-09 21:33:34 +010044 * - Adding WinBar to terminal window doesn't display, text isn't shifted down.
Bram Moolenaar46359e12017-11-29 22:33:38 +010045 * a job that uses 16 colors while Vim is using > 256.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020046 * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito
47 * Higashi, 2017 Sep 19)
Bram Moolenaarede35bb2018-01-26 20:05:18 +010048 * - Trigger TerminalOpen event? #2422 patch in #2484
Bram Moolenaar3a497e12017-09-30 20:40:27 +020049 * - after resizing windows overlap. (Boris Staletic, #2164)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020050 * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file()
51 * is disabled.
Bram Moolenaarf2bd8ef2018-03-04 18:08:14 +010052 * - if the job in the terminal does not support the mouse, we can use the
53 * mouse in the Terminal window for copy/paste and scrolling.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020054 * - cursor blinks in terminal on widows with a timer. (xtal8, #2142)
Bram Moolenaarba6febd2017-10-30 21:56:23 +010055 * - When closing gvim with an active terminal buffer, the dialog suggests
56 * saving the buffer. Should say something else. (Manas Thakur, #2215)
57 * Also: #2223
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 Moolenaar46359e12017-11-29 22:33:38 +010062 * - implement term_setsize()
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020063 * - add test for giving error for invalid 'termsize' value.
64 * - support minimal size when 'termsize' is "rows*cols".
65 * - support minimal size when 'termsize' is empty?
66 * - GUI: when using tabs, focus in terminal, click on tab does not work.
67 * - GUI: when 'confirm' is set and trying to exit Vim, dialog offers to save
68 * changes to "!shell".
69 * (justrajdeep, 2017 Aug 22)
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020070 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020071 * - For the GUI fill termios with default values, perhaps like pangoterm:
72 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020073 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
74 * conversions.
75 * - In the GUI use a terminal emulator for :!cmd. Make the height the same as
76 * the window and position it higher up when it gets filled, so it looks like
77 * the text scrolls up.
78 * - Copy text in the vterm to the Vim buffer once in a while, so that
79 * completion works.
80 * - add an optional limit for the scrollback size. When reaching it remove
81 * 10% at the start.
82 */
83
84#include "vim.h"
85
86#if defined(FEAT_TERMINAL) || defined(PROTO)
87
88#ifndef MIN
89# define MIN(x,y) ((x) < (y) ? (x) : (y))
90#endif
91#ifndef MAX
92# define MAX(x,y) ((x) > (y) ? (x) : (y))
93#endif
94
95#include "libvterm/include/vterm.h"
96
97/* This is VTermScreenCell without the characters, thus much smaller. */
98typedef struct {
99 VTermScreenCellAttrs attrs;
100 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +0100101 VTermColor fg;
102 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200103} cellattr_T;
104
105typedef struct sb_line_S {
106 int sb_cols; /* can differ per line */
107 cellattr_T *sb_cells; /* allocated */
108 cellattr_T sb_fill_attr; /* for short line */
109} sb_line_T;
110
111/* typedef term_T in structs.h */
112struct terminal_S {
113 term_T *tl_next;
114
115 VTerm *tl_vterm;
116 job_T *tl_job;
117 buf_T *tl_buffer;
118
119 /* Set when setting the size of a vterm, reset after redrawing. */
120 int tl_vterm_size_changed;
121
122 /* used when tl_job is NULL and only a pty was created */
123 int tl_tty_fd;
124 char_u *tl_tty_in;
125 char_u *tl_tty_out;
126
127 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
128 int tl_channel_closed;
129 int tl_finish; /* 'c' for ++close, 'o' for ++open */
130 char_u *tl_opencmd;
131 char_u *tl_eof_chars;
132
133#ifdef WIN3264
134 void *tl_winpty_config;
135 void *tl_winpty;
136#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100137#if defined(FEAT_SESSION)
138 char_u *tl_command;
139#endif
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 */
188static int term_and_job_init(term_T *term, typval_T *argvar, jobopt_T *opt);
189static 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);
192
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100193/* The character that we know (or assume) that the terminal expects for the
194 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200195static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200196
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100197/* "Terminal" highlight group colors. */
198static int term_default_cterm_fg = -1;
199static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200200
Bram Moolenaard317b382018-02-08 22:33:31 +0100201/* Store the last set and the desired cursor properties, so that we only update
202 * them when needed. Doing it unnecessary may result in flicker. */
203static char_u *last_set_cursor_color = (char_u *)"";
204static char_u *desired_cursor_color = (char_u *)"";
205static int last_set_cursor_shape = -1;
206static int desired_cursor_shape = -1;
207static int last_set_cursor_blink = -1;
208static int desired_cursor_blink = -1;
209
210
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200211/**************************************
212 * 1. Generic code for all systems.
213 */
214
215/*
216 * Determine the terminal size from 'termsize' and the current window.
217 * Assumes term->tl_rows and term->tl_cols are zero.
218 */
219 static void
220set_term_and_win_size(term_T *term)
221{
222 if (*curwin->w_p_tms != NUL)
223 {
224 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
225
226 term->tl_rows = atoi((char *)curwin->w_p_tms);
227 term->tl_cols = atoi((char *)p);
228 }
229 if (term->tl_rows == 0)
230 term->tl_rows = curwin->w_height;
231 else
232 {
233 win_setheight_win(term->tl_rows, curwin);
234 term->tl_rows_fixed = TRUE;
235 }
236 if (term->tl_cols == 0)
237 term->tl_cols = curwin->w_width;
238 else
239 {
240 win_setwidth_win(term->tl_cols, curwin);
241 term->tl_cols_fixed = TRUE;
242 }
243}
244
245/*
246 * Initialize job options for a terminal job.
247 * Caller may overrule some of them.
248 */
249 static void
250init_job_options(jobopt_T *opt)
251{
252 clear_job_options(opt);
253
254 opt->jo_mode = MODE_RAW;
255 opt->jo_out_mode = MODE_RAW;
256 opt->jo_err_mode = MODE_RAW;
257 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
258}
259
260/*
261 * Set job options mandatory for a terminal job.
262 */
263 static void
264setup_job_options(jobopt_T *opt, int rows, int cols)
265{
266 if (!(opt->jo_set & JO_OUT_IO))
267 {
268 /* Connect stdout to the terminal. */
269 opt->jo_io[PART_OUT] = JIO_BUFFER;
270 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
271 opt->jo_modifiable[PART_OUT] = 0;
272 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
273 }
274
275 if (!(opt->jo_set & JO_ERR_IO))
276 {
277 /* Connect stderr to the terminal. */
278 opt->jo_io[PART_ERR] = JIO_BUFFER;
279 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
280 opt->jo_modifiable[PART_ERR] = 0;
281 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
282 }
283
284 opt->jo_pty = TRUE;
285 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
286 opt->jo_term_rows = rows;
287 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
288 opt->jo_term_cols = cols;
289}
290
291/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100292 * Close a terminal buffer (and its window). Used when creating the terminal
293 * fails.
294 */
295 static void
296term_close_buffer(buf_T *buf, buf_T *old_curbuf)
297{
298 free_terminal(buf);
299 if (old_curbuf != NULL)
300 {
301 --curbuf->b_nwindows;
302 curbuf = old_curbuf;
303 curwin->w_buffer = curbuf;
304 ++curbuf->b_nwindows;
305 }
306
307 /* Wiping out the buffer will also close the window and call
308 * free_terminal(). */
309 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
310}
311
312/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200313 * Start a terminal window and return its buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +0100314 * When "without_job" is TRUE only create the buffer, b_term and open the
315 * window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200316 * Returns NULL when failed.
317 */
318 static buf_T *
Bram Moolenaard96ff162018-02-18 22:13:29 +0100319term_start(typval_T *argvar, jobopt_T *opt, int without_job, int forceit)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200320{
321 exarg_T split_ea;
322 win_T *old_curwin = curwin;
323 term_T *term;
324 buf_T *old_curbuf = NULL;
325 int res;
326 buf_T *newbuf;
327
328 if (check_restricted() || check_secure())
329 return NULL;
330
331 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
332 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
333 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
334 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
335 {
336 EMSG(_(e_invarg));
337 return NULL;
338 }
339
340 term = (term_T *)alloc_clear(sizeof(term_T));
341 if (term == NULL)
342 return NULL;
343 term->tl_dirty_row_end = MAX_ROW;
344 term->tl_cursor_visible = TRUE;
345 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
346 term->tl_finish = opt->jo_term_finish;
347 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
348
349 vim_memset(&split_ea, 0, sizeof(split_ea));
350 if (opt->jo_curwin)
351 {
352 /* Create a new buffer in the current window. */
353 if (!can_abandon(curbuf, forceit))
354 {
355 no_write_message();
356 vim_free(term);
357 return NULL;
358 }
359 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
360 ECMD_HIDE + (forceit ? ECMD_FORCEIT : 0), curwin) == FAIL)
361 {
362 vim_free(term);
363 return NULL;
364 }
365 }
366 else if (opt->jo_hidden)
367 {
368 buf_T *buf;
369
370 /* Create a new buffer without a window. Make it the current buffer for
371 * a moment to be able to do the initialisations. */
372 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
373 BLN_NEW | BLN_LISTED);
374 if (buf == NULL || ml_open(buf) == FAIL)
375 {
376 vim_free(term);
377 return NULL;
378 }
379 old_curbuf = curbuf;
380 --curbuf->b_nwindows;
381 curbuf = buf;
382 curwin->w_buffer = buf;
383 ++curbuf->b_nwindows;
384 }
385 else
386 {
387 /* Open a new window or tab. */
388 split_ea.cmdidx = CMD_new;
389 split_ea.cmd = (char_u *)"new";
390 split_ea.arg = (char_u *)"";
391 if (opt->jo_term_rows > 0 && !(cmdmod.split & WSP_VERT))
392 {
393 split_ea.line2 = opt->jo_term_rows;
394 split_ea.addr_count = 1;
395 }
396 if (opt->jo_term_cols > 0 && (cmdmod.split & WSP_VERT))
397 {
398 split_ea.line2 = opt->jo_term_cols;
399 split_ea.addr_count = 1;
400 }
401
402 ex_splitview(&split_ea);
403 if (curwin == old_curwin)
404 {
405 /* split failed */
406 vim_free(term);
407 return NULL;
408 }
409 }
410 term->tl_buffer = curbuf;
411 curbuf->b_term = term;
412
413 if (!opt->jo_hidden)
414 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100415 /* Only one size was taken care of with :new, do the other one. With
416 * "curwin" both need to be done. */
417 if (opt->jo_term_rows > 0 && (opt->jo_curwin
418 || (cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200419 win_setheight(opt->jo_term_rows);
Bram Moolenaarda650582018-02-20 15:51:40 +0100420 if (opt->jo_term_cols > 0 && (opt->jo_curwin
421 || !(cmdmod.split & WSP_VERT)))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200422 win_setwidth(opt->jo_term_cols);
423 }
424
425 /* Link the new terminal in the list of active terminals. */
426 term->tl_next = first_term;
427 first_term = term;
428
429 if (opt->jo_term_name != NULL)
430 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
431 else
432 {
433 int i;
434 size_t len;
435 char_u *cmd, *p;
436
437 if (argvar->v_type == VAR_STRING)
438 {
439 cmd = argvar->vval.v_string;
440 if (cmd == NULL)
441 cmd = (char_u *)"";
442 else if (STRCMP(cmd, "NONE") == 0)
443 cmd = (char_u *)"pty";
444 }
445 else if (argvar->v_type != VAR_LIST
446 || argvar->vval.v_list == NULL
447 || argvar->vval.v_list->lv_len < 1
448 || (cmd = get_tv_string_chk(
449 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
450 cmd = (char_u*)"";
451
452 len = STRLEN(cmd) + 10;
453 p = alloc((int)len);
454
455 for (i = 0; p != NULL; ++i)
456 {
457 /* Prepend a ! to the command name to avoid the buffer name equals
458 * the executable, otherwise ":w!" would overwrite it. */
459 if (i == 0)
460 vim_snprintf((char *)p, len, "!%s", cmd);
461 else
462 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
463 if (buflist_findname(p) == NULL)
464 {
465 vim_free(curbuf->b_ffname);
466 curbuf->b_ffname = p;
467 break;
468 }
469 }
470 }
471 curbuf->b_fname = curbuf->b_ffname;
472
473 if (opt->jo_term_opencmd != NULL)
474 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
475
476 if (opt->jo_eof_chars != NULL)
477 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
478
479 set_string_option_direct((char_u *)"buftype", -1,
480 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
481
482 /* Mark the buffer as not modifiable. It can only be made modifiable after
483 * the job finished. */
484 curbuf->b_p_ma = FALSE;
485
486 set_term_and_win_size(term);
487 setup_job_options(opt, term->tl_rows, term->tl_cols);
488
Bram Moolenaard96ff162018-02-18 22:13:29 +0100489 if (without_job)
490 return curbuf;
491
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100492#if defined(FEAT_SESSION)
493 /* Remember the command for the session file. */
494 if (opt->jo_term_norestore)
495 {
496 term->tl_command = vim_strsave((char_u *)"NONE");
497 }
498 else if (argvar->v_type == VAR_STRING)
499 {
500 char_u *cmd = argvar->vval.v_string;
501
502 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
503 term->tl_command = vim_strsave(cmd);
504 }
505 else if (argvar->v_type == VAR_LIST
506 && argvar->vval.v_list != NULL
507 && argvar->vval.v_list->lv_len > 0)
508 {
509 garray_T ga;
510 listitem_T *item;
511
512 ga_init2(&ga, 1, 100);
513 for (item = argvar->vval.v_list->lv_first;
514 item != NULL; item = item->li_next)
515 {
516 char_u *s = get_tv_string_chk(&item->li_tv);
517 char_u *p;
518
519 if (s == NULL)
520 break;
521 p = vim_strsave_fnameescape(s, FALSE);
522 if (p == NULL)
523 break;
524 ga_concat(&ga, p);
525 vim_free(p);
526 ga_append(&ga, ' ');
527 }
528 if (item == NULL)
529 {
530 ga_append(&ga, NUL);
531 term->tl_command = ga.ga_data;
532 }
533 else
534 ga_clear(&ga);
535 }
536#endif
537
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200538 /* System dependent: setup the vterm and maybe start the job in it. */
539 if (argvar->v_type == VAR_STRING
540 && argvar->vval.v_string != NULL
541 && STRCMP(argvar->vval.v_string, "NONE") == 0)
542 res = create_pty_only(term, opt);
543 else
544 res = term_and_job_init(term, argvar, opt);
545
546 newbuf = curbuf;
547 if (res == OK)
548 {
549 /* Get and remember the size we ended up with. Update the pty. */
550 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
551 term_report_winsize(term, term->tl_rows, term->tl_cols);
552
553 /* Make sure we don't get stuck on sending keys to the job, it leads to
554 * a deadlock if the job is waiting for Vim to read. */
555 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
556
Bram Moolenaarab5e7c32018-02-13 14:07:18 +0100557 if (!opt->jo_hidden)
558 {
559 ++curbuf->b_locked;
560 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
561 --curbuf->b_locked;
562 }
Bram Moolenaar8b21de32017-09-22 11:13:52 +0200563
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200564 if (old_curbuf != NULL)
565 {
566 --curbuf->b_nwindows;
567 curbuf = old_curbuf;
568 curwin->w_buffer = curbuf;
569 ++curbuf->b_nwindows;
570 }
571 }
572 else
573 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100574 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200575 return NULL;
576 }
577 return newbuf;
578}
579
580/*
581 * ":terminal": open a terminal window and execute a job in it.
582 */
583 void
584ex_terminal(exarg_T *eap)
585{
586 typval_T argvar[2];
587 jobopt_T opt;
588 char_u *cmd;
589 char_u *tofree = NULL;
590
591 init_job_options(&opt);
592
593 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100594 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200595 {
596 char_u *p, *ep;
597
598 cmd += 2;
599 p = skiptowhite(cmd);
600 ep = vim_strchr(cmd, '=');
601 if (ep != NULL && ep < p)
602 p = ep;
603
604 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
605 opt.jo_term_finish = 'c';
606 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
607 opt.jo_term_finish = 'o';
608 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
609 opt.jo_curwin = 1;
610 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
611 opt.jo_hidden = 1;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100612 else if ((int)(p - cmd) == 9 && STRNICMP(cmd, "norestore", 9) == 0)
613 opt.jo_term_norestore = 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200614 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
615 && ep != NULL && isdigit(ep[1]))
616 {
617 opt.jo_set2 |= JO2_TERM_ROWS;
618 opt.jo_term_rows = atoi((char *)ep + 1);
619 p = skiptowhite(cmd);
620 }
621 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
622 && ep != NULL && isdigit(ep[1]))
623 {
624 opt.jo_set2 |= JO2_TERM_COLS;
625 opt.jo_term_cols = atoi((char *)ep + 1);
626 p = skiptowhite(cmd);
627 }
628 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
629 && ep != NULL)
630 {
631 char_u *buf = NULL;
632 char_u *keys;
633
634 p = skiptowhite(cmd);
635 *p = NUL;
636 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
637 opt.jo_set2 |= JO2_EOF_CHARS;
638 opt.jo_eof_chars = vim_strsave(keys);
639 vim_free(buf);
640 *p = ' ';
641 }
642 else
643 {
644 if (*p)
645 *p = NUL;
646 EMSG2(_("E181: Invalid attribute: %s"), cmd);
647 return;
648 }
649 cmd = skipwhite(p);
650 }
651 if (*cmd == NUL)
652 /* Make a copy of 'shell', an autocommand may change the option. */
653 tofree = cmd = vim_strsave(p_sh);
654
655 if (eap->addr_count > 0)
656 {
657 /* Write lines from current buffer to the job. */
658 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
659 opt.jo_io[PART_IN] = JIO_BUFFER;
660 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
661 opt.jo_in_top = eap->line1;
662 opt.jo_in_bot = eap->line2;
663 }
664
665 argvar[0].v_type = VAR_STRING;
666 argvar[0].vval.v_string = cmd;
667 argvar[1].v_type = VAR_UNKNOWN;
Bram Moolenaard96ff162018-02-18 22:13:29 +0100668 term_start(argvar, &opt, FALSE, eap->forceit);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200669 vim_free(tofree);
670 vim_free(opt.jo_eof_chars);
671}
672
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100673#if defined(FEAT_SESSION) || defined(PROTO)
674/*
675 * Write a :terminal command to the session file to restore the terminal in
676 * window "wp".
677 * Return FAIL if writing fails.
678 */
679 int
680term_write_session(FILE *fd, win_T *wp)
681{
682 term_T *term = wp->w_buffer->b_term;
683
684 /* Create the terminal and run the command. This is not without
685 * risk, but let's assume the user only creates a session when this
686 * will be OK. */
687 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
688 term->tl_cols, term->tl_rows) < 0)
689 return FAIL;
690 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
691 return FAIL;
692
693 return put_eol(fd);
694}
695
696/*
697 * Return TRUE if "buf" has a terminal that should be restored.
698 */
699 int
700term_should_restore(buf_T *buf)
701{
702 term_T *term = buf->b_term;
703
704 return term != NULL && (term->tl_command == NULL
705 || STRCMP(term->tl_command, "NONE") != 0);
706}
707#endif
708
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200709/*
710 * Free the scrollback buffer for "term".
711 */
712 static void
713free_scrollback(term_T *term)
714{
715 int i;
716
717 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
718 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
719 ga_clear(&term->tl_scrollback);
720}
721
722/*
723 * Free a terminal and everything it refers to.
724 * Kills the job if there is one.
725 * Called when wiping out a buffer.
726 */
727 void
728free_terminal(buf_T *buf)
729{
730 term_T *term = buf->b_term;
731 term_T *tp;
732
733 if (term == NULL)
734 return;
735 if (first_term == term)
736 first_term = term->tl_next;
737 else
738 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
739 if (tp->tl_next == term)
740 {
741 tp->tl_next = term->tl_next;
742 break;
743 }
744
745 if (term->tl_job != NULL)
746 {
747 if (term->tl_job->jv_status != JOB_ENDED
748 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100749 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200750 job_stop(term->tl_job, NULL, "kill");
751 job_unref(term->tl_job);
752 }
753
754 free_scrollback(term);
755
756 term_free_vterm(term);
757 vim_free(term->tl_title);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100758#ifdef FEAT_SESSION
759 vim_free(term->tl_command);
760#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200761 vim_free(term->tl_status_text);
762 vim_free(term->tl_opencmd);
763 vim_free(term->tl_eof_chars);
Bram Moolenaard317b382018-02-08 22:33:31 +0100764 if (desired_cursor_color == term->tl_cursor_color)
765 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200766 vim_free(term->tl_cursor_color);
767 vim_free(term);
768 buf->b_term = NULL;
769 if (in_terminal_loop == term)
770 in_terminal_loop = NULL;
771}
772
773/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100774 * Get the part that is connected to the tty. Normally this is PART_IN, but
775 * when writing buffer lines to the job it can be another. This makes it
776 * possible to do "1,5term vim -".
777 */
778 static ch_part_T
779get_tty_part(term_T *term)
780{
781#ifdef UNIX
782 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
783 int i;
784
785 for (i = 0; i < 3; ++i)
786 {
787 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
788
789 if (isatty(fd))
790 return parts[i];
791 }
792#endif
793 return PART_IN;
794}
795
796/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200797 * Write job output "msg[len]" to the vterm.
798 */
799 static void
800term_write_job_output(term_T *term, char_u *msg, size_t len)
801{
802 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100803 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200804
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100805 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200806
Bram Moolenaarb50773c2018-01-30 22:31:19 +0100807 /* flush vterm buffer when vterm responded to control sequence */
808 if (prevlen != vterm_output_get_buffer_current(vterm))
809 {
810 char buf[KEY_BUF_LEN];
811 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
812
813 if (curlen > 0)
814 channel_send(term->tl_job->jv_channel, get_tty_part(term),
815 (char_u *)buf, (int)curlen, NULL);
816 }
817
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200818 /* this invokes the damage callbacks */
819 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
820}
821
822 static void
823update_cursor(term_T *term, int redraw)
824{
825 if (term->tl_normal_mode)
826 return;
827 setcursor();
828 if (redraw)
829 {
830 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
831 cursor_on();
832 out_flush();
833#ifdef FEAT_GUI
834 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100835 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200836 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +0100837 gui_mch_flush();
838 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200839#endif
840 }
841}
842
843/*
844 * Invoked when "msg" output from a job was received. Write it to the terminal
845 * of "buffer".
846 */
847 void
848write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
849{
850 size_t len = STRLEN(msg);
851 term_T *term = buffer->b_term;
852
853 if (term->tl_vterm == NULL)
854 {
855 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
856 return;
857 }
858 ch_log(channel, "writing %d bytes to terminal", (int)len);
859 term_write_job_output(term, msg, len);
860
861 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
862 * contents, thus no screen update is needed. */
863 if (!term->tl_normal_mode)
864 {
865 /* TODO: only update once in a while. */
866 ch_log(term->tl_job->jv_channel, "updating screen");
867 if (buffer == curbuf)
868 {
869 update_screen(0);
870 update_cursor(term, TRUE);
871 }
872 else
873 redraw_after_callback(TRUE);
874 }
875}
876
877/*
878 * Send a mouse position and click to the vterm
879 */
880 static int
881term_send_mouse(VTerm *vterm, int button, int pressed)
882{
883 VTermModifier mod = VTERM_MOD_NONE;
884
885 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200886 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100887 if (button != 0)
888 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200889 return TRUE;
890}
891
892/*
893 * Convert typed key "c" into bytes to send to the job.
894 * Return the number of bytes in "buf".
895 */
896 static int
897term_convert_key(term_T *term, int c, char *buf)
898{
899 VTerm *vterm = term->tl_vterm;
900 VTermKey key = VTERM_KEY_NONE;
901 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +0100902 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200903
904 switch (c)
905 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100906 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
907
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200908 /* don't use VTERM_KEY_BACKSPACE, it always
909 * becomes 0x7f DEL */
910 case K_BS: c = term_backspace_char; break;
911
912 case ESC: key = VTERM_KEY_ESCAPE; break;
913 case K_DEL: key = VTERM_KEY_DEL; break;
914 case K_DOWN: key = VTERM_KEY_DOWN; break;
915 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
916 key = VTERM_KEY_DOWN; break;
917 case K_END: key = VTERM_KEY_END; break;
918 case K_S_END: mod = VTERM_MOD_SHIFT;
919 key = VTERM_KEY_END; break;
920 case K_C_END: mod = VTERM_MOD_CTRL;
921 key = VTERM_KEY_END; break;
922 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
923 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
924 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
925 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
926 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
927 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
928 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
929 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
930 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
931 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
932 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
933 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
934 case K_HOME: key = VTERM_KEY_HOME; break;
935 case K_S_HOME: mod = VTERM_MOD_SHIFT;
936 key = VTERM_KEY_HOME; break;
937 case K_C_HOME: mod = VTERM_MOD_CTRL;
938 key = VTERM_KEY_HOME; break;
939 case K_INS: key = VTERM_KEY_INS; break;
940 case K_K0: key = VTERM_KEY_KP_0; break;
941 case K_K1: key = VTERM_KEY_KP_1; break;
942 case K_K2: key = VTERM_KEY_KP_2; break;
943 case K_K3: key = VTERM_KEY_KP_3; break;
944 case K_K4: key = VTERM_KEY_KP_4; break;
945 case K_K5: key = VTERM_KEY_KP_5; break;
946 case K_K6: key = VTERM_KEY_KP_6; break;
947 case K_K7: key = VTERM_KEY_KP_7; break;
948 case K_K8: key = VTERM_KEY_KP_8; break;
949 case K_K9: key = VTERM_KEY_KP_9; break;
950 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
951 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
952 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
953 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
954 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
955 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
956 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
957 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
958 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
959 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
960 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
961 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
962 case K_LEFT: key = VTERM_KEY_LEFT; break;
963 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
964 key = VTERM_KEY_LEFT; break;
965 case K_C_LEFT: mod = VTERM_MOD_CTRL;
966 key = VTERM_KEY_LEFT; break;
967 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
968 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
969 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
970 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
971 key = VTERM_KEY_RIGHT; break;
972 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
973 key = VTERM_KEY_RIGHT; break;
974 case K_UP: key = VTERM_KEY_UP; break;
975 case K_S_UP: mod = VTERM_MOD_SHIFT;
976 key = VTERM_KEY_UP; break;
977 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +0100978 case K_S_TAB: mod = VTERM_MOD_SHIFT;
979 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200980
Bram Moolenaara42ad572017-11-16 13:08:04 +0100981 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
982 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200983 case K_MOUSELEFT: /* TODO */ return 0;
984 case K_MOUSERIGHT: /* TODO */ return 0;
985
986 case K_LEFTMOUSE:
Bram Moolenaara42ad572017-11-16 13:08:04 +0100987 case K_LEFTMOUSE_NM: other = term_send_mouse(vterm, 1, 1); break;
988 case K_LEFTDRAG: other = term_send_mouse(vterm, 1, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200989 case K_LEFTRELEASE:
Bram Moolenaara42ad572017-11-16 13:08:04 +0100990 case K_LEFTRELEASE_NM: other = term_send_mouse(vterm, 1, 0); break;
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100991 case K_MOUSEMOVE: other = term_send_mouse(vterm, 0, 0); break;
Bram Moolenaara42ad572017-11-16 13:08:04 +0100992 case K_MIDDLEMOUSE: other = term_send_mouse(vterm, 2, 1); break;
993 case K_MIDDLEDRAG: other = term_send_mouse(vterm, 2, 1); break;
994 case K_MIDDLERELEASE: other = term_send_mouse(vterm, 2, 0); break;
995 case K_RIGHTMOUSE: other = term_send_mouse(vterm, 3, 1); break;
996 case K_RIGHTDRAG: other = term_send_mouse(vterm, 3, 1); break;
997 case K_RIGHTRELEASE: other = term_send_mouse(vterm, 3, 0); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200998 case K_X1MOUSE: /* TODO */ return 0;
999 case K_X1DRAG: /* TODO */ return 0;
1000 case K_X1RELEASE: /* TODO */ return 0;
1001 case K_X2MOUSE: /* TODO */ return 0;
1002 case K_X2DRAG: /* TODO */ return 0;
1003 case K_X2RELEASE: /* TODO */ return 0;
1004
1005 case K_IGNORE: return 0;
1006 case K_NOP: return 0;
1007 case K_UNDO: return 0;
1008 case K_HELP: return 0;
1009 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1010 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1011 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1012 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1013 case K_SELECT: return 0;
1014#ifdef FEAT_GUI
1015 case K_VER_SCROLLBAR: return 0;
1016 case K_HOR_SCROLLBAR: return 0;
1017#endif
1018#ifdef FEAT_GUI_TABLINE
1019 case K_TABLINE: return 0;
1020 case K_TABMENU: return 0;
1021#endif
1022#ifdef FEAT_NETBEANS_INTG
1023 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1024#endif
1025#ifdef FEAT_DND
1026 case K_DROP: return 0;
1027#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001028 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001029 case K_PS: vterm_keyboard_start_paste(vterm);
1030 other = TRUE;
1031 break;
1032 case K_PE: vterm_keyboard_end_paste(vterm);
1033 other = TRUE;
1034 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001035 }
1036
1037 /*
1038 * Convert special keys to vterm keys:
1039 * - Write keys to vterm: vterm_keyboard_key()
1040 * - Write output to channel.
1041 * TODO: use mod_mask
1042 */
1043 if (key != VTERM_KEY_NONE)
1044 /* Special key, let vterm convert it. */
1045 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001046 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001047 /* Normal character, let vterm convert it. */
1048 vterm_keyboard_unichar(vterm, c, mod);
1049
1050 /* Read back the converted escape sequence. */
1051 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1052}
1053
1054/*
1055 * Return TRUE if the job for "term" is still running.
1056 */
1057 int
1058term_job_running(term_T *term)
1059{
1060 /* Also consider the job finished when the channel is closed, to avoid a
1061 * race condition when updating the title. */
1062 return term != NULL
1063 && term->tl_job != NULL
1064 && channel_is_open(term->tl_job->jv_channel)
1065 && (term->tl_job->jv_status == JOB_STARTED
1066 || term->tl_job->jv_channel->ch_keep_open);
1067}
1068
1069/*
1070 * Return TRUE if "term" has an active channel and used ":term NONE".
1071 */
1072 int
1073term_none_open(term_T *term)
1074{
1075 /* Also consider the job finished when the channel is closed, to avoid a
1076 * race condition when updating the title. */
1077 return term != NULL
1078 && term->tl_job != NULL
1079 && channel_is_open(term->tl_job->jv_channel)
1080 && term->tl_job->jv_channel->ch_keep_open;
1081}
1082
1083/*
1084 * Add the last line of the scrollback buffer to the buffer in the window.
1085 */
1086 static void
1087add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1088{
1089 buf_T *buf = term->tl_buffer;
1090 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1091 linenr_T lnum = buf->b_ml.ml_line_count;
1092
1093#ifdef WIN3264
1094 if (!enc_utf8 && enc_codepage > 0)
1095 {
1096 WCHAR *ret = NULL;
1097 int length = 0;
1098
1099 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1100 &ret, &length);
1101 if (ret != NULL)
1102 {
1103 WideCharToMultiByte_alloc(enc_codepage, 0,
1104 ret, length, (char **)&text, &len, 0, 0);
1105 vim_free(ret);
1106 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1107 vim_free(text);
1108 }
1109 }
1110 else
1111#endif
1112 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1113 if (empty)
1114 {
1115 /* Delete the empty line that was in the empty buffer. */
1116 curbuf = buf;
1117 ml_delete(1, FALSE);
1118 curbuf = curwin->w_buffer;
1119 }
1120}
1121
1122 static void
1123cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1124{
1125 attr->width = cell->width;
1126 attr->attrs = cell->attrs;
1127 attr->fg = cell->fg;
1128 attr->bg = cell->bg;
1129}
1130
1131 static int
1132equal_celattr(cellattr_T *a, cellattr_T *b)
1133{
1134 /* Comparing the colors should be sufficient. */
1135 return a->fg.red == b->fg.red
1136 && a->fg.green == b->fg.green
1137 && a->fg.blue == b->fg.blue
1138 && a->bg.red == b->bg.red
1139 && a->bg.green == b->bg.green
1140 && a->bg.blue == b->bg.blue;
1141}
1142
Bram Moolenaard96ff162018-02-18 22:13:29 +01001143/*
1144 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1145 * line at this position. Otherwise at the end.
1146 */
1147 static int
1148add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1149{
1150 if (ga_grow(&term->tl_scrollback, 1) == OK)
1151 {
1152 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1153 + term->tl_scrollback.ga_len;
1154
1155 if (lnum > 0)
1156 {
1157 int i;
1158
1159 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1160 {
1161 *line = *(line - 1);
1162 --line;
1163 }
1164 }
1165 line->sb_cols = 0;
1166 line->sb_cells = NULL;
1167 line->sb_fill_attr = *fill_attr;
1168 ++term->tl_scrollback.ga_len;
1169 return OK;
1170 }
1171 return FALSE;
1172}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001173
1174/*
1175 * Add the current lines of the terminal to scrollback and to the buffer.
1176 * Called after the job has ended and when switching to Terminal-Normal mode.
1177 */
1178 static void
1179move_terminal_to_buffer(term_T *term)
1180{
1181 win_T *wp;
1182 int len;
1183 int lines_skipped = 0;
1184 VTermPos pos;
1185 VTermScreenCell cell;
1186 cellattr_T fill_attr, new_fill_attr;
1187 cellattr_T *p;
1188 VTermScreen *screen;
1189
1190 if (term->tl_vterm == NULL)
1191 return;
1192 screen = vterm_obtain_screen(term->tl_vterm);
1193 fill_attr = new_fill_attr = term->tl_default_color;
1194
1195 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1196 {
1197 len = 0;
1198 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1199 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1200 && cell.chars[0] != NUL)
1201 {
1202 len = pos.col + 1;
1203 new_fill_attr = term->tl_default_color;
1204 }
1205 else
1206 /* Assume the last attr is the filler attr. */
1207 cell2cellattr(&cell, &new_fill_attr);
1208
1209 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1210 ++lines_skipped;
1211 else
1212 {
1213 while (lines_skipped > 0)
1214 {
1215 /* Line was skipped, add an empty line. */
1216 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001217 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001218 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001219 }
1220
1221 if (len == 0)
1222 p = NULL;
1223 else
1224 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1225 if ((p != NULL || len == 0)
1226 && ga_grow(&term->tl_scrollback, 1) == OK)
1227 {
1228 garray_T ga;
1229 int width;
1230 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1231 + term->tl_scrollback.ga_len;
1232
1233 ga_init2(&ga, 1, 100);
1234 for (pos.col = 0; pos.col < len; pos.col += width)
1235 {
1236 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1237 {
1238 width = 1;
1239 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1240 if (ga_grow(&ga, 1) == OK)
1241 ga.ga_len += utf_char2bytes(' ',
1242 (char_u *)ga.ga_data + ga.ga_len);
1243 }
1244 else
1245 {
1246 width = cell.width;
1247
1248 cell2cellattr(&cell, &p[pos.col]);
1249
1250 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1251 {
1252 int i;
1253 int c;
1254
1255 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1256 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1257 (char_u *)ga.ga_data + ga.ga_len);
1258 }
1259 }
1260 }
1261 line->sb_cols = len;
1262 line->sb_cells = p;
1263 line->sb_fill_attr = new_fill_attr;
1264 fill_attr = new_fill_attr;
1265 ++term->tl_scrollback.ga_len;
1266
1267 if (ga_grow(&ga, 1) == FAIL)
1268 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1269 else
1270 {
1271 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1272 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1273 }
1274 ga_clear(&ga);
1275 }
1276 else
1277 vim_free(p);
1278 }
1279 }
1280
1281 /* Obtain the current background color. */
1282 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1283 &term->tl_default_color.fg, &term->tl_default_color.bg);
1284
1285 FOR_ALL_WINDOWS(wp)
1286 {
1287 if (wp->w_buffer == term->tl_buffer)
1288 {
1289 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1290 wp->w_cursor.col = 0;
1291 wp->w_valid = 0;
1292 if (wp->w_cursor.lnum >= wp->w_height)
1293 {
1294 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1295
1296 if (wp->w_topline < min_topline)
1297 wp->w_topline = min_topline;
1298 }
1299 redraw_win_later(wp, NOT_VALID);
1300 }
1301 }
1302}
1303
1304 static void
1305set_terminal_mode(term_T *term, int normal_mode)
1306{
1307 term->tl_normal_mode = normal_mode;
Bram Moolenaard23a8232018-02-10 18:45:26 +01001308 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001309 if (term->tl_buffer == curbuf)
1310 maketitle();
1311}
1312
1313/*
1314 * Called after the job if finished and Terminal mode is not active:
1315 * Move the vterm contents into the scrollback buffer and free the vterm.
1316 */
1317 static void
1318cleanup_vterm(term_T *term)
1319{
1320 if (term->tl_finish != 'c')
1321 move_terminal_to_buffer(term);
1322 term_free_vterm(term);
1323 set_terminal_mode(term, FALSE);
1324}
1325
1326/*
1327 * Switch from Terminal-Job mode to Terminal-Normal mode.
1328 * Suspends updating the terminal window.
1329 */
1330 static void
1331term_enter_normal_mode(void)
1332{
1333 term_T *term = curbuf->b_term;
1334
1335 /* Append the current terminal contents to the buffer. */
1336 move_terminal_to_buffer(term);
1337
1338 set_terminal_mode(term, TRUE);
1339
1340 /* Move the window cursor to the position of the cursor in the
1341 * terminal. */
1342 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1343 + term->tl_cursor_pos.row + 1;
1344 check_cursor();
1345 coladvance(term->tl_cursor_pos.col);
1346
1347 /* Display the same lines as in the terminal. */
1348 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1349}
1350
1351/*
1352 * Returns TRUE if the current window contains a terminal and we are in
1353 * Terminal-Normal mode.
1354 */
1355 int
1356term_in_normal_mode(void)
1357{
1358 term_T *term = curbuf->b_term;
1359
1360 return term != NULL && term->tl_normal_mode;
1361}
1362
1363/*
1364 * Switch from Terminal-Normal mode to Terminal-Job mode.
1365 * Restores updating the terminal window.
1366 */
1367 void
1368term_enter_job_mode()
1369{
1370 term_T *term = curbuf->b_term;
1371 sb_line_T *line;
1372 garray_T *gap;
1373
1374 /* Remove the terminal contents from the scrollback and the buffer. */
1375 gap = &term->tl_scrollback;
1376 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1377 && gap->ga_len > 0)
1378 {
1379 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1380 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1381 vim_free(line->sb_cells);
1382 --gap->ga_len;
1383 }
1384 check_cursor();
1385
1386 set_terminal_mode(term, FALSE);
1387
1388 if (term->tl_channel_closed)
1389 cleanup_vterm(term);
1390 redraw_buf_and_status_later(curbuf, NOT_VALID);
1391}
1392
1393/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001394 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001395 * Note: while waiting a terminal may be closed and freed if the channel is
1396 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001397 */
1398 static int
1399term_vgetc()
1400{
1401 int c;
1402 int save_State = State;
1403
1404 State = TERMINAL;
1405 got_int = FALSE;
1406#ifdef WIN3264
1407 ctrl_break_was_pressed = FALSE;
1408#endif
1409 c = vgetc();
1410 got_int = FALSE;
1411 State = save_State;
1412 return c;
1413}
1414
1415/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001416 * Send keys to terminal.
1417 * Return FAIL when the key needs to be handled in Normal mode.
1418 * Return OK when the key was dropped or sent to the terminal.
1419 */
1420 int
1421send_keys_to_term(term_T *term, int c, int typed)
1422{
1423 char msg[KEY_BUF_LEN];
1424 size_t len;
1425 static int mouse_was_outside = FALSE;
1426 int dragging_outside = FALSE;
1427
1428 /* Catch keys that need to be handled as in Normal mode. */
1429 switch (c)
1430 {
1431 case NUL:
1432 case K_ZERO:
1433 if (typed)
1434 stuffcharReadbuff(c);
1435 return FAIL;
1436
1437 case K_IGNORE:
1438 return FAIL;
1439
1440 case K_LEFTDRAG:
1441 case K_MIDDLEDRAG:
1442 case K_RIGHTDRAG:
1443 case K_X1DRAG:
1444 case K_X2DRAG:
1445 dragging_outside = mouse_was_outside;
1446 /* FALLTHROUGH */
1447 case K_LEFTMOUSE:
1448 case K_LEFTMOUSE_NM:
1449 case K_LEFTRELEASE:
1450 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001451 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001452 case K_MIDDLEMOUSE:
1453 case K_MIDDLERELEASE:
1454 case K_RIGHTMOUSE:
1455 case K_RIGHTRELEASE:
1456 case K_X1MOUSE:
1457 case K_X1RELEASE:
1458 case K_X2MOUSE:
1459 case K_X2RELEASE:
1460
1461 case K_MOUSEUP:
1462 case K_MOUSEDOWN:
1463 case K_MOUSELEFT:
1464 case K_MOUSERIGHT:
1465 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001466 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001467 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001468 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001469 || dragging_outside)
1470 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01001471 /* click or scroll outside the current window or on status line
1472 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001473 if (typed)
1474 {
1475 stuffcharReadbuff(c);
1476 mouse_was_outside = TRUE;
1477 }
1478 return FAIL;
1479 }
1480 }
1481 if (typed)
1482 mouse_was_outside = FALSE;
1483
1484 /* Convert the typed key to a sequence of bytes for the job. */
1485 len = term_convert_key(term, c, msg);
1486 if (len > 0)
1487 /* TODO: if FAIL is returned, stop? */
1488 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1489 (char_u *)msg, (int)len, NULL);
1490
1491 return OK;
1492}
1493
1494 static void
1495position_cursor(win_T *wp, VTermPos *pos)
1496{
1497 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1498 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1499 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1500}
1501
1502/*
1503 * Handle CTRL-W "": send register contents to the job.
1504 */
1505 static void
1506term_paste_register(int prev_c UNUSED)
1507{
1508 int c;
1509 list_T *l;
1510 listitem_T *item;
1511 long reglen = 0;
1512 int type;
1513
1514#ifdef FEAT_CMDL_INFO
1515 if (add_to_showcmd(prev_c))
1516 if (add_to_showcmd('"'))
1517 out_flush();
1518#endif
1519 c = term_vgetc();
1520#ifdef FEAT_CMDL_INFO
1521 clear_showcmd();
1522#endif
1523 if (!term_use_loop())
1524 /* job finished while waiting for a character */
1525 return;
1526
1527 /* CTRL-W "= prompt for expression to evaluate. */
1528 if (c == '=' && get_expr_register() != '=')
1529 return;
1530 if (!term_use_loop())
1531 /* job finished while waiting for a character */
1532 return;
1533
1534 l = (list_T *)get_reg_contents(c, GREG_LIST);
1535 if (l != NULL)
1536 {
1537 type = get_reg_type(c, &reglen);
1538 for (item = l->lv_first; item != NULL; item = item->li_next)
1539 {
1540 char_u *s = get_tv_string(&item->li_tv);
1541#ifdef WIN3264
1542 char_u *tmp = s;
1543
1544 if (!enc_utf8 && enc_codepage > 0)
1545 {
1546 WCHAR *ret = NULL;
1547 int length = 0;
1548
1549 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1550 (int)STRLEN(s), &ret, &length);
1551 if (ret != NULL)
1552 {
1553 WideCharToMultiByte_alloc(CP_UTF8, 0,
1554 ret, length, (char **)&s, &length, 0, 0);
1555 vim_free(ret);
1556 }
1557 }
1558#endif
1559 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1560 s, (int)STRLEN(s), NULL);
1561#ifdef WIN3264
1562 if (tmp != s)
1563 vim_free(s);
1564#endif
1565
1566 if (item->li_next != NULL || type == MLINE)
1567 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1568 (char_u *)"\r", 1, NULL);
1569 }
1570 list_free(l);
1571 }
1572}
1573
1574#if defined(FEAT_GUI) || defined(PROTO)
1575/*
1576 * Return TRUE when the cursor of the terminal should be displayed.
1577 */
1578 int
1579terminal_is_active()
1580{
1581 return in_terminal_loop != NULL;
1582}
1583
1584 cursorentry_T *
1585term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1586{
1587 term_T *term = in_terminal_loop;
1588 static cursorentry_T entry;
1589
1590 vim_memset(&entry, 0, sizeof(entry));
1591 entry.shape = entry.mshape =
1592 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1593 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1594 SHAPE_BLOCK;
1595 entry.percentage = 20;
1596 if (term->tl_cursor_blink)
1597 {
1598 entry.blinkwait = 700;
1599 entry.blinkon = 400;
1600 entry.blinkoff = 250;
1601 }
1602 *fg = gui.back_pixel;
1603 if (term->tl_cursor_color == NULL)
1604 *bg = gui.norm_pixel;
1605 else
1606 *bg = color_name2handle(term->tl_cursor_color);
1607 entry.name = "n";
1608 entry.used_for = SHAPE_CURSOR;
1609
1610 return &entry;
1611}
1612#endif
1613
Bram Moolenaard317b382018-02-08 22:33:31 +01001614 static void
1615may_output_cursor_props(void)
1616{
1617 if (STRCMP(last_set_cursor_color, desired_cursor_color) != 0
1618 || last_set_cursor_shape != desired_cursor_shape
1619 || last_set_cursor_blink != desired_cursor_blink)
1620 {
1621 last_set_cursor_color = desired_cursor_color;
1622 last_set_cursor_shape = desired_cursor_shape;
1623 last_set_cursor_blink = desired_cursor_blink;
1624 term_cursor_color(desired_cursor_color);
1625 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
1626 /* this will restore the initial cursor style, if possible */
1627 ui_cursor_shape_forced(TRUE);
1628 else
1629 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
1630 }
1631}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001632
Bram Moolenaard317b382018-02-08 22:33:31 +01001633/*
1634 * Set the cursor color and shape, if not last set to these.
1635 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001636 static void
1637may_set_cursor_props(term_T *term)
1638{
1639#ifdef FEAT_GUI
1640 /* For the GUI the cursor properties are obtained with
1641 * term_get_cursor_shape(). */
1642 if (gui.in_use)
1643 return;
1644#endif
1645 if (in_terminal_loop == term)
1646 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001647 if (term->tl_cursor_color != NULL)
Bram Moolenaard317b382018-02-08 22:33:31 +01001648 desired_cursor_color = term->tl_cursor_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001649 else
Bram Moolenaard317b382018-02-08 22:33:31 +01001650 desired_cursor_color = (char_u *)"";
1651 desired_cursor_shape = term->tl_cursor_shape;
1652 desired_cursor_blink = term->tl_cursor_blink;
1653 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001654 }
1655}
1656
Bram Moolenaard317b382018-02-08 22:33:31 +01001657/*
1658 * Reset the desired cursor properties and restore them when needed.
1659 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001660 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01001661prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001662{
1663#ifdef FEAT_GUI
1664 if (gui.in_use)
1665 return;
1666#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001667 desired_cursor_color = (char_u *)"";
1668 desired_cursor_shape = -1;
1669 desired_cursor_blink = -1;
1670 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001671}
1672
1673/*
1674 * Returns TRUE if the current window contains a terminal and we are sending
1675 * keys to the job.
1676 */
1677 int
1678term_use_loop(void)
1679{
1680 term_T *term = curbuf->b_term;
1681
1682 return term != NULL
1683 && !term->tl_normal_mode
1684 && term->tl_vterm != NULL
1685 && term_job_running(term);
1686}
1687
1688/*
1689 * Wait for input and send it to the job.
1690 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1691 * when there is no more typahead.
1692 * Return when the start of a CTRL-W command is typed or anything else that
1693 * should be handled as a Normal mode command.
1694 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1695 * the terminal was closed.
1696 */
1697 int
1698terminal_loop(int blocking)
1699{
1700 int c;
1701 int termkey = 0;
1702 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001703#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001704 int tty_fd = curbuf->b_term->tl_job->jv_channel
1705 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001706#endif
Bram Moolenaard317b382018-02-08 22:33:31 +01001707 int restore_cursor;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001708
1709 /* Remember the terminal we are sending keys to. However, the terminal
1710 * might be closed while waiting for a character, e.g. typing "exit" in a
1711 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1712 * stored reference. */
1713 in_terminal_loop = curbuf->b_term;
1714
1715 if (*curwin->w_p_tk != NUL)
1716 termkey = string_to_key(curwin->w_p_tk, TRUE);
1717 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
1718 may_set_cursor_props(curbuf->b_term);
1719
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001720 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001721 {
1722 /* TODO: skip screen update when handling a sequence of keys. */
1723 /* Repeat redrawing in case a message is received while redrawing. */
1724 while (must_redraw != 0)
1725 if (update_screen(0) == FAIL)
1726 break;
1727 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01001728 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001729
1730 c = term_vgetc();
1731 if (!term_use_loop())
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001732 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001733 /* Job finished while waiting for a character. Push back the
1734 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001735 if (c != K_IGNORE)
1736 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001737 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001738 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001739 if (c == K_IGNORE)
1740 continue;
1741
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001742#ifdef UNIX
1743 /*
1744 * The shell or another program may change the tty settings. Getting
1745 * them for every typed character is a bit of overhead, but it's needed
1746 * for the first character typed, e.g. when Vim starts in a shell.
1747 */
1748 if (isatty(tty_fd))
1749 {
1750 ttyinfo_T info;
1751
1752 /* Get the current backspace character of the pty. */
1753 if (get_tty_info(tty_fd, &info) == OK)
1754 term_backspace_char = info.backspace;
1755 }
1756#endif
1757
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001758#ifdef WIN3264
1759 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
1760 * Use CTRL-BREAK to kill the job. */
1761 if (ctrl_break_was_pressed)
1762 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
1763#endif
1764 /* Was either CTRL-W (termkey) or CTRL-\ pressed? */
1765 if (c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
1766 {
1767 int prev_c = c;
1768
1769#ifdef FEAT_CMDL_INFO
1770 if (add_to_showcmd(c))
1771 out_flush();
1772#endif
1773 c = term_vgetc();
1774#ifdef FEAT_CMDL_INFO
1775 clear_showcmd();
1776#endif
1777 if (!term_use_loop())
1778 /* job finished while waiting for a character */
1779 break;
1780
1781 if (prev_c == Ctrl_BSL)
1782 {
1783 if (c == Ctrl_N)
1784 {
1785 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
1786 term_enter_normal_mode();
1787 ret = FAIL;
1788 goto theend;
1789 }
1790 /* Send both keys to the terminal. */
1791 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
1792 }
1793 else if (c == Ctrl_C)
1794 {
1795 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
1796 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
1797 }
1798 else if (termkey == 0 && c == '.')
1799 {
1800 /* "CTRL-W .": send CTRL-W to the job */
1801 c = Ctrl_W;
1802 }
1803 else if (c == 'N')
1804 {
1805 /* CTRL-W N : go to Terminal-Normal mode. */
1806 term_enter_normal_mode();
1807 ret = FAIL;
1808 goto theend;
1809 }
1810 else if (c == '"')
1811 {
1812 term_paste_register(prev_c);
1813 continue;
1814 }
1815 else if (termkey == 0 || c != termkey)
1816 {
1817 stuffcharReadbuff(Ctrl_W);
1818 stuffcharReadbuff(c);
1819 ret = OK;
1820 goto theend;
1821 }
1822 }
1823# ifdef WIN3264
1824 if (!enc_utf8 && has_mbyte && c >= 0x80)
1825 {
1826 WCHAR wc;
1827 char_u mb[3];
1828
1829 mb[0] = (unsigned)c >> 8;
1830 mb[1] = c;
1831 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
1832 c = wc;
1833 }
1834# endif
1835 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
1836 {
Bram Moolenaard317b382018-02-08 22:33:31 +01001837 if (c == K_MOUSEMOVE)
1838 /* We are sure to come back here, don't reset the cursor color
1839 * and shape to avoid flickering. */
1840 restore_cursor = FALSE;
1841
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001842 ret = OK;
1843 goto theend;
1844 }
1845 }
1846 ret = FAIL;
1847
1848theend:
1849 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01001850 if (restore_cursor)
1851 prepare_restore_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001852 return ret;
1853}
1854
1855/*
1856 * Called when a job has finished.
1857 * This updates the title and status, but does not close the vterm, because
1858 * there might still be pending output in the channel.
1859 */
1860 void
1861term_job_ended(job_T *job)
1862{
1863 term_T *term;
1864 int did_one = FALSE;
1865
1866 for (term = first_term; term != NULL; term = term->tl_next)
1867 if (term->tl_job == job)
1868 {
Bram Moolenaard23a8232018-02-10 18:45:26 +01001869 VIM_CLEAR(term->tl_title);
1870 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001871 redraw_buf_and_status_later(term->tl_buffer, VALID);
1872 did_one = TRUE;
1873 }
1874 if (did_one)
1875 redraw_statuslines();
1876 if (curbuf->b_term != NULL)
1877 {
1878 if (curbuf->b_term->tl_job == job)
1879 maketitle();
1880 update_cursor(curbuf->b_term, TRUE);
1881 }
1882}
1883
1884 static void
1885may_toggle_cursor(term_T *term)
1886{
1887 if (in_terminal_loop == term)
1888 {
1889 if (term->tl_cursor_visible)
1890 cursor_on();
1891 else
1892 cursor_off();
1893 }
1894}
1895
1896/*
1897 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01001898 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001899 */
1900 static int
1901color2index(VTermColor *color, int fg, int *boldp)
1902{
1903 int red = color->red;
1904 int blue = color->blue;
1905 int green = color->green;
1906
Bram Moolenaar46359e12017-11-29 22:33:38 +01001907 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001908 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01001909 /* First 16 colors and default: use the ANSI index, because these
1910 * colors can be redefined. */
1911 if (t_colors >= 16)
1912 return color->ansi_index;
1913 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001914 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01001915 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01001916 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01001917 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
1918 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
1919 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
1920 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue*/
1921 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
1922 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
1923 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
1924 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
1925 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
1926 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
1927 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
1928 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
1929 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
1930 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
1931 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001932 }
1933 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01001934
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001935 if (t_colors >= 256)
1936 {
1937 if (red == blue && red == green)
1938 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001939 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001940 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001941 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
1942 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
1943 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001944 int i;
1945
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001946 if (red < 5)
1947 return 17; /* 00/00/00 */
1948 if (red > 245) /* ff/ff/ff */
1949 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001950 for (i = 0; i < 23; ++i)
1951 if (red < cutoff[i])
1952 return i + 233;
1953 return 256;
1954 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001955 {
1956 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
1957 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001958
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001959 /* 216-color cube */
1960 for (ri = 0; ri < 5; ++ri)
1961 if (red < cutoff[ri])
1962 break;
1963 for (gi = 0; gi < 5; ++gi)
1964 if (green < cutoff[gi])
1965 break;
1966 for (bi = 0; bi < 5; ++bi)
1967 if (blue < cutoff[bi])
1968 break;
1969 return 17 + ri * 36 + gi * 6 + bi;
1970 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001971 }
1972 return 0;
1973}
1974
1975/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01001976 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001977 */
1978 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01001979vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001980{
1981 int attr = 0;
1982
1983 if (cellattrs.bold)
1984 attr |= HL_BOLD;
1985 if (cellattrs.underline)
1986 attr |= HL_UNDERLINE;
1987 if (cellattrs.italic)
1988 attr |= HL_ITALIC;
1989 if (cellattrs.strike)
1990 attr |= HL_STRIKETHROUGH;
1991 if (cellattrs.reverse)
1992 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001993 return attr;
1994}
1995
1996/*
1997 * Store Vterm attributes in "cell" from highlight flags.
1998 */
1999 static void
2000hl2vtermAttr(int attr, cellattr_T *cell)
2001{
2002 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2003 if (attr & HL_BOLD)
2004 cell->attrs.bold = 1;
2005 if (attr & HL_UNDERLINE)
2006 cell->attrs.underline = 1;
2007 if (attr & HL_ITALIC)
2008 cell->attrs.italic = 1;
2009 if (attr & HL_STRIKETHROUGH)
2010 cell->attrs.strike = 1;
2011 if (attr & HL_INVERSE)
2012 cell->attrs.reverse = 1;
2013}
2014
2015/*
2016 * Convert the attributes of a vterm cell into an attribute index.
2017 */
2018 static int
2019cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2020{
2021 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002022
2023#ifdef FEAT_GUI
2024 if (gui.in_use)
2025 {
2026 guicolor_T fg, bg;
2027
2028 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2029 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2030 return get_gui_attr_idx(attr, fg, bg);
2031 }
2032 else
2033#endif
2034#ifdef FEAT_TERMGUICOLORS
2035 if (p_tgc)
2036 {
2037 guicolor_T fg, bg;
2038
2039 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2040 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2041
2042 return get_tgc_attr_idx(attr, fg, bg);
2043 }
2044 else
2045#endif
2046 {
2047 int bold = MAYBE;
2048 int fg = color2index(&cellfg, TRUE, &bold);
2049 int bg = color2index(&cellbg, FALSE, &bold);
2050
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002051 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002052 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002053 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002054 if (fg == 0 && term_default_cterm_fg >= 0)
2055 fg = term_default_cterm_fg + 1;
2056 if (bg == 0 && term_default_cterm_bg >= 0)
2057 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002058 }
2059
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002060 /* with 8 colors set the bold attribute to get a bright foreground */
2061 if (bold == TRUE)
2062 attr |= HL_BOLD;
2063 return get_cterm_attr_idx(attr, fg, bg);
2064 }
2065 return 0;
2066}
2067
2068 static int
2069handle_damage(VTermRect rect, void *user)
2070{
2071 term_T *term = (term_T *)user;
2072
2073 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2074 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
2075 redraw_buf_later(term->tl_buffer, NOT_VALID);
2076 return 1;
2077}
2078
2079 static int
2080handle_moverect(VTermRect dest, VTermRect src, void *user)
2081{
2082 term_T *term = (term_T *)user;
2083
2084 /* Scrolling up is done much more efficiently by deleting lines instead of
2085 * redrawing the text. */
2086 if (dest.start_col == src.start_col
2087 && dest.end_col == src.end_col
2088 && dest.start_row < src.start_row)
2089 {
2090 win_T *wp;
2091 VTermColor fg, bg;
2092 VTermScreenCellAttrs attr;
2093 int clear_attr;
2094
2095 /* Set the color to clear lines with. */
2096 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2097 &fg, &bg);
2098 vim_memset(&attr, 0, sizeof(attr));
2099 clear_attr = cell2attr(attr, fg, bg);
2100
2101 FOR_ALL_WINDOWS(wp)
2102 {
2103 if (wp->w_buffer == term->tl_buffer)
2104 win_del_lines(wp, dest.start_row,
2105 src.start_row - dest.start_row, FALSE, FALSE,
2106 clear_attr);
2107 }
2108 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002109
2110 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2111 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
2112
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002113 redraw_buf_later(term->tl_buffer, NOT_VALID);
2114 return 1;
2115}
2116
2117 static int
2118handle_movecursor(
2119 VTermPos pos,
2120 VTermPos oldpos UNUSED,
2121 int visible,
2122 void *user)
2123{
2124 term_T *term = (term_T *)user;
2125 win_T *wp;
2126
2127 term->tl_cursor_pos = pos;
2128 term->tl_cursor_visible = visible;
2129
2130 FOR_ALL_WINDOWS(wp)
2131 {
2132 if (wp->w_buffer == term->tl_buffer)
2133 position_cursor(wp, &pos);
2134 }
2135 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2136 {
2137 may_toggle_cursor(term);
2138 update_cursor(term, term->tl_cursor_visible);
2139 }
2140
2141 return 1;
2142}
2143
2144 static int
2145handle_settermprop(
2146 VTermProp prop,
2147 VTermValue *value,
2148 void *user)
2149{
2150 term_T *term = (term_T *)user;
2151
2152 switch (prop)
2153 {
2154 case VTERM_PROP_TITLE:
2155 vim_free(term->tl_title);
2156 /* a blank title isn't useful, make it empty, so that "running" is
2157 * displayed */
2158 if (*skipwhite((char_u *)value->string) == NUL)
2159 term->tl_title = NULL;
2160#ifdef WIN3264
2161 else if (!enc_utf8 && enc_codepage > 0)
2162 {
2163 WCHAR *ret = NULL;
2164 int length = 0;
2165
2166 MultiByteToWideChar_alloc(CP_UTF8, 0,
2167 (char*)value->string, (int)STRLEN(value->string),
2168 &ret, &length);
2169 if (ret != NULL)
2170 {
2171 WideCharToMultiByte_alloc(enc_codepage, 0,
2172 ret, length, (char**)&term->tl_title,
2173 &length, 0, 0);
2174 vim_free(ret);
2175 }
2176 }
2177#endif
2178 else
2179 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002180 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002181 if (term == curbuf->b_term)
2182 maketitle();
2183 break;
2184
2185 case VTERM_PROP_CURSORVISIBLE:
2186 term->tl_cursor_visible = value->boolean;
2187 may_toggle_cursor(term);
2188 out_flush();
2189 break;
2190
2191 case VTERM_PROP_CURSORBLINK:
2192 term->tl_cursor_blink = value->boolean;
2193 may_set_cursor_props(term);
2194 break;
2195
2196 case VTERM_PROP_CURSORSHAPE:
2197 term->tl_cursor_shape = value->number;
2198 may_set_cursor_props(term);
2199 break;
2200
2201 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaard317b382018-02-08 22:33:31 +01002202 if (desired_cursor_color == term->tl_cursor_color)
2203 desired_cursor_color = (char_u *)"";
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002204 vim_free(term->tl_cursor_color);
2205 if (*value->string == NUL)
2206 term->tl_cursor_color = NULL;
2207 else
2208 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2209 may_set_cursor_props(term);
2210 break;
2211
2212 case VTERM_PROP_ALTSCREEN:
2213 /* TODO: do anything else? */
2214 term->tl_using_altscreen = value->boolean;
2215 break;
2216
2217 default:
2218 break;
2219 }
2220 /* Always return 1, otherwise vterm doesn't store the value internally. */
2221 return 1;
2222}
2223
2224/*
2225 * The job running in the terminal resized the terminal.
2226 */
2227 static int
2228handle_resize(int rows, int cols, void *user)
2229{
2230 term_T *term = (term_T *)user;
2231 win_T *wp;
2232
2233 term->tl_rows = rows;
2234 term->tl_cols = cols;
2235 if (term->tl_vterm_size_changed)
2236 /* Size was set by vterm_set_size(), don't set the window size. */
2237 term->tl_vterm_size_changed = FALSE;
2238 else
2239 {
2240 FOR_ALL_WINDOWS(wp)
2241 {
2242 if (wp->w_buffer == term->tl_buffer)
2243 {
2244 win_setheight_win(rows, wp);
2245 win_setwidth_win(cols, wp);
2246 }
2247 }
2248 redraw_buf_later(term->tl_buffer, NOT_VALID);
2249 }
2250 return 1;
2251}
2252
2253/*
2254 * Handle a line that is pushed off the top of the screen.
2255 */
2256 static int
2257handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2258{
2259 term_T *term = (term_T *)user;
2260
2261 /* TODO: Limit the number of lines that are stored. */
2262 if (ga_grow(&term->tl_scrollback, 1) == OK)
2263 {
2264 cellattr_T *p = NULL;
2265 int len = 0;
2266 int i;
2267 int c;
2268 int col;
2269 sb_line_T *line;
2270 garray_T ga;
2271 cellattr_T fill_attr = term->tl_default_color;
2272
2273 /* do not store empty cells at the end */
2274 for (i = 0; i < cols; ++i)
2275 if (cells[i].chars[0] != 0)
2276 len = i + 1;
2277 else
2278 cell2cellattr(&cells[i], &fill_attr);
2279
2280 ga_init2(&ga, 1, 100);
2281 if (len > 0)
2282 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2283 if (p != NULL)
2284 {
2285 for (col = 0; col < len; col += cells[col].width)
2286 {
2287 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2288 {
2289 ga.ga_len = 0;
2290 break;
2291 }
2292 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2293 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2294 (char_u *)ga.ga_data + ga.ga_len);
2295 cell2cellattr(&cells[col], &p[col]);
2296 }
2297 }
2298 if (ga_grow(&ga, 1) == FAIL)
2299 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2300 else
2301 {
2302 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2303 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2304 }
2305 ga_clear(&ga);
2306
2307 line = (sb_line_T *)term->tl_scrollback.ga_data
2308 + term->tl_scrollback.ga_len;
2309 line->sb_cols = len;
2310 line->sb_cells = p;
2311 line->sb_fill_attr = fill_attr;
2312 ++term->tl_scrollback.ga_len;
2313 ++term->tl_scrollback_scrolled;
2314 }
2315 return 0; /* ignored */
2316}
2317
2318static VTermScreenCallbacks screen_callbacks = {
2319 handle_damage, /* damage */
2320 handle_moverect, /* moverect */
2321 handle_movecursor, /* movecursor */
2322 handle_settermprop, /* settermprop */
2323 NULL, /* bell */
2324 handle_resize, /* resize */
2325 handle_pushline, /* sb_pushline */
2326 NULL /* sb_popline */
2327};
2328
2329/*
2330 * Called when a channel has been closed.
2331 * If this was a channel for a terminal window then finish it up.
2332 */
2333 void
2334term_channel_closed(channel_T *ch)
2335{
2336 term_T *term;
2337 int did_one = FALSE;
2338
2339 for (term = first_term; term != NULL; term = term->tl_next)
2340 if (term->tl_job == ch->ch_job)
2341 {
2342 term->tl_channel_closed = TRUE;
2343 did_one = TRUE;
2344
Bram Moolenaard23a8232018-02-10 18:45:26 +01002345 VIM_CLEAR(term->tl_title);
2346 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002347
2348 /* Unless in Terminal-Normal mode: clear the vterm. */
2349 if (!term->tl_normal_mode)
2350 {
2351 int fnum = term->tl_buffer->b_fnum;
2352
2353 cleanup_vterm(term);
2354
2355 if (term->tl_finish == 'c')
2356 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002357 aco_save_T aco;
2358
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002359 /* ++close or term_finish == "close" */
2360 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002361 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002362 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002363 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002364 break;
2365 }
2366 if (term->tl_finish == 'o' && term->tl_buffer->b_nwindows == 0)
2367 {
2368 char buf[50];
2369
2370 /* TODO: use term_opencmd */
2371 ch_log(NULL, "terminal job finished, opening window");
2372 vim_snprintf(buf, sizeof(buf),
2373 term->tl_opencmd == NULL
2374 ? "botright sbuf %d"
2375 : (char *)term->tl_opencmd, fnum);
2376 do_cmdline_cmd((char_u *)buf);
2377 }
2378 else
2379 ch_log(NULL, "terminal job finished");
2380 }
2381
2382 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2383 }
2384 if (did_one)
2385 {
2386 redraw_statuslines();
2387
2388 /* Need to break out of vgetc(). */
2389 ins_char_typebuf(K_IGNORE);
2390 typebuf_was_filled = TRUE;
2391
2392 term = curbuf->b_term;
2393 if (term != NULL)
2394 {
2395 if (term->tl_job == ch->ch_job)
2396 maketitle();
2397 update_cursor(term, term->tl_cursor_visible);
2398 }
2399 }
2400}
2401
2402/*
2403 * Called to update a window that contains an active terminal.
2404 * Returns FAIL when there is no terminal running in this window or in
2405 * Terminal-Normal mode.
2406 */
2407 int
2408term_update_window(win_T *wp)
2409{
2410 term_T *term = wp->w_buffer->b_term;
2411 VTerm *vterm;
2412 VTermScreen *screen;
2413 VTermState *state;
2414 VTermPos pos;
2415
2416 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2417 return FAIL;
2418
2419 vterm = term->tl_vterm;
2420 screen = vterm_obtain_screen(vterm);
2421 state = vterm_obtain_state(vterm);
2422
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002423 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002424 {
2425 term->tl_dirty_row_start = 0;
2426 term->tl_dirty_row_end = MAX_ROW;
2427 }
2428
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002429 /*
2430 * If the window was resized a redraw will be triggered and we get here.
2431 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2432 */
2433 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
2434 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
2435 {
2436 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
2437 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
2438 win_T *twp;
2439
2440 FOR_ALL_WINDOWS(twp)
2441 {
2442 /* When more than one window shows the same terminal, use the
2443 * smallest size. */
2444 if (twp->w_buffer == term->tl_buffer)
2445 {
2446 if (!term->tl_rows_fixed && rows > twp->w_height)
2447 rows = twp->w_height;
2448 if (!term->tl_cols_fixed && cols > twp->w_width)
2449 cols = twp->w_width;
2450 }
2451 }
2452
2453 term->tl_vterm_size_changed = TRUE;
2454 vterm_set_size(vterm, rows, cols);
2455 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
2456 rows);
2457 term_report_winsize(term, rows, cols);
2458 }
2459
2460 /* The cursor may have been moved when resizing. */
2461 vterm_state_get_cursorpos(state, &pos);
2462 position_cursor(wp, &pos);
2463
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002464 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2465 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002466 {
2467 int off = screen_get_current_line_off();
2468 int max_col = MIN(wp->w_width, term->tl_cols);
2469
2470 if (pos.row < term->tl_rows)
2471 {
2472 for (pos.col = 0; pos.col < max_col; )
2473 {
2474 VTermScreenCell cell;
2475 int c;
2476
2477 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
2478 vim_memset(&cell, 0, sizeof(cell));
2479
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002480 c = cell.chars[0];
2481 if (c == NUL)
2482 {
2483 ScreenLines[off] = ' ';
2484 if (enc_utf8)
2485 ScreenLinesUC[off] = NUL;
2486 }
2487 else
2488 {
2489 if (enc_utf8)
2490 {
Bram Moolenaar6daeef12017-10-15 22:56:49 +02002491 int i;
2492
2493 /* composing chars */
2494 for (i = 0; i < Screen_mco
2495 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2496 {
2497 ScreenLinesC[i][off] = cell.chars[i + 1];
2498 if (cell.chars[i + 1] == 0)
2499 break;
2500 }
2501 if (c >= 0x80 || (Screen_mco > 0
2502 && ScreenLinesC[0][off] != 0))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002503 {
2504 ScreenLines[off] = ' ';
2505 ScreenLinesUC[off] = c;
2506 }
2507 else
2508 {
2509 ScreenLines[off] = c;
2510 ScreenLinesUC[off] = NUL;
2511 }
2512 }
2513#ifdef WIN3264
2514 else if (has_mbyte && c >= 0x80)
2515 {
2516 char_u mb[MB_MAXBYTES+1];
2517 WCHAR wc = c;
2518
2519 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2520 (char*)mb, 2, 0, 0) > 1)
2521 {
2522 ScreenLines[off] = mb[0];
2523 ScreenLines[off + 1] = mb[1];
2524 cell.width = mb_ptr2cells(mb);
2525 }
2526 else
2527 ScreenLines[off] = c;
2528 }
2529#endif
2530 else
2531 ScreenLines[off] = c;
2532 }
2533 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2534
2535 ++pos.col;
2536 ++off;
2537 if (cell.width == 2)
2538 {
2539 if (enc_utf8)
2540 ScreenLinesUC[off] = NUL;
2541
2542 /* don't set the second byte to NUL for a DBCS encoding, it
2543 * has been set above */
2544 if (enc_utf8 || !has_mbyte)
2545 ScreenLines[off] = NUL;
2546
2547 ++pos.col;
2548 ++off;
2549 }
2550 }
2551 }
2552 else
2553 pos.col = 0;
2554
Bram Moolenaar181ca992018-02-13 21:19:21 +01002555 screen_line(wp->w_winrow + pos.row + winbar_height(wp),
2556 wp->w_wincol, pos.col, wp->w_width, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002557 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002558 term->tl_dirty_row_start = MAX_ROW;
2559 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002560
2561 return OK;
2562}
2563
2564/*
2565 * Return TRUE if "wp" is a terminal window where the job has finished.
2566 */
2567 int
2568term_is_finished(buf_T *buf)
2569{
2570 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2571}
2572
2573/*
2574 * Return TRUE if "wp" is a terminal window where the job has finished or we
2575 * are in Terminal-Normal mode, thus we show the buffer contents.
2576 */
2577 int
2578term_show_buffer(buf_T *buf)
2579{
2580 term_T *term = buf->b_term;
2581
2582 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2583}
2584
2585/*
2586 * The current buffer is going to be changed. If there is terminal
2587 * highlighting remove it now.
2588 */
2589 void
2590term_change_in_curbuf(void)
2591{
2592 term_T *term = curbuf->b_term;
2593
2594 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2595 {
2596 free_scrollback(term);
2597 redraw_buf_later(term->tl_buffer, NOT_VALID);
2598
2599 /* The buffer is now like a normal buffer, it cannot be easily
2600 * abandoned when changed. */
2601 set_string_option_direct((char_u *)"buftype", -1,
2602 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2603 }
2604}
2605
2606/*
2607 * Get the screen attribute for a position in the buffer.
2608 * Use a negative "col" to get the filler background color.
2609 */
2610 int
2611term_get_attr(buf_T *buf, linenr_T lnum, int col)
2612{
2613 term_T *term = buf->b_term;
2614 sb_line_T *line;
2615 cellattr_T *cellattr;
2616
2617 if (lnum > term->tl_scrollback.ga_len)
2618 cellattr = &term->tl_default_color;
2619 else
2620 {
2621 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
2622 if (col < 0 || col >= line->sb_cols)
2623 cellattr = &line->sb_fill_attr;
2624 else
2625 cellattr = line->sb_cells + col;
2626 }
2627 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
2628}
2629
2630static VTermColor ansi_table[16] = {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002631 { 0, 0, 0, 1}, /* black */
2632 {224, 0, 0, 2}, /* dark red */
2633 { 0, 224, 0, 3}, /* dark green */
2634 {224, 224, 0, 4}, /* dark yellow / brown */
2635 { 0, 0, 224, 5}, /* dark blue */
2636 {224, 0, 224, 6}, /* dark magenta */
2637 { 0, 224, 224, 7}, /* dark cyan */
2638 {224, 224, 224, 8}, /* light grey */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002639
Bram Moolenaar46359e12017-11-29 22:33:38 +01002640 {128, 128, 128, 9}, /* dark grey */
2641 {255, 64, 64, 10}, /* light red */
2642 { 64, 255, 64, 11}, /* light green */
2643 {255, 255, 64, 12}, /* yellow */
2644 { 64, 64, 255, 13}, /* light blue */
2645 {255, 64, 255, 14}, /* light magenta */
2646 { 64, 255, 255, 15}, /* light cyan */
2647 {255, 255, 255, 16}, /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002648};
2649
2650static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002651 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002652};
2653
2654static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002655 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
2656 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002657};
2658
2659/*
2660 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002661 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002662 */
2663 static void
2664cterm_color2rgb(int nr, VTermColor *rgb)
2665{
2666 int idx;
2667
2668 if (nr < 16)
2669 {
2670 *rgb = ansi_table[nr];
2671 }
2672 else if (nr < 232)
2673 {
2674 /* 216 color cube */
2675 idx = nr - 16;
2676 rgb->blue = cube_value[idx % 6];
2677 rgb->green = cube_value[idx / 6 % 6];
2678 rgb->red = cube_value[idx / 36 % 6];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002679 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002680 }
2681 else if (nr < 256)
2682 {
2683 /* 24 grey scale ramp */
2684 idx = nr - 232;
2685 rgb->blue = grey_ramp[idx];
2686 rgb->green = grey_ramp[idx];
2687 rgb->red = grey_ramp[idx];
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002688 rgb->ansi_index = VTERM_ANSI_INDEX_NONE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002689 }
2690}
2691
2692/*
2693 * Create a new vterm and initialize it.
2694 */
2695 static void
2696create_vterm(term_T *term, int rows, int cols)
2697{
2698 VTerm *vterm;
2699 VTermScreen *screen;
2700 VTermValue value;
2701 VTermColor *fg, *bg;
2702 int fgval, bgval;
2703 int id;
2704
2705 vterm = vterm_new(rows, cols);
2706 term->tl_vterm = vterm;
2707 screen = vterm_obtain_screen(vterm);
2708 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
2709 /* TODO: depends on 'encoding'. */
2710 vterm_set_utf8(vterm, 1);
2711
2712 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
2713 term->tl_default_color.width = 1;
2714 fg = &term->tl_default_color.fg;
2715 bg = &term->tl_default_color.bg;
2716
2717 /* Vterm uses a default black background. Set it to white when
2718 * 'background' is "light". */
2719 if (*p_bg == 'l')
2720 {
2721 fgval = 0;
2722 bgval = 255;
2723 }
2724 else
2725 {
2726 fgval = 255;
2727 bgval = 0;
2728 }
2729 fg->red = fg->green = fg->blue = fgval;
2730 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002731 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002732
2733 /* The "Terminal" highlight group overrules the defaults. */
2734 id = syn_name2id((char_u *)"Terminal");
2735
Bram Moolenaar46359e12017-11-29 22:33:38 +01002736 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002737#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
2738 if (0
2739# ifdef FEAT_GUI
2740 || gui.in_use
2741# endif
2742# ifdef FEAT_TERMGUICOLORS
2743 || p_tgc
2744# endif
2745 )
2746 {
2747 guicolor_T fg_rgb = INVALCOLOR;
2748 guicolor_T bg_rgb = INVALCOLOR;
2749
2750 if (id != 0)
2751 syn_id2colors(id, &fg_rgb, &bg_rgb);
2752
2753# ifdef FEAT_GUI
2754 if (gui.in_use)
2755 {
2756 if (fg_rgb == INVALCOLOR)
2757 fg_rgb = gui.norm_pixel;
2758 if (bg_rgb == INVALCOLOR)
2759 bg_rgb = gui.back_pixel;
2760 }
2761# ifdef FEAT_TERMGUICOLORS
2762 else
2763# endif
2764# endif
2765# ifdef FEAT_TERMGUICOLORS
2766 {
2767 if (fg_rgb == INVALCOLOR)
2768 fg_rgb = cterm_normal_fg_gui_color;
2769 if (bg_rgb == INVALCOLOR)
2770 bg_rgb = cterm_normal_bg_gui_color;
2771 }
2772# endif
2773 if (fg_rgb != INVALCOLOR)
2774 {
2775 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
2776
2777 fg->red = (unsigned)(rgb >> 16);
2778 fg->green = (unsigned)(rgb >> 8) & 255;
2779 fg->blue = (unsigned)rgb & 255;
2780 }
2781 if (bg_rgb != INVALCOLOR)
2782 {
2783 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
2784
2785 bg->red = (unsigned)(rgb >> 16);
2786 bg->green = (unsigned)(rgb >> 8) & 255;
2787 bg->blue = (unsigned)rgb & 255;
2788 }
2789 }
2790 else
2791#endif
2792 if (id != 0 && t_colors >= 16)
2793 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002794 if (term_default_cterm_fg >= 0)
2795 cterm_color2rgb(term_default_cterm_fg, fg);
2796 if (term_default_cterm_bg >= 0)
2797 cterm_color2rgb(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002798 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002799 else
2800 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002801#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002802 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002803#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002804
2805 /* In an MS-Windows console we know the normal colors. */
2806 if (cterm_normal_fg_color > 0)
2807 {
2808 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002809# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002810 tmp = fg->red;
2811 fg->red = fg->blue;
2812 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002813# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002814 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02002815# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002816 else
2817 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02002818# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002819
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002820 if (cterm_normal_bg_color > 0)
2821 {
2822 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002823# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002824 tmp = bg->red;
2825 bg->red = bg->blue;
2826 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002827# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002828 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02002829# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002830 else
2831 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02002832# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002833 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002834
2835 vterm_state_set_default_colors(vterm_obtain_state(vterm), fg, bg);
2836
2837 /* Required to initialize most things. */
2838 vterm_screen_reset(screen, 1 /* hard */);
2839
2840 /* Allow using alternate screen. */
2841 vterm_screen_enable_altscreen(screen, 1);
2842
2843 /* For unix do not use a blinking cursor. In an xterm this causes the
2844 * cursor to blink if it's blinking in the xterm.
2845 * For Windows we respect the system wide setting. */
2846#ifdef WIN3264
2847 if (GetCaretBlinkTime() == INFINITE)
2848 value.boolean = 0;
2849 else
2850 value.boolean = 1;
2851#else
2852 value.boolean = 0;
2853#endif
2854 vterm_state_set_termprop(vterm_obtain_state(vterm),
2855 VTERM_PROP_CURSORBLINK, &value);
2856}
2857
2858/*
2859 * Return the text to show for the buffer name and status.
2860 */
2861 char_u *
2862term_get_status_text(term_T *term)
2863{
2864 if (term->tl_status_text == NULL)
2865 {
2866 char_u *txt;
2867 size_t len;
2868
2869 if (term->tl_normal_mode)
2870 {
2871 if (term_job_running(term))
2872 txt = (char_u *)_("Terminal");
2873 else
2874 txt = (char_u *)_("Terminal-finished");
2875 }
2876 else if (term->tl_title != NULL)
2877 txt = term->tl_title;
2878 else if (term_none_open(term))
2879 txt = (char_u *)_("active");
2880 else if (term_job_running(term))
2881 txt = (char_u *)_("running");
2882 else
2883 txt = (char_u *)_("finished");
2884 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
2885 term->tl_status_text = alloc((int)len);
2886 if (term->tl_status_text != NULL)
2887 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
2888 term->tl_buffer->b_fname, txt);
2889 }
2890 return term->tl_status_text;
2891}
2892
2893/*
2894 * Mark references in jobs of terminals.
2895 */
2896 int
2897set_ref_in_term(int copyID)
2898{
2899 int abort = FALSE;
2900 term_T *term;
2901 typval_T tv;
2902
2903 for (term = first_term; term != NULL; term = term->tl_next)
2904 if (term->tl_job != NULL)
2905 {
2906 tv.v_type = VAR_JOB;
2907 tv.vval.v_job = term->tl_job;
2908 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
2909 }
2910 return abort;
2911}
2912
2913/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002914 * Cache "Terminal" highlight group colors.
2915 */
2916 void
2917set_terminal_default_colors(int cterm_fg, int cterm_bg)
2918{
2919 term_default_cterm_fg = cterm_fg - 1;
2920 term_default_cterm_bg = cterm_bg - 1;
2921}
2922
2923/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002924 * Get the buffer from the first argument in "argvars".
2925 * Returns NULL when the buffer is not for a terminal window.
2926 */
2927 static buf_T *
2928term_get_buf(typval_T *argvars)
2929{
2930 buf_T *buf;
2931
2932 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
2933 ++emsg_off;
2934 buf = get_buf_tv(&argvars[0], FALSE);
2935 --emsg_off;
2936 if (buf == NULL || buf->b_term == NULL)
2937 return NULL;
2938 return buf;
2939}
2940
Bram Moolenaard96ff162018-02-18 22:13:29 +01002941 static int
2942same_color(VTermColor *a, VTermColor *b)
2943{
2944 return a->red == b->red
2945 && a->green == b->green
2946 && a->blue == b->blue
2947 && a->ansi_index == b->ansi_index;
2948}
2949
2950 static void
2951dump_term_color(FILE *fd, VTermColor *color)
2952{
2953 fprintf(fd, "%02x%02x%02x%d",
2954 (int)color->red, (int)color->green, (int)color->blue,
2955 (int)color->ansi_index);
2956}
2957
2958/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002959 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01002960 *
2961 * Each screen cell in full is:
2962 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
2963 * {characters} is a space for an empty cell
2964 * For a double-width character "+" is changed to "*" and the next cell is
2965 * skipped.
2966 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
2967 * when "&" use the same as the previous cell.
2968 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
2969 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
2970 * {color-idx} is a number from 0 to 255
2971 *
2972 * Screen cell with same width, attributes and color as the previous one:
2973 * |{characters}
2974 *
2975 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
2976 *
2977 * Repeating the previous screen cell:
2978 * @{count}
2979 */
2980 void
2981f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
2982{
2983 buf_T *buf = term_get_buf(argvars);
2984 term_T *term;
2985 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002986 int max_height = 0;
2987 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002988 stat_T st;
2989 FILE *fd;
2990 VTermPos pos;
2991 VTermScreen *screen;
2992 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01002993 VTermState *state;
2994 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002995
2996 if (check_restricted() || check_secure())
2997 return;
2998 if (buf == NULL)
2999 return;
3000 term = buf->b_term;
3001
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003002 if (argvars[2].v_type != VAR_UNKNOWN)
3003 {
3004 dict_T *d;
3005
3006 if (argvars[2].v_type != VAR_DICT)
3007 {
3008 EMSG(_(e_dictreq));
3009 return;
3010 }
3011 d = argvars[2].vval.v_dict;
3012 if (d != NULL)
3013 {
3014 max_height = get_dict_number(d, (char_u *)"rows");
3015 max_width = get_dict_number(d, (char_u *)"columns");
3016 }
3017 }
3018
Bram Moolenaard96ff162018-02-18 22:13:29 +01003019 fname = get_tv_string_chk(&argvars[1]);
3020 if (fname == NULL)
3021 return;
3022 if (mch_stat((char *)fname, &st) >= 0)
3023 {
3024 EMSG2(_("E953: File exists: %s"), fname);
3025 return;
3026 }
3027
Bram Moolenaard96ff162018-02-18 22:13:29 +01003028 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
3029 {
3030 EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
3031 return;
3032 }
3033
3034 vim_memset(&prev_cell, 0, sizeof(prev_cell));
3035
3036 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003037 state = vterm_obtain_state(term->tl_vterm);
3038 vterm_state_get_cursorpos(state, &cursor_pos);
3039
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003040 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
3041 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003042 {
3043 int repeat = 0;
3044
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01003045 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
3046 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003047 {
3048 VTermScreenCell cell;
3049 int same_attr;
3050 int same_chars = TRUE;
3051 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003052 int is_cursor_pos = (pos.col == cursor_pos.col
3053 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003054
3055 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3056 vim_memset(&cell, 0, sizeof(cell));
3057
3058 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3059 {
3060 if (cell.chars[i] != prev_cell.chars[i])
3061 same_chars = FALSE;
3062 if (cell.chars[i] == NUL || prev_cell.chars[i] == NUL)
3063 break;
3064 }
3065 same_attr = vtermAttr2hl(cell.attrs)
3066 == vtermAttr2hl(prev_cell.attrs)
3067 && same_color(&cell.fg, &prev_cell.fg)
3068 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003069 if (same_chars && cell.width == prev_cell.width && same_attr
3070 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003071 {
3072 ++repeat;
3073 }
3074 else
3075 {
3076 if (repeat > 0)
3077 {
3078 fprintf(fd, "@%d", repeat);
3079 repeat = 0;
3080 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003081 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003082
3083 if (cell.chars[0] == NUL)
3084 fputs(" ", fd);
3085 else
3086 {
3087 char_u charbuf[10];
3088 int len;
3089
3090 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
3091 && cell.chars[i] != NUL; ++i)
3092 {
3093 len = utf_char2bytes(cell.chars[0], charbuf);
3094 fwrite(charbuf, len, 1, fd);
3095 }
3096 }
3097
3098 /* When only the characters differ we don't write anything, the
3099 * following "|", "@" or NL will indicate using the same
3100 * attributes. */
3101 if (cell.width != prev_cell.width || !same_attr)
3102 {
3103 if (cell.width == 2)
3104 {
3105 fputs("*", fd);
3106 ++pos.col;
3107 }
3108 else
3109 fputs("+", fd);
3110
3111 if (same_attr)
3112 {
3113 fputs("&", fd);
3114 }
3115 else
3116 {
3117 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
3118 if (same_color(&cell.fg, &prev_cell.fg))
3119 fputs("&", fd);
3120 else
3121 {
3122 fputs("#", fd);
3123 dump_term_color(fd, &cell.fg);
3124 }
3125 if (same_color(&cell.bg, &prev_cell.bg))
3126 fputs("&", fd);
3127 else
3128 {
3129 fputs("#", fd);
3130 dump_term_color(fd, &cell.bg);
3131 }
3132 }
3133 }
3134
3135 prev_cell = cell;
3136 }
3137 }
3138 if (repeat > 0)
3139 fprintf(fd, "@%d", repeat);
3140 fputs("\n", fd);
3141 }
3142
3143 fclose(fd);
3144}
3145
3146/*
3147 * Called when a dump is corrupted. Put a breakpoint here when debugging.
3148 */
3149 static void
3150dump_is_corrupt(garray_T *gap)
3151{
3152 ga_concat(gap, (char_u *)"CORRUPT");
3153}
3154
3155 static void
3156append_cell(garray_T *gap, cellattr_T *cell)
3157{
3158 if (ga_grow(gap, 1) == OK)
3159 {
3160 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
3161 ++gap->ga_len;
3162 }
3163}
3164
3165/*
3166 * Read the dump file from "fd" and append lines to the current buffer.
3167 * Return the cell width of the longest line.
3168 */
3169 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01003170read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003171{
3172 int c;
3173 garray_T ga_text;
3174 garray_T ga_cell;
3175 char_u *prev_char = NULL;
3176 int attr = 0;
3177 cellattr_T cell;
3178 term_T *term = curbuf->b_term;
3179 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003180 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003181
3182 ga_init2(&ga_text, 1, 90);
3183 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
3184 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01003185 cursor_pos->row = -1;
3186 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003187
3188 c = fgetc(fd);
3189 for (;;)
3190 {
3191 if (c == EOF)
3192 break;
3193 if (c == '\n')
3194 {
3195 /* End of a line: append it to the buffer. */
3196 if (ga_text.ga_data == NULL)
3197 dump_is_corrupt(&ga_text);
3198 if (ga_grow(&term->tl_scrollback, 1) == OK)
3199 {
3200 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
3201 + term->tl_scrollback.ga_len;
3202
3203 if (max_cells < ga_cell.ga_len)
3204 max_cells = ga_cell.ga_len;
3205 line->sb_cols = ga_cell.ga_len;
3206 line->sb_cells = ga_cell.ga_data;
3207 line->sb_fill_attr = term->tl_default_color;
3208 ++term->tl_scrollback.ga_len;
3209 ga_init(&ga_cell);
3210
3211 ga_append(&ga_text, NUL);
3212 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3213 ga_text.ga_len, FALSE);
3214 }
3215 else
3216 ga_clear(&ga_cell);
3217 ga_text.ga_len = 0;
3218
3219 c = fgetc(fd);
3220 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01003221 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003222 {
3223 int prev_len = ga_text.ga_len;
3224
Bram Moolenaar9271d052018-02-25 21:39:46 +01003225 if (c == '>')
3226 {
3227 if (cursor_pos->row != -1)
3228 dump_is_corrupt(&ga_text); /* duplicate cursor */
3229 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
3230 cursor_pos->col = ga_cell.ga_len;
3231 }
3232
Bram Moolenaard96ff162018-02-18 22:13:29 +01003233 /* normal character(s) followed by "+", "*", "|", "@" or NL */
3234 c = fgetc(fd);
3235 if (c != EOF)
3236 ga_append(&ga_text, c);
3237 for (;;)
3238 {
3239 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01003240 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01003241 || c == EOF || c == '\n')
3242 break;
3243 ga_append(&ga_text, c);
3244 }
3245
3246 /* save the character for repeating it */
3247 vim_free(prev_char);
3248 if (ga_text.ga_data != NULL)
3249 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
3250 ga_text.ga_len - prev_len);
3251
Bram Moolenaar9271d052018-02-25 21:39:46 +01003252 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01003253 {
3254 /* use all attributes from previous cell */
3255 }
3256 else if (c == '+' || c == '*')
3257 {
3258 int is_bg;
3259
3260 cell.width = c == '+' ? 1 : 2;
3261
3262 c = fgetc(fd);
3263 if (c == '&')
3264 {
3265 /* use same attr as previous cell */
3266 c = fgetc(fd);
3267 }
3268 else if (isdigit(c))
3269 {
3270 /* get the decimal attribute */
3271 attr = 0;
3272 while (isdigit(c))
3273 {
3274 attr = attr * 10 + (c - '0');
3275 c = fgetc(fd);
3276 }
3277 hl2vtermAttr(attr, &cell);
3278 }
3279 else
3280 dump_is_corrupt(&ga_text);
3281
3282 /* is_bg == 0: fg, is_bg == 1: bg */
3283 for (is_bg = 0; is_bg <= 1; ++is_bg)
3284 {
3285 if (c == '&')
3286 {
3287 /* use same color as previous cell */
3288 c = fgetc(fd);
3289 }
3290 else if (c == '#')
3291 {
3292 int red, green, blue, index = 0;
3293
3294 c = fgetc(fd);
3295 red = hex2nr(c);
3296 c = fgetc(fd);
3297 red = (red << 4) + hex2nr(c);
3298 c = fgetc(fd);
3299 green = hex2nr(c);
3300 c = fgetc(fd);
3301 green = (green << 4) + hex2nr(c);
3302 c = fgetc(fd);
3303 blue = hex2nr(c);
3304 c = fgetc(fd);
3305 blue = (blue << 4) + hex2nr(c);
3306 c = fgetc(fd);
3307 if (!isdigit(c))
3308 dump_is_corrupt(&ga_text);
3309 while (isdigit(c))
3310 {
3311 index = index * 10 + (c - '0');
3312 c = fgetc(fd);
3313 }
3314
3315 if (is_bg)
3316 {
3317 cell.bg.red = red;
3318 cell.bg.green = green;
3319 cell.bg.blue = blue;
3320 cell.bg.ansi_index = index;
3321 }
3322 else
3323 {
3324 cell.fg.red = red;
3325 cell.fg.green = green;
3326 cell.fg.blue = blue;
3327 cell.fg.ansi_index = index;
3328 }
3329 }
3330 else
3331 dump_is_corrupt(&ga_text);
3332 }
3333 }
3334 else
3335 dump_is_corrupt(&ga_text);
3336
3337 append_cell(&ga_cell, &cell);
3338 }
3339 else if (c == '@')
3340 {
3341 if (prev_char == NULL)
3342 dump_is_corrupt(&ga_text);
3343 else
3344 {
3345 int count = 0;
3346
3347 /* repeat previous character, get the count */
3348 for (;;)
3349 {
3350 c = fgetc(fd);
3351 if (!isdigit(c))
3352 break;
3353 count = count * 10 + (c - '0');
3354 }
3355
3356 while (count-- > 0)
3357 {
3358 ga_concat(&ga_text, prev_char);
3359 append_cell(&ga_cell, &cell);
3360 }
3361 }
3362 }
3363 else
3364 {
3365 dump_is_corrupt(&ga_text);
3366 c = fgetc(fd);
3367 }
3368 }
3369
3370 if (ga_text.ga_len > 0)
3371 {
3372 /* trailing characters after last NL */
3373 dump_is_corrupt(&ga_text);
3374 ga_append(&ga_text, NUL);
3375 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
3376 ga_text.ga_len, FALSE);
3377 }
3378
3379 ga_clear(&ga_text);
3380 vim_free(prev_char);
3381
3382 return max_cells;
3383}
3384
3385/*
3386 * Common for "term_dumpdiff()" and "term_dumpload()".
3387 */
3388 static void
3389term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
3390{
3391 jobopt_T opt;
3392 buf_T *buf;
3393 char_u buf1[NUMBUFLEN];
3394 char_u buf2[NUMBUFLEN];
3395 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003396 char_u *fname2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003397 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003398 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003399 char_u *textline = NULL;
3400
3401 /* First open the files. If this fails bail out. */
3402 fname1 = get_tv_string_buf_chk(&argvars[0], buf1);
3403 if (do_diff)
3404 fname2 = get_tv_string_buf_chk(&argvars[1], buf2);
3405 if (fname1 == NULL || (do_diff && fname2 == NULL))
3406 {
3407 EMSG(_(e_invarg));
3408 return;
3409 }
3410 fd1 = mch_fopen((char *)fname1, READBIN);
3411 if (fd1 == NULL)
3412 {
3413 EMSG2(_(e_notread), fname1);
3414 return;
3415 }
3416 if (do_diff)
3417 {
3418 fd2 = mch_fopen((char *)fname2, READBIN);
3419 if (fd2 == NULL)
3420 {
3421 fclose(fd1);
3422 EMSG2(_(e_notread), fname2);
3423 return;
3424 }
3425 }
3426
3427 init_job_options(&opt);
3428 /* TODO: use the {options} argument */
3429
3430 /* TODO: use the file name arguments for the buffer name */
3431 opt.jo_term_name = (char_u *)"dump diff";
3432
3433 buf = term_start(&argvars[0], &opt, TRUE, FALSE);
3434 if (buf != NULL && buf->b_term != NULL)
3435 {
3436 int i;
3437 linenr_T bot_lnum;
3438 linenr_T lnum;
3439 term_T *term = buf->b_term;
3440 int width;
3441 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003442 VTermPos cursor_pos1;
3443 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01003444
3445 rettv->vval.v_number = buf->b_fnum;
3446
3447 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01003448 width = read_dump_file(fd1, &cursor_pos1);
3449
3450 /* position the cursor */
3451 if (cursor_pos1.row >= 0)
3452 {
3453 curwin->w_cursor.lnum = cursor_pos1.row + 1;
3454 coladvance(cursor_pos1.col);
3455 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01003456
3457 /* Delete the empty line that was in the empty buffer. */
3458 ml_delete(1, FALSE);
3459
3460 /* For term_dumpload() we are done here. */
3461 if (!do_diff)
3462 goto theend;
3463
3464 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
3465
3466 textline = alloc(width + 1);
3467 if (textline == NULL)
3468 goto theend;
3469 for (i = 0; i < width; ++i)
3470 textline[i] = '=';
3471 textline[width] = NUL;
3472 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3473 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3474 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
3475 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
3476
3477 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01003478 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003479 if (width2 > width)
3480 {
3481 vim_free(textline);
3482 textline = alloc(width2 + 1);
3483 if (textline == NULL)
3484 goto theend;
3485 width = width2;
3486 textline[width] = NUL;
3487 }
3488 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
3489
3490 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
3491 {
3492 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
3493 {
3494 /* bottom part has fewer rows, fill with "-" */
3495 for (i = 0; i < width; ++i)
3496 textline[i] = '-';
3497 }
3498 else
3499 {
3500 char_u *line1;
3501 char_u *line2;
3502 char_u *p1;
3503 char_u *p2;
3504 int col;
3505 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3506 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
3507 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
3508 ->sb_cells;
3509
3510 /* Make a copy, getting the second line will invalidate it. */
3511 line1 = vim_strsave(ml_get(lnum));
3512 if (line1 == NULL)
3513 break;
3514 p1 = line1;
3515
3516 line2 = ml_get(lnum + bot_lnum);
3517 p2 = line2;
3518 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
3519 {
3520 int len1 = utfc_ptr2len(p1);
3521 int len2 = utfc_ptr2len(p2);
3522
3523 textline[col] = ' ';
3524 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01003525 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01003526 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01003527 else if (lnum == cursor_pos1.row + 1
3528 && col == cursor_pos1.col
3529 && (cursor_pos1.row != cursor_pos2.row
3530 || cursor_pos1.col != cursor_pos2.col))
3531 /* cursor in first but not in second */
3532 textline[col] = '>';
3533 else if (lnum == cursor_pos2.row + 1
3534 && col == cursor_pos2.col
3535 && (cursor_pos1.row != cursor_pos2.row
3536 || cursor_pos1.col != cursor_pos2.col))
3537 /* cursor in second but not in first */
3538 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01003539 else if (cellattr1 != NULL && cellattr2 != NULL)
3540 {
3541 if ((cellattr1 + col)->width
3542 != (cellattr2 + col)->width)
3543 textline[col] = 'w';
3544 else if (!same_color(&(cellattr1 + col)->fg,
3545 &(cellattr2 + col)->fg))
3546 textline[col] = 'f';
3547 else if (!same_color(&(cellattr1 + col)->bg,
3548 &(cellattr2 + col)->bg))
3549 textline[col] = 'b';
3550 else if (vtermAttr2hl((cellattr1 + col)->attrs)
3551 != vtermAttr2hl(((cellattr2 + col)->attrs)))
3552 textline[col] = 'a';
3553 }
3554 p1 += len1;
3555 p2 += len2;
3556 /* TODO: handle different width */
3557 }
3558 vim_free(line1);
3559
3560 while (col < width)
3561 {
3562 if (*p1 == NUL && *p2 == NUL)
3563 textline[col] = '?';
3564 else if (*p1 == NUL)
3565 {
3566 textline[col] = '+';
3567 p2 += utfc_ptr2len(p2);
3568 }
3569 else
3570 {
3571 textline[col] = '-';
3572 p1 += utfc_ptr2len(p1);
3573 }
3574 ++col;
3575 }
3576 }
3577 if (add_empty_scrollback(term, &term->tl_default_color,
3578 term->tl_top_diff_rows) == OK)
3579 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3580 ++bot_lnum;
3581 }
3582
3583 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
3584 {
3585 /* bottom part has more rows, fill with "+" */
3586 for (i = 0; i < width; ++i)
3587 textline[i] = '+';
3588 if (add_empty_scrollback(term, &term->tl_default_color,
3589 term->tl_top_diff_rows) == OK)
3590 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
3591 ++lnum;
3592 ++bot_lnum;
3593 }
3594
3595 term->tl_cols = width;
3596 }
3597
3598theend:
3599 vim_free(textline);
3600 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01003601 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003602 fclose(fd2);
3603}
3604
3605/*
3606 * If the current buffer shows the output of term_dumpdiff(), swap the top and
3607 * bottom files.
3608 * Return FAIL when this is not possible.
3609 */
3610 int
3611term_swap_diff()
3612{
3613 term_T *term = curbuf->b_term;
3614 linenr_T line_count;
3615 linenr_T top_rows;
3616 linenr_T bot_rows;
3617 linenr_T bot_start;
3618 linenr_T lnum;
3619 char_u *p;
3620 sb_line_T *sb_line;
3621
3622 if (term == NULL
3623 || !term_is_finished(curbuf)
3624 || term->tl_top_diff_rows == 0
3625 || term->tl_scrollback.ga_len == 0)
3626 return FAIL;
3627
3628 line_count = curbuf->b_ml.ml_line_count;
3629 top_rows = term->tl_top_diff_rows;
3630 bot_rows = term->tl_bot_diff_rows;
3631 bot_start = line_count - bot_rows;
3632 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
3633
3634 /* move lines from top to above the bottom part */
3635 for (lnum = 1; lnum <= top_rows; ++lnum)
3636 {
3637 p = vim_strsave(ml_get(1));
3638 if (p == NULL)
3639 return OK;
3640 ml_append(bot_start, p, 0, FALSE);
3641 ml_delete(1, FALSE);
3642 vim_free(p);
3643 }
3644
3645 /* move lines from bottom to the top */
3646 for (lnum = 1; lnum <= bot_rows; ++lnum)
3647 {
3648 p = vim_strsave(ml_get(bot_start + lnum));
3649 if (p == NULL)
3650 return OK;
3651 ml_delete(bot_start + lnum, FALSE);
3652 ml_append(lnum - 1, p, 0, FALSE);
3653 vim_free(p);
3654 }
3655
3656 if (top_rows == bot_rows)
3657 {
3658 /* rows counts are equal, can swap cell properties */
3659 for (lnum = 0; lnum < top_rows; ++lnum)
3660 {
3661 sb_line_T temp;
3662
3663 temp = *(sb_line + lnum);
3664 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
3665 *(sb_line + bot_start + lnum) = temp;
3666 }
3667 }
3668 else
3669 {
3670 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
3671 sb_line_T *temp = (sb_line_T *)alloc((int)size);
3672
3673 /* need to copy cell properties into temp memory */
3674 if (temp != NULL)
3675 {
3676 mch_memmove(temp, term->tl_scrollback.ga_data, size);
3677 mch_memmove(term->tl_scrollback.ga_data,
3678 temp + bot_start,
3679 sizeof(sb_line_T) * bot_rows);
3680 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
3681 temp + top_rows,
3682 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
3683 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
3684 + line_count - top_rows,
3685 temp,
3686 sizeof(sb_line_T) * top_rows);
3687 vim_free(temp);
3688 }
3689 }
3690
3691 term->tl_top_diff_rows = bot_rows;
3692 term->tl_bot_diff_rows = top_rows;
3693
3694 update_screen(NOT_VALID);
3695 return OK;
3696}
3697
3698/*
3699 * "term_dumpdiff(filename, filename, options)" function
3700 */
3701 void
3702f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
3703{
3704 term_load_dump(argvars, rettv, TRUE);
3705}
3706
3707/*
3708 * "term_dumpload(filename, options)" function
3709 */
3710 void
3711f_term_dumpload(typval_T *argvars, typval_T *rettv)
3712{
3713 term_load_dump(argvars, rettv, FALSE);
3714}
3715
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003716/*
3717 * "term_getaltscreen(buf)" function
3718 */
3719 void
3720f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
3721{
3722 buf_T *buf = term_get_buf(argvars);
3723
3724 if (buf == NULL)
3725 return;
3726 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
3727}
3728
3729/*
3730 * "term_getattr(attr, name)" function
3731 */
3732 void
3733f_term_getattr(typval_T *argvars, typval_T *rettv)
3734{
3735 int attr;
3736 size_t i;
3737 char_u *name;
3738
3739 static struct {
3740 char *name;
3741 int attr;
3742 } attrs[] = {
3743 {"bold", HL_BOLD},
3744 {"italic", HL_ITALIC},
3745 {"underline", HL_UNDERLINE},
3746 {"strike", HL_STRIKETHROUGH},
3747 {"reverse", HL_INVERSE},
3748 };
3749
3750 attr = get_tv_number(&argvars[0]);
3751 name = get_tv_string_chk(&argvars[1]);
3752 if (name == NULL)
3753 return;
3754
3755 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
3756 if (STRCMP(name, attrs[i].name) == 0)
3757 {
3758 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
3759 break;
3760 }
3761}
3762
3763/*
3764 * "term_getcursor(buf)" function
3765 */
3766 void
3767f_term_getcursor(typval_T *argvars, typval_T *rettv)
3768{
3769 buf_T *buf = term_get_buf(argvars);
3770 term_T *term;
3771 list_T *l;
3772 dict_T *d;
3773
3774 if (rettv_list_alloc(rettv) == FAIL)
3775 return;
3776 if (buf == NULL)
3777 return;
3778 term = buf->b_term;
3779
3780 l = rettv->vval.v_list;
3781 list_append_number(l, term->tl_cursor_pos.row + 1);
3782 list_append_number(l, term->tl_cursor_pos.col + 1);
3783
3784 d = dict_alloc();
3785 if (d != NULL)
3786 {
3787 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
3788 dict_add_nr_str(d, "blink", blink_state_is_inverted()
3789 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
3790 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
3791 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
3792 ? (char_u *)"" : term->tl_cursor_color);
3793 list_append_dict(l, d);
3794 }
3795}
3796
3797/*
3798 * "term_getjob(buf)" function
3799 */
3800 void
3801f_term_getjob(typval_T *argvars, typval_T *rettv)
3802{
3803 buf_T *buf = term_get_buf(argvars);
3804
3805 rettv->v_type = VAR_JOB;
3806 rettv->vval.v_job = NULL;
3807 if (buf == NULL)
3808 return;
3809
3810 rettv->vval.v_job = buf->b_term->tl_job;
3811 if (rettv->vval.v_job != NULL)
3812 ++rettv->vval.v_job->jv_refcount;
3813}
3814
3815 static int
3816get_row_number(typval_T *tv, term_T *term)
3817{
3818 if (tv->v_type == VAR_STRING
3819 && tv->vval.v_string != NULL
3820 && STRCMP(tv->vval.v_string, ".") == 0)
3821 return term->tl_cursor_pos.row;
3822 return (int)get_tv_number(tv) - 1;
3823}
3824
3825/*
3826 * "term_getline(buf, row)" function
3827 */
3828 void
3829f_term_getline(typval_T *argvars, typval_T *rettv)
3830{
3831 buf_T *buf = term_get_buf(argvars);
3832 term_T *term;
3833 int row;
3834
3835 rettv->v_type = VAR_STRING;
3836 if (buf == NULL)
3837 return;
3838 term = buf->b_term;
3839 row = get_row_number(&argvars[1], term);
3840
3841 if (term->tl_vterm == NULL)
3842 {
3843 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
3844
3845 /* vterm is finished, get the text from the buffer */
3846 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
3847 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
3848 }
3849 else
3850 {
3851 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
3852 VTermRect rect;
3853 int len;
3854 char_u *p;
3855
3856 if (row < 0 || row >= term->tl_rows)
3857 return;
3858 len = term->tl_cols * MB_MAXBYTES + 1;
3859 p = alloc(len);
3860 if (p == NULL)
3861 return;
3862 rettv->vval.v_string = p;
3863
3864 rect.start_col = 0;
3865 rect.end_col = term->tl_cols;
3866 rect.start_row = row;
3867 rect.end_row = row + 1;
3868 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
3869 }
3870}
3871
3872/*
3873 * "term_getscrolled(buf)" function
3874 */
3875 void
3876f_term_getscrolled(typval_T *argvars, typval_T *rettv)
3877{
3878 buf_T *buf = term_get_buf(argvars);
3879
3880 if (buf == NULL)
3881 return;
3882 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
3883}
3884
3885/*
3886 * "term_getsize(buf)" function
3887 */
3888 void
3889f_term_getsize(typval_T *argvars, typval_T *rettv)
3890{
3891 buf_T *buf = term_get_buf(argvars);
3892 list_T *l;
3893
3894 if (rettv_list_alloc(rettv) == FAIL)
3895 return;
3896 if (buf == NULL)
3897 return;
3898
3899 l = rettv->vval.v_list;
3900 list_append_number(l, buf->b_term->tl_rows);
3901 list_append_number(l, buf->b_term->tl_cols);
3902}
3903
3904/*
3905 * "term_getstatus(buf)" function
3906 */
3907 void
3908f_term_getstatus(typval_T *argvars, typval_T *rettv)
3909{
3910 buf_T *buf = term_get_buf(argvars);
3911 term_T *term;
3912 char_u val[100];
3913
3914 rettv->v_type = VAR_STRING;
3915 if (buf == NULL)
3916 return;
3917 term = buf->b_term;
3918
3919 if (term_job_running(term))
3920 STRCPY(val, "running");
3921 else
3922 STRCPY(val, "finished");
3923 if (term->tl_normal_mode)
3924 STRCAT(val, ",normal");
3925 rettv->vval.v_string = vim_strsave(val);
3926}
3927
3928/*
3929 * "term_gettitle(buf)" function
3930 */
3931 void
3932f_term_gettitle(typval_T *argvars, typval_T *rettv)
3933{
3934 buf_T *buf = term_get_buf(argvars);
3935
3936 rettv->v_type = VAR_STRING;
3937 if (buf == NULL)
3938 return;
3939
3940 if (buf->b_term->tl_title != NULL)
3941 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
3942}
3943
3944/*
3945 * "term_gettty(buf)" function
3946 */
3947 void
3948f_term_gettty(typval_T *argvars, typval_T *rettv)
3949{
3950 buf_T *buf = term_get_buf(argvars);
3951 char_u *p;
3952 int num = 0;
3953
3954 rettv->v_type = VAR_STRING;
3955 if (buf == NULL)
3956 return;
3957 if (argvars[1].v_type != VAR_UNKNOWN)
3958 num = get_tv_number(&argvars[1]);
3959
3960 switch (num)
3961 {
3962 case 0:
3963 if (buf->b_term->tl_job != NULL)
3964 p = buf->b_term->tl_job->jv_tty_out;
3965 else
3966 p = buf->b_term->tl_tty_out;
3967 break;
3968 case 1:
3969 if (buf->b_term->tl_job != NULL)
3970 p = buf->b_term->tl_job->jv_tty_in;
3971 else
3972 p = buf->b_term->tl_tty_in;
3973 break;
3974 default:
3975 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
3976 return;
3977 }
3978 if (p != NULL)
3979 rettv->vval.v_string = vim_strsave(p);
3980}
3981
3982/*
3983 * "term_list()" function
3984 */
3985 void
3986f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
3987{
3988 term_T *tp;
3989 list_T *l;
3990
3991 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
3992 return;
3993
3994 l = rettv->vval.v_list;
3995 for (tp = first_term; tp != NULL; tp = tp->tl_next)
3996 if (tp != NULL && tp->tl_buffer != NULL)
3997 if (list_append_number(l,
3998 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
3999 return;
4000}
4001
4002/*
4003 * "term_scrape(buf, row)" function
4004 */
4005 void
4006f_term_scrape(typval_T *argvars, typval_T *rettv)
4007{
4008 buf_T *buf = term_get_buf(argvars);
4009 VTermScreen *screen = NULL;
4010 VTermPos pos;
4011 list_T *l;
4012 term_T *term;
4013 char_u *p;
4014 sb_line_T *line;
4015
4016 if (rettv_list_alloc(rettv) == FAIL)
4017 return;
4018 if (buf == NULL)
4019 return;
4020 term = buf->b_term;
4021
4022 l = rettv->vval.v_list;
4023 pos.row = get_row_number(&argvars[1], term);
4024
4025 if (term->tl_vterm != NULL)
4026 {
4027 screen = vterm_obtain_screen(term->tl_vterm);
4028 p = NULL;
4029 line = NULL;
4030 }
4031 else
4032 {
4033 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
4034
4035 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
4036 return;
4037 p = ml_get_buf(buf, lnum + 1, FALSE);
4038 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
4039 }
4040
4041 for (pos.col = 0; pos.col < term->tl_cols; )
4042 {
4043 dict_T *dcell;
4044 int width;
4045 VTermScreenCellAttrs attrs;
4046 VTermColor fg, bg;
4047 char_u rgb[8];
4048 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
4049 int off = 0;
4050 int i;
4051
4052 if (screen == NULL)
4053 {
4054 cellattr_T *cellattr;
4055 int len;
4056
4057 /* vterm has finished, get the cell from scrollback */
4058 if (pos.col >= line->sb_cols)
4059 break;
4060 cellattr = line->sb_cells + pos.col;
4061 width = cellattr->width;
4062 attrs = cellattr->attrs;
4063 fg = cellattr->fg;
4064 bg = cellattr->bg;
4065 len = MB_PTR2LEN(p);
4066 mch_memmove(mbs, p, len);
4067 mbs[len] = NUL;
4068 p += len;
4069 }
4070 else
4071 {
4072 VTermScreenCell cell;
4073 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4074 break;
4075 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4076 {
4077 if (cell.chars[i] == 0)
4078 break;
4079 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
4080 }
4081 mbs[off] = NUL;
4082 width = cell.width;
4083 attrs = cell.attrs;
4084 fg = cell.fg;
4085 bg = cell.bg;
4086 }
4087 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01004088 if (dcell == NULL)
4089 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004090 list_append_dict(l, dcell);
4091
4092 dict_add_nr_str(dcell, "chars", 0, mbs);
4093
4094 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4095 fg.red, fg.green, fg.blue);
4096 dict_add_nr_str(dcell, "fg", 0, rgb);
4097 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
4098 bg.red, bg.green, bg.blue);
4099 dict_add_nr_str(dcell, "bg", 0, rgb);
4100
4101 dict_add_nr_str(dcell, "attr",
4102 cell2attr(attrs, fg, bg), NULL);
4103 dict_add_nr_str(dcell, "width", width, NULL);
4104
4105 ++pos.col;
4106 if (width == 2)
4107 ++pos.col;
4108 }
4109}
4110
4111/*
4112 * "term_sendkeys(buf, keys)" function
4113 */
4114 void
4115f_term_sendkeys(typval_T *argvars, typval_T *rettv)
4116{
4117 buf_T *buf = term_get_buf(argvars);
4118 char_u *msg;
4119 term_T *term;
4120
4121 rettv->v_type = VAR_UNKNOWN;
4122 if (buf == NULL)
4123 return;
4124
4125 msg = get_tv_string_chk(&argvars[1]);
4126 if (msg == NULL)
4127 return;
4128 term = buf->b_term;
4129 if (term->tl_vterm == NULL)
4130 return;
4131
4132 while (*msg != NUL)
4133 {
4134 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02004135 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004136 }
4137}
4138
4139/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004140 * "term_setrestore(buf, command)" function
4141 */
4142 void
4143f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4144{
4145#if defined(FEAT_SESSION)
4146 buf_T *buf = term_get_buf(argvars);
4147 term_T *term;
4148 char_u *cmd;
4149
4150 if (buf == NULL)
4151 return;
4152 term = buf->b_term;
4153 vim_free(term->tl_command);
4154 cmd = get_tv_string_chk(&argvars[1]);
4155 if (cmd != NULL)
4156 term->tl_command = vim_strsave(cmd);
4157 else
4158 term->tl_command = NULL;
4159#endif
4160}
4161
4162/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004163 * "term_start(command, options)" function
4164 */
4165 void
4166f_term_start(typval_T *argvars, typval_T *rettv)
4167{
4168 jobopt_T opt;
4169 buf_T *buf;
4170
4171 init_job_options(&opt);
4172 if (argvars[1].v_type != VAR_UNKNOWN
4173 && get_job_options(&argvars[1], &opt,
4174 JO_TIMEOUT_ALL + JO_STOPONEXIT
4175 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
4176 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
4177 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
4178 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004179 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
4180 + JO2_NORESTORE) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004181 return;
4182
4183 if (opt.jo_vertical)
4184 cmdmod.split = WSP_VERT;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004185 buf = term_start(&argvars[0], &opt, FALSE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004186
4187 if (buf != NULL && buf->b_term != NULL)
4188 rettv->vval.v_number = buf->b_fnum;
4189}
4190
4191/*
4192 * "term_wait" function
4193 */
4194 void
4195f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
4196{
4197 buf_T *buf = term_get_buf(argvars);
4198
4199 if (buf == NULL)
4200 {
4201 ch_log(NULL, "term_wait(): invalid argument");
4202 return;
4203 }
4204 if (buf->b_term->tl_job == NULL)
4205 {
4206 ch_log(NULL, "term_wait(): no job to wait for");
4207 return;
4208 }
4209 if (buf->b_term->tl_job->jv_channel == NULL)
4210 /* channel is closed, nothing to do */
4211 return;
4212
4213 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01004214 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004215 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
4216 {
4217 /* The job is dead, keep reading channel I/O until the channel is
4218 * closed. buf->b_term may become NULL if the terminal was closed while
4219 * waiting. */
4220 ch_log(NULL, "term_wait(): waiting for channel to close");
4221 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
4222 {
4223 mch_check_messages();
4224 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01004225 if (!buf_valid(buf))
4226 /* If the terminal is closed when the channel is closed the
4227 * buffer disappears. */
4228 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004229 ui_delay(10L, FALSE);
4230 }
4231 mch_check_messages();
4232 parse_queued_messages();
4233 }
4234 else
4235 {
4236 long wait = 10L;
4237
4238 mch_check_messages();
4239 parse_queued_messages();
4240
4241 /* Wait for some time for any channel I/O. */
4242 if (argvars[1].v_type != VAR_UNKNOWN)
4243 wait = get_tv_number(&argvars[1]);
4244 ui_delay(wait, TRUE);
4245 mch_check_messages();
4246
4247 /* Flushing messages on channels is hopefully sufficient.
4248 * TODO: is there a better way? */
4249 parse_queued_messages();
4250 }
4251}
4252
4253/*
4254 * Called when a channel has sent all the lines to a terminal.
4255 * Send a CTRL-D to mark the end of the text.
4256 */
4257 void
4258term_send_eof(channel_T *ch)
4259{
4260 term_T *term;
4261
4262 for (term = first_term; term != NULL; term = term->tl_next)
4263 if (term->tl_job == ch->ch_job)
4264 {
4265 if (term->tl_eof_chars != NULL)
4266 {
4267 channel_send(ch, PART_IN, term->tl_eof_chars,
4268 (int)STRLEN(term->tl_eof_chars), NULL);
4269 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
4270 }
4271# ifdef WIN3264
4272 else
4273 /* Default: CTRL-D */
4274 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
4275# endif
4276 }
4277}
4278
4279# if defined(WIN3264) || defined(PROTO)
4280
4281/**************************************
4282 * 2. MS-Windows implementation.
4283 */
4284
4285# ifndef PROTO
4286
4287#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
4288#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01004289#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004290
4291void* (*winpty_config_new)(UINT64, void*);
4292void* (*winpty_open)(void*, void*);
4293void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
4294BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
4295void (*winpty_config_set_mouse_mode)(void*, int);
4296void (*winpty_config_set_initial_size)(void*, int, int);
4297LPCWSTR (*winpty_conin_name)(void*);
4298LPCWSTR (*winpty_conout_name)(void*);
4299LPCWSTR (*winpty_conerr_name)(void*);
4300void (*winpty_free)(void*);
4301void (*winpty_config_free)(void*);
4302void (*winpty_spawn_config_free)(void*);
4303void (*winpty_error_free)(void*);
4304LPCWSTR (*winpty_error_msg)(void*);
4305BOOL (*winpty_set_size)(void*, int, int, void*);
4306HANDLE (*winpty_agent_process)(void*);
4307
4308#define WINPTY_DLL "winpty.dll"
4309
4310static HINSTANCE hWinPtyDLL = NULL;
4311# endif
4312
4313 static int
4314dyn_winpty_init(int verbose)
4315{
4316 int i;
4317 static struct
4318 {
4319 char *name;
4320 FARPROC *ptr;
4321 } winpty_entry[] =
4322 {
4323 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
4324 {"winpty_config_free", (FARPROC*)&winpty_config_free},
4325 {"winpty_config_new", (FARPROC*)&winpty_config_new},
4326 {"winpty_config_set_mouse_mode",
4327 (FARPROC*)&winpty_config_set_mouse_mode},
4328 {"winpty_config_set_initial_size",
4329 (FARPROC*)&winpty_config_set_initial_size},
4330 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
4331 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
4332 {"winpty_error_free", (FARPROC*)&winpty_error_free},
4333 {"winpty_free", (FARPROC*)&winpty_free},
4334 {"winpty_open", (FARPROC*)&winpty_open},
4335 {"winpty_spawn", (FARPROC*)&winpty_spawn},
4336 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
4337 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
4338 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
4339 {"winpty_set_size", (FARPROC*)&winpty_set_size},
4340 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
4341 {NULL, NULL}
4342 };
4343
4344 /* No need to initialize twice. */
4345 if (hWinPtyDLL)
4346 return OK;
4347 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
4348 * winpty.dll. */
4349 if (*p_winptydll != NUL)
4350 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
4351 if (!hWinPtyDLL)
4352 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
4353 if (!hWinPtyDLL)
4354 {
4355 if (verbose)
4356 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
4357 : (char_u *)WINPTY_DLL);
4358 return FAIL;
4359 }
4360 for (i = 0; winpty_entry[i].name != NULL
4361 && winpty_entry[i].ptr != NULL; ++i)
4362 {
4363 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
4364 winpty_entry[i].name)) == NULL)
4365 {
4366 if (verbose)
4367 EMSG2(_(e_loadfunc), winpty_entry[i].name);
4368 return FAIL;
4369 }
4370 }
4371
4372 return OK;
4373}
4374
4375/*
4376 * Create a new terminal of "rows" by "cols" cells.
4377 * Store a reference in "term".
4378 * Return OK or FAIL.
4379 */
4380 static int
4381term_and_job_init(
4382 term_T *term,
4383 typval_T *argvar,
4384 jobopt_T *opt)
4385{
4386 WCHAR *cmd_wchar = NULL;
4387 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004388 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004389 channel_T *channel = NULL;
4390 job_T *job = NULL;
4391 DWORD error;
4392 HANDLE jo = NULL;
4393 HANDLE child_process_handle;
4394 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01004395 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004396 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004397 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004398 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004399
4400 if (dyn_winpty_init(TRUE) == FAIL)
4401 return FAIL;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004402 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
4403 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004404
4405 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004406 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004407 cmd = argvar->vval.v_string;
4408 }
4409 else if (argvar->v_type == VAR_LIST)
4410 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004411 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004412 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004413 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004414 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004415 if (cmd == NULL || *cmd == NUL)
4416 {
4417 EMSG(_(e_invarg));
4418 goto failed;
4419 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004420
4421 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004422 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004423 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004424 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004425 if (opt->jo_cwd != NULL)
4426 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004427
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01004428 win32_build_env(opt->jo_env, &ga_env, TRUE);
4429 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004430
4431 job = job_alloc();
4432 if (job == NULL)
4433 goto failed;
4434
4435 channel = add_channel();
4436 if (channel == NULL)
4437 goto failed;
4438
4439 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
4440 if (term->tl_winpty_config == NULL)
4441 goto failed;
4442
4443 winpty_config_set_mouse_mode(term->tl_winpty_config,
4444 WINPTY_MOUSE_MODE_FORCE);
4445 winpty_config_set_initial_size(term->tl_winpty_config,
4446 term->tl_cols, term->tl_rows);
4447 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
4448 if (term->tl_winpty == NULL)
4449 goto failed;
4450
4451 spawn_config = winpty_spawn_config_new(
4452 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
4453 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
4454 NULL,
4455 cmd_wchar,
4456 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01004457 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004458 &winpty_err);
4459 if (spawn_config == NULL)
4460 goto failed;
4461
4462 channel = add_channel();
4463 if (channel == NULL)
4464 goto failed;
4465
4466 job = job_alloc();
4467 if (job == NULL)
4468 goto failed;
4469
4470 if (opt->jo_set & JO_IN_BUF)
4471 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
4472
4473 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
4474 &child_thread_handle, &error, &winpty_err))
4475 goto failed;
4476
4477 channel_set_pipes(channel,
4478 (sock_T)CreateFileW(
4479 winpty_conin_name(term->tl_winpty),
4480 GENERIC_WRITE, 0, NULL,
4481 OPEN_EXISTING, 0, NULL),
4482 (sock_T)CreateFileW(
4483 winpty_conout_name(term->tl_winpty),
4484 GENERIC_READ, 0, NULL,
4485 OPEN_EXISTING, 0, NULL),
4486 (sock_T)CreateFileW(
4487 winpty_conerr_name(term->tl_winpty),
4488 GENERIC_READ, 0, NULL,
4489 OPEN_EXISTING, 0, NULL));
4490
4491 /* Write lines with CR instead of NL. */
4492 channel->ch_write_text_mode = TRUE;
4493
4494 jo = CreateJobObject(NULL, NULL);
4495 if (jo == NULL)
4496 goto failed;
4497
4498 if (!AssignProcessToJobObject(jo, child_process_handle))
4499 {
4500 /* Failed, switch the way to terminate process with TerminateProcess. */
4501 CloseHandle(jo);
4502 jo = NULL;
4503 }
4504
4505 winpty_spawn_config_free(spawn_config);
4506 vim_free(cmd_wchar);
4507 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004508 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004509
4510 create_vterm(term, term->tl_rows, term->tl_cols);
4511
4512 channel_set_job(channel, job, opt);
4513 job_set_options(job, opt);
4514
4515 job->jv_channel = channel;
4516 job->jv_proc_info.hProcess = child_process_handle;
4517 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
4518 job->jv_job_object = jo;
4519 job->jv_status = JOB_STARTED;
4520 job->jv_tty_in = utf16_to_enc(
4521 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
4522 job->jv_tty_out = utf16_to_enc(
4523 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
4524 ++job->jv_refcount;
4525 term->tl_job = job;
4526
4527 return OK;
4528
4529failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01004530 ga_clear(&ga_cmd);
4531 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004532 vim_free(cmd_wchar);
4533 vim_free(cwd_wchar);
4534 if (spawn_config != NULL)
4535 winpty_spawn_config_free(spawn_config);
4536 if (channel != NULL)
4537 channel_clear(channel);
4538 if (job != NULL)
4539 {
4540 job->jv_channel = NULL;
4541 job_cleanup(job);
4542 }
4543 term->tl_job = NULL;
4544 if (jo != NULL)
4545 CloseHandle(jo);
4546 if (term->tl_winpty != NULL)
4547 winpty_free(term->tl_winpty);
4548 term->tl_winpty = NULL;
4549 if (term->tl_winpty_config != NULL)
4550 winpty_config_free(term->tl_winpty_config);
4551 term->tl_winpty_config = NULL;
4552 if (winpty_err != NULL)
4553 {
4554 char_u *msg = utf16_to_enc(
4555 (short_u *)winpty_error_msg(winpty_err), NULL);
4556
4557 EMSG(msg);
4558 winpty_error_free(winpty_err);
4559 }
4560 return FAIL;
4561}
4562
4563 static int
4564create_pty_only(term_T *term, jobopt_T *options)
4565{
4566 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
4567 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
4568 char in_name[80], out_name[80];
4569 channel_T *channel = NULL;
4570
4571 create_vterm(term, term->tl_rows, term->tl_cols);
4572
4573 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
4574 GetCurrentProcessId(),
4575 curbuf->b_fnum);
4576 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
4577 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4578 PIPE_UNLIMITED_INSTANCES,
4579 0, 0, NMPWAIT_NOWAIT, NULL);
4580 if (hPipeIn == INVALID_HANDLE_VALUE)
4581 goto failed;
4582
4583 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
4584 GetCurrentProcessId(),
4585 curbuf->b_fnum);
4586 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
4587 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
4588 PIPE_UNLIMITED_INSTANCES,
4589 0, 0, 0, NULL);
4590 if (hPipeOut == INVALID_HANDLE_VALUE)
4591 goto failed;
4592
4593 ConnectNamedPipe(hPipeIn, NULL);
4594 ConnectNamedPipe(hPipeOut, NULL);
4595
4596 term->tl_job = job_alloc();
4597 if (term->tl_job == NULL)
4598 goto failed;
4599 ++term->tl_job->jv_refcount;
4600
4601 /* behave like the job is already finished */
4602 term->tl_job->jv_status = JOB_FINISHED;
4603
4604 channel = add_channel();
4605 if (channel == NULL)
4606 goto failed;
4607 term->tl_job->jv_channel = channel;
4608 channel->ch_keep_open = TRUE;
4609 channel->ch_named_pipe = TRUE;
4610
4611 channel_set_pipes(channel,
4612 (sock_T)hPipeIn,
4613 (sock_T)hPipeOut,
4614 (sock_T)hPipeOut);
4615 channel_set_job(channel, term->tl_job, options);
4616 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
4617 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
4618
4619 return OK;
4620
4621failed:
4622 if (hPipeIn != NULL)
4623 CloseHandle(hPipeIn);
4624 if (hPipeOut != NULL)
4625 CloseHandle(hPipeOut);
4626 return FAIL;
4627}
4628
4629/*
4630 * Free the terminal emulator part of "term".
4631 */
4632 static void
4633term_free_vterm(term_T *term)
4634{
4635 if (term->tl_winpty != NULL)
4636 winpty_free(term->tl_winpty);
4637 term->tl_winpty = NULL;
4638 if (term->tl_winpty_config != NULL)
4639 winpty_config_free(term->tl_winpty_config);
4640 term->tl_winpty_config = NULL;
4641 if (term->tl_vterm != NULL)
4642 vterm_free(term->tl_vterm);
4643 term->tl_vterm = NULL;
4644}
4645
4646/*
4647 * Request size to terminal.
4648 */
4649 static void
4650term_report_winsize(term_T *term, int rows, int cols)
4651{
4652 if (term->tl_winpty)
4653 winpty_set_size(term->tl_winpty, cols, rows, NULL);
4654}
4655
4656 int
4657terminal_enabled(void)
4658{
4659 return dyn_winpty_init(FALSE) == OK;
4660}
4661
4662# else
4663
4664/**************************************
4665 * 3. Unix-like implementation.
4666 */
4667
4668/*
4669 * Create a new terminal of "rows" by "cols" cells.
4670 * Start job for "cmd".
4671 * Store the pointers in "term".
4672 * Return OK or FAIL.
4673 */
4674 static int
4675term_and_job_init(
4676 term_T *term,
4677 typval_T *argvar,
4678 jobopt_T *opt)
4679{
4680 create_vterm(term, term->tl_rows, term->tl_cols);
4681
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01004682 /* This will change a string in "argvar". */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004683 term->tl_job = job_start(argvar, opt);
4684 if (term->tl_job != NULL)
4685 ++term->tl_job->jv_refcount;
4686
4687 return term->tl_job != NULL
4688 && term->tl_job->jv_channel != NULL
4689 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
4690}
4691
4692 static int
4693create_pty_only(term_T *term, jobopt_T *opt)
4694{
4695 create_vterm(term, term->tl_rows, term->tl_cols);
4696
4697 term->tl_job = job_alloc();
4698 if (term->tl_job == NULL)
4699 return FAIL;
4700 ++term->tl_job->jv_refcount;
4701
4702 /* behave like the job is already finished */
4703 term->tl_job->jv_status = JOB_FINISHED;
4704
4705 return mch_create_pty_channel(term->tl_job, opt);
4706}
4707
4708/*
4709 * Free the terminal emulator part of "term".
4710 */
4711 static void
4712term_free_vterm(term_T *term)
4713{
4714 if (term->tl_vterm != NULL)
4715 vterm_free(term->tl_vterm);
4716 term->tl_vterm = NULL;
4717}
4718
4719/*
4720 * Request size to terminal.
4721 */
4722 static void
4723term_report_winsize(term_T *term, int rows, int cols)
4724{
4725 /* Use an ioctl() to report the new window size to the job. */
4726 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
4727 {
4728 int fd = -1;
4729 int part;
4730
4731 for (part = PART_OUT; part < PART_COUNT; ++part)
4732 {
4733 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
4734 if (isatty(fd))
4735 break;
4736 }
4737 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
4738 mch_signal_job(term->tl_job, (char_u *)"winch");
4739 }
4740}
4741
4742# endif
4743
4744#endif /* FEAT_TERMINAL */