blob: 716d0b53b20c533d91f7ecbb1b9747b2ce188e27 [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:
41 * - in GUI vertical split causes problems. Cursor is flickering. (Hirohito
42 * Higashi, 2017 Sep 19)
43 * - Shift-Tab does not work.
Bram Moolenaar3a497e12017-09-30 20:40:27 +020044 * - after resizing windows overlap. (Boris Staletic, #2164)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020045 * - Redirecting output does not work on MS-Windows, Test_terminal_redir_file()
46 * is disabled.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020047 * - cursor blinks in terminal on widows with a timer. (xtal8, #2142)
Bram Moolenaarba6febd2017-10-30 21:56:23 +010048 * - When closing gvim with an active terminal buffer, the dialog suggests
49 * saving the buffer. Should say something else. (Manas Thakur, #2215)
50 * Also: #2223
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020051 * - implement term_setsize()
Bram Moolenaarba6febd2017-10-30 21:56:23 +010052 * - Termdebug does not work when Vim build with mzscheme. gdb hangs.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +020053 * - MS-Windows GUI: WinBar has tearoff item
Bram Moolenaarff546792017-11-21 14:47:57 +010054 * - Adding WinBar to terminal window doesn't display, text isn't shifted down.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020055 * - MS-Windows GUI: still need to type a key after shell exits? #1924
Bram Moolenaar51b0f372017-11-18 18:52:04 +010056 * - After executing a shell command the status line isn't redraw.
Bram Moolenaarba6febd2017-10-30 21:56:23 +010057 * - What to store in a session file? Shell at the prompt would be OK to
58 * restore, but others may not. Open the window and let the user start the
59 * command?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020060 * - add test for giving error for invalid 'termsize' value.
61 * - support minimal size when 'termsize' is "rows*cols".
62 * - support minimal size when 'termsize' is empty?
63 * - GUI: when using tabs, focus in terminal, click on tab does not work.
64 * - GUI: when 'confirm' is set and trying to exit Vim, dialog offers to save
65 * changes to "!shell".
66 * (justrajdeep, 2017 Aug 22)
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +020067 * - Redrawing is slow with Athena and Motif. Also other GUI? (Ramel Eshed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020068 * - For the GUI fill termios with default values, perhaps like pangoterm:
69 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134
70 * - if the job in the terminal does not support the mouse, we can use the
71 * mouse in the Terminal window for copy/paste.
72 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
73 * conversions.
74 * - In the GUI use a terminal emulator for :!cmd. Make the height the same as
75 * the window and position it higher up when it gets filled, so it looks like
76 * the text scrolls up.
77 * - Copy text in the vterm to the Vim buffer once in a while, so that
78 * completion works.
79 * - add an optional limit for the scrollback size. When reaching it remove
80 * 10% at the start.
81 */
82
83#include "vim.h"
84
85#if defined(FEAT_TERMINAL) || defined(PROTO)
86
87#ifndef MIN
88# define MIN(x,y) ((x) < (y) ? (x) : (y))
89#endif
90#ifndef MAX
91# define MAX(x,y) ((x) > (y) ? (x) : (y))
92#endif
93
94#include "libvterm/include/vterm.h"
95
96/* This is VTermScreenCell without the characters, thus much smaller. */
97typedef struct {
98 VTermScreenCellAttrs attrs;
99 char width;
100 VTermColor fg, bg;
101} cellattr_T;
102
103typedef struct sb_line_S {
104 int sb_cols; /* can differ per line */
105 cellattr_T *sb_cells; /* allocated */
106 cellattr_T sb_fill_attr; /* for short line */
107} sb_line_T;
108
109/* typedef term_T in structs.h */
110struct terminal_S {
111 term_T *tl_next;
112
113 VTerm *tl_vterm;
114 job_T *tl_job;
115 buf_T *tl_buffer;
116
117 /* Set when setting the size of a vterm, reset after redrawing. */
118 int tl_vterm_size_changed;
119
120 /* used when tl_job is NULL and only a pty was created */
121 int tl_tty_fd;
122 char_u *tl_tty_in;
123 char_u *tl_tty_out;
124
125 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
126 int tl_channel_closed;
127 int tl_finish; /* 'c' for ++close, 'o' for ++open */
128 char_u *tl_opencmd;
129 char_u *tl_eof_chars;
130
131#ifdef WIN3264
132 void *tl_winpty_config;
133 void *tl_winpty;
134#endif
135
136 /* last known vterm size */
137 int tl_rows;
138 int tl_cols;
139 /* vterm size does not follow window size */
140 int tl_rows_fixed;
141 int tl_cols_fixed;
142
143 char_u *tl_title; /* NULL or allocated */
144 char_u *tl_status_text; /* NULL or allocated */
145
146 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200147 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200148 int tl_dirty_row_end; /* row below last one to update */
149
150 garray_T tl_scrollback;
151 int tl_scrollback_scrolled;
152 cellattr_T tl_default_color;
153
154 VTermPos tl_cursor_pos;
155 int tl_cursor_visible;
156 int tl_cursor_blink;
157 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
158 char_u *tl_cursor_color; /* NULL or allocated */
159
160 int tl_using_altscreen;
161};
162
163#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
164#define TMODE_LOOP 2 /* CTRL-W N used */
165
166/*
167 * List of all active terminals.
168 */
169static term_T *first_term = NULL;
170
171/* Terminal active in terminal_loop(). */
172static term_T *in_terminal_loop = NULL;
173
174#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
175#define KEY_BUF_LEN 200
176
177/*
178 * Functions with separate implementation for MS-Windows and Unix-like systems.
179 */
180static int term_and_job_init(term_T *term, typval_T *argvar, jobopt_T *opt);
181static int create_pty_only(term_T *term, jobopt_T *opt);
182static void term_report_winsize(term_T *term, int rows, int cols);
183static void term_free_vterm(term_T *term);
184
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100185/* The character that we know (or assume) that the terminal expects for the
186 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200187static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200188
189
190/**************************************
191 * 1. Generic code for all systems.
192 */
193
194/*
195 * Determine the terminal size from 'termsize' and the current window.
196 * Assumes term->tl_rows and term->tl_cols are zero.
197 */
198 static void
199set_term_and_win_size(term_T *term)
200{
201 if (*curwin->w_p_tms != NUL)
202 {
203 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
204
205 term->tl_rows = atoi((char *)curwin->w_p_tms);
206 term->tl_cols = atoi((char *)p);
207 }
208 if (term->tl_rows == 0)
209 term->tl_rows = curwin->w_height;
210 else
211 {
212 win_setheight_win(term->tl_rows, curwin);
213 term->tl_rows_fixed = TRUE;
214 }
215 if (term->tl_cols == 0)
216 term->tl_cols = curwin->w_width;
217 else
218 {
219 win_setwidth_win(term->tl_cols, curwin);
220 term->tl_cols_fixed = TRUE;
221 }
222}
223
224/*
225 * Initialize job options for a terminal job.
226 * Caller may overrule some of them.
227 */
228 static void
229init_job_options(jobopt_T *opt)
230{
231 clear_job_options(opt);
232
233 opt->jo_mode = MODE_RAW;
234 opt->jo_out_mode = MODE_RAW;
235 opt->jo_err_mode = MODE_RAW;
236 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
237}
238
239/*
240 * Set job options mandatory for a terminal job.
241 */
242 static void
243setup_job_options(jobopt_T *opt, int rows, int cols)
244{
245 if (!(opt->jo_set & JO_OUT_IO))
246 {
247 /* Connect stdout to the terminal. */
248 opt->jo_io[PART_OUT] = JIO_BUFFER;
249 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
250 opt->jo_modifiable[PART_OUT] = 0;
251 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
252 }
253
254 if (!(opt->jo_set & JO_ERR_IO))
255 {
256 /* Connect stderr to the terminal. */
257 opt->jo_io[PART_ERR] = JIO_BUFFER;
258 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
259 opt->jo_modifiable[PART_ERR] = 0;
260 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
261 }
262
263 opt->jo_pty = TRUE;
264 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
265 opt->jo_term_rows = rows;
266 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
267 opt->jo_term_cols = cols;
268}
269
270/*
271 * Start a terminal window and return its buffer.
272 * Returns NULL when failed.
273 */
274 static buf_T *
275term_start(typval_T *argvar, jobopt_T *opt, int forceit)
276{
277 exarg_T split_ea;
278 win_T *old_curwin = curwin;
279 term_T *term;
280 buf_T *old_curbuf = NULL;
281 int res;
282 buf_T *newbuf;
283
284 if (check_restricted() || check_secure())
285 return NULL;
286
287 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
288 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
289 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
290 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
291 {
292 EMSG(_(e_invarg));
293 return NULL;
294 }
295
296 term = (term_T *)alloc_clear(sizeof(term_T));
297 if (term == NULL)
298 return NULL;
299 term->tl_dirty_row_end = MAX_ROW;
300 term->tl_cursor_visible = TRUE;
301 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
302 term->tl_finish = opt->jo_term_finish;
303 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
304
305 vim_memset(&split_ea, 0, sizeof(split_ea));
306 if (opt->jo_curwin)
307 {
308 /* Create a new buffer in the current window. */
309 if (!can_abandon(curbuf, forceit))
310 {
311 no_write_message();
312 vim_free(term);
313 return NULL;
314 }
315 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
316 ECMD_HIDE + (forceit ? ECMD_FORCEIT : 0), curwin) == FAIL)
317 {
318 vim_free(term);
319 return NULL;
320 }
321 }
322 else if (opt->jo_hidden)
323 {
324 buf_T *buf;
325
326 /* Create a new buffer without a window. Make it the current buffer for
327 * a moment to be able to do the initialisations. */
328 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
329 BLN_NEW | BLN_LISTED);
330 if (buf == NULL || ml_open(buf) == FAIL)
331 {
332 vim_free(term);
333 return NULL;
334 }
335 old_curbuf = curbuf;
336 --curbuf->b_nwindows;
337 curbuf = buf;
338 curwin->w_buffer = buf;
339 ++curbuf->b_nwindows;
340 }
341 else
342 {
343 /* Open a new window or tab. */
344 split_ea.cmdidx = CMD_new;
345 split_ea.cmd = (char_u *)"new";
346 split_ea.arg = (char_u *)"";
347 if (opt->jo_term_rows > 0 && !(cmdmod.split & WSP_VERT))
348 {
349 split_ea.line2 = opt->jo_term_rows;
350 split_ea.addr_count = 1;
351 }
352 if (opt->jo_term_cols > 0 && (cmdmod.split & WSP_VERT))
353 {
354 split_ea.line2 = opt->jo_term_cols;
355 split_ea.addr_count = 1;
356 }
357
358 ex_splitview(&split_ea);
359 if (curwin == old_curwin)
360 {
361 /* split failed */
362 vim_free(term);
363 return NULL;
364 }
365 }
366 term->tl_buffer = curbuf;
367 curbuf->b_term = term;
368
369 if (!opt->jo_hidden)
370 {
371 /* only one size was taken care of with :new, do the other one */
372 if (opt->jo_term_rows > 0 && (cmdmod.split & WSP_VERT))
373 win_setheight(opt->jo_term_rows);
374 if (opt->jo_term_cols > 0 && !(cmdmod.split & WSP_VERT))
375 win_setwidth(opt->jo_term_cols);
376 }
377
378 /* Link the new terminal in the list of active terminals. */
379 term->tl_next = first_term;
380 first_term = term;
381
382 if (opt->jo_term_name != NULL)
383 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
384 else
385 {
386 int i;
387 size_t len;
388 char_u *cmd, *p;
389
390 if (argvar->v_type == VAR_STRING)
391 {
392 cmd = argvar->vval.v_string;
393 if (cmd == NULL)
394 cmd = (char_u *)"";
395 else if (STRCMP(cmd, "NONE") == 0)
396 cmd = (char_u *)"pty";
397 }
398 else if (argvar->v_type != VAR_LIST
399 || argvar->vval.v_list == NULL
400 || argvar->vval.v_list->lv_len < 1
401 || (cmd = get_tv_string_chk(
402 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
403 cmd = (char_u*)"";
404
405 len = STRLEN(cmd) + 10;
406 p = alloc((int)len);
407
408 for (i = 0; p != NULL; ++i)
409 {
410 /* Prepend a ! to the command name to avoid the buffer name equals
411 * the executable, otherwise ":w!" would overwrite it. */
412 if (i == 0)
413 vim_snprintf((char *)p, len, "!%s", cmd);
414 else
415 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
416 if (buflist_findname(p) == NULL)
417 {
418 vim_free(curbuf->b_ffname);
419 curbuf->b_ffname = p;
420 break;
421 }
422 }
423 }
424 curbuf->b_fname = curbuf->b_ffname;
425
426 if (opt->jo_term_opencmd != NULL)
427 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
428
429 if (opt->jo_eof_chars != NULL)
430 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
431
432 set_string_option_direct((char_u *)"buftype", -1,
433 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
434
435 /* Mark the buffer as not modifiable. It can only be made modifiable after
436 * the job finished. */
437 curbuf->b_p_ma = FALSE;
438
439 set_term_and_win_size(term);
440 setup_job_options(opt, term->tl_rows, term->tl_cols);
441
442 /* System dependent: setup the vterm and maybe start the job in it. */
443 if (argvar->v_type == VAR_STRING
444 && argvar->vval.v_string != NULL
445 && STRCMP(argvar->vval.v_string, "NONE") == 0)
446 res = create_pty_only(term, opt);
447 else
448 res = term_and_job_init(term, argvar, opt);
449
450 newbuf = curbuf;
451 if (res == OK)
452 {
453 /* Get and remember the size we ended up with. Update the pty. */
454 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
455 term_report_winsize(term, term->tl_rows, term->tl_cols);
456
457 /* Make sure we don't get stuck on sending keys to the job, it leads to
458 * a deadlock if the job is waiting for Vim to read. */
459 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
460
Bram Moolenaar8b21de32017-09-22 11:13:52 +0200461#ifdef FEAT_AUTOCMD
462 ++curbuf->b_locked;
463 apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf);
464 --curbuf->b_locked;
465#endif
466
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200467 if (old_curbuf != NULL)
468 {
469 --curbuf->b_nwindows;
470 curbuf = old_curbuf;
471 curwin->w_buffer = curbuf;
472 ++curbuf->b_nwindows;
473 }
474 }
475 else
476 {
477 buf_T *buf = curbuf;
478
479 free_terminal(curbuf);
480 if (old_curbuf != NULL)
481 {
482 --curbuf->b_nwindows;
483 curbuf = old_curbuf;
484 curwin->w_buffer = curbuf;
485 ++curbuf->b_nwindows;
486 }
487
488 /* Wiping out the buffer will also close the window and call
489 * free_terminal(). */
490 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
491 return NULL;
492 }
493 return newbuf;
494}
495
496/*
497 * ":terminal": open a terminal window and execute a job in it.
498 */
499 void
500ex_terminal(exarg_T *eap)
501{
502 typval_T argvar[2];
503 jobopt_T opt;
504 char_u *cmd;
505 char_u *tofree = NULL;
506
507 init_job_options(&opt);
508
509 cmd = eap->arg;
510 while (*cmd && *cmd == '+' && *(cmd + 1) == '+')
511 {
512 char_u *p, *ep;
513
514 cmd += 2;
515 p = skiptowhite(cmd);
516 ep = vim_strchr(cmd, '=');
517 if (ep != NULL && ep < p)
518 p = ep;
519
520 if ((int)(p - cmd) == 5 && STRNICMP(cmd, "close", 5) == 0)
521 opt.jo_term_finish = 'c';
522 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "open", 4) == 0)
523 opt.jo_term_finish = 'o';
524 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "curwin", 6) == 0)
525 opt.jo_curwin = 1;
526 else if ((int)(p - cmd) == 6 && STRNICMP(cmd, "hidden", 6) == 0)
527 opt.jo_hidden = 1;
528 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "rows", 4) == 0
529 && ep != NULL && isdigit(ep[1]))
530 {
531 opt.jo_set2 |= JO2_TERM_ROWS;
532 opt.jo_term_rows = atoi((char *)ep + 1);
533 p = skiptowhite(cmd);
534 }
535 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "cols", 4) == 0
536 && ep != NULL && isdigit(ep[1]))
537 {
538 opt.jo_set2 |= JO2_TERM_COLS;
539 opt.jo_term_cols = atoi((char *)ep + 1);
540 p = skiptowhite(cmd);
541 }
542 else if ((int)(p - cmd) == 3 && STRNICMP(cmd, "eof", 3) == 0
543 && ep != NULL)
544 {
545 char_u *buf = NULL;
546 char_u *keys;
547
548 p = skiptowhite(cmd);
549 *p = NUL;
550 keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE);
551 opt.jo_set2 |= JO2_EOF_CHARS;
552 opt.jo_eof_chars = vim_strsave(keys);
553 vim_free(buf);
554 *p = ' ';
555 }
556 else
557 {
558 if (*p)
559 *p = NUL;
560 EMSG2(_("E181: Invalid attribute: %s"), cmd);
561 return;
562 }
563 cmd = skipwhite(p);
564 }
565 if (*cmd == NUL)
566 /* Make a copy of 'shell', an autocommand may change the option. */
567 tofree = cmd = vim_strsave(p_sh);
568
569 if (eap->addr_count > 0)
570 {
571 /* Write lines from current buffer to the job. */
572 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
573 opt.jo_io[PART_IN] = JIO_BUFFER;
574 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
575 opt.jo_in_top = eap->line1;
576 opt.jo_in_bot = eap->line2;
577 }
578
579 argvar[0].v_type = VAR_STRING;
580 argvar[0].vval.v_string = cmd;
581 argvar[1].v_type = VAR_UNKNOWN;
582 term_start(argvar, &opt, eap->forceit);
583 vim_free(tofree);
584 vim_free(opt.jo_eof_chars);
585}
586
587/*
588 * Free the scrollback buffer for "term".
589 */
590 static void
591free_scrollback(term_T *term)
592{
593 int i;
594
595 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
596 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
597 ga_clear(&term->tl_scrollback);
598}
599
600/*
601 * Free a terminal and everything it refers to.
602 * Kills the job if there is one.
603 * Called when wiping out a buffer.
604 */
605 void
606free_terminal(buf_T *buf)
607{
608 term_T *term = buf->b_term;
609 term_T *tp;
610
611 if (term == NULL)
612 return;
613 if (first_term == term)
614 first_term = term->tl_next;
615 else
616 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
617 if (tp->tl_next == term)
618 {
619 tp->tl_next = term->tl_next;
620 break;
621 }
622
623 if (term->tl_job != NULL)
624 {
625 if (term->tl_job->jv_status != JOB_ENDED
626 && term->tl_job->jv_status != JOB_FINISHED
627 && term->tl_job->jv_status != JOB_FAILED)
628 job_stop(term->tl_job, NULL, "kill");
629 job_unref(term->tl_job);
630 }
631
632 free_scrollback(term);
633
634 term_free_vterm(term);
635 vim_free(term->tl_title);
636 vim_free(term->tl_status_text);
637 vim_free(term->tl_opencmd);
638 vim_free(term->tl_eof_chars);
639 vim_free(term->tl_cursor_color);
640 vim_free(term);
641 buf->b_term = NULL;
642 if (in_terminal_loop == term)
643 in_terminal_loop = NULL;
644}
645
646/*
647 * Write job output "msg[len]" to the vterm.
648 */
649 static void
650term_write_job_output(term_T *term, char_u *msg, size_t len)
651{
652 VTerm *vterm = term->tl_vterm;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200653
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100654 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200655
656 /* this invokes the damage callbacks */
657 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
658}
659
660 static void
661update_cursor(term_T *term, int redraw)
662{
663 if (term->tl_normal_mode)
664 return;
665 setcursor();
666 if (redraw)
667 {
668 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
669 cursor_on();
670 out_flush();
671#ifdef FEAT_GUI
672 if (gui.in_use)
673 gui_update_cursor(FALSE, FALSE);
674#endif
675 }
676}
677
678/*
679 * Invoked when "msg" output from a job was received. Write it to the terminal
680 * of "buffer".
681 */
682 void
683write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
684{
685 size_t len = STRLEN(msg);
686 term_T *term = buffer->b_term;
687
688 if (term->tl_vterm == NULL)
689 {
690 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
691 return;
692 }
693 ch_log(channel, "writing %d bytes to terminal", (int)len);
694 term_write_job_output(term, msg, len);
695
696 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
697 * contents, thus no screen update is needed. */
698 if (!term->tl_normal_mode)
699 {
700 /* TODO: only update once in a while. */
701 ch_log(term->tl_job->jv_channel, "updating screen");
702 if (buffer == curbuf)
703 {
704 update_screen(0);
705 update_cursor(term, TRUE);
706 }
707 else
708 redraw_after_callback(TRUE);
709 }
710}
711
712/*
713 * Send a mouse position and click to the vterm
714 */
715 static int
716term_send_mouse(VTerm *vterm, int button, int pressed)
717{
718 VTermModifier mod = VTERM_MOD_NONE;
719
720 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +0200721 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100722 if (button != 0)
723 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200724 return TRUE;
725}
726
727/*
728 * Convert typed key "c" into bytes to send to the job.
729 * Return the number of bytes in "buf".
730 */
731 static int
732term_convert_key(term_T *term, int c, char *buf)
733{
734 VTerm *vterm = term->tl_vterm;
735 VTermKey key = VTERM_KEY_NONE;
736 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +0100737 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200738
739 switch (c)
740 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100741 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
742
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200743 /* don't use VTERM_KEY_BACKSPACE, it always
744 * becomes 0x7f DEL */
745 case K_BS: c = term_backspace_char; break;
746
747 case ESC: key = VTERM_KEY_ESCAPE; break;
748 case K_DEL: key = VTERM_KEY_DEL; break;
749 case K_DOWN: key = VTERM_KEY_DOWN; break;
750 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
751 key = VTERM_KEY_DOWN; break;
752 case K_END: key = VTERM_KEY_END; break;
753 case K_S_END: mod = VTERM_MOD_SHIFT;
754 key = VTERM_KEY_END; break;
755 case K_C_END: mod = VTERM_MOD_CTRL;
756 key = VTERM_KEY_END; break;
757 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
758 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
759 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
760 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
761 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
762 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
763 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
764 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
765 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
766 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
767 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
768 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
769 case K_HOME: key = VTERM_KEY_HOME; break;
770 case K_S_HOME: mod = VTERM_MOD_SHIFT;
771 key = VTERM_KEY_HOME; break;
772 case K_C_HOME: mod = VTERM_MOD_CTRL;
773 key = VTERM_KEY_HOME; break;
774 case K_INS: key = VTERM_KEY_INS; break;
775 case K_K0: key = VTERM_KEY_KP_0; break;
776 case K_K1: key = VTERM_KEY_KP_1; break;
777 case K_K2: key = VTERM_KEY_KP_2; break;
778 case K_K3: key = VTERM_KEY_KP_3; break;
779 case K_K4: key = VTERM_KEY_KP_4; break;
780 case K_K5: key = VTERM_KEY_KP_5; break;
781 case K_K6: key = VTERM_KEY_KP_6; break;
782 case K_K7: key = VTERM_KEY_KP_7; break;
783 case K_K8: key = VTERM_KEY_KP_8; break;
784 case K_K9: key = VTERM_KEY_KP_9; break;
785 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
786 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
787 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
788 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
789 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
790 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
791 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
792 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
793 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
794 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
795 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
796 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
797 case K_LEFT: key = VTERM_KEY_LEFT; break;
798 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
799 key = VTERM_KEY_LEFT; break;
800 case K_C_LEFT: mod = VTERM_MOD_CTRL;
801 key = VTERM_KEY_LEFT; break;
802 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
803 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
804 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
805 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
806 key = VTERM_KEY_RIGHT; break;
807 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
808 key = VTERM_KEY_RIGHT; break;
809 case K_UP: key = VTERM_KEY_UP; break;
810 case K_S_UP: mod = VTERM_MOD_SHIFT;
811 key = VTERM_KEY_UP; break;
812 case TAB: key = VTERM_KEY_TAB; break;
813
Bram Moolenaara42ad572017-11-16 13:08:04 +0100814 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
815 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200816 case K_MOUSELEFT: /* TODO */ return 0;
817 case K_MOUSERIGHT: /* TODO */ return 0;
818
819 case K_LEFTMOUSE:
Bram Moolenaara42ad572017-11-16 13:08:04 +0100820 case K_LEFTMOUSE_NM: other = term_send_mouse(vterm, 1, 1); break;
821 case K_LEFTDRAG: other = term_send_mouse(vterm, 1, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200822 case K_LEFTRELEASE:
Bram Moolenaara42ad572017-11-16 13:08:04 +0100823 case K_LEFTRELEASE_NM: other = term_send_mouse(vterm, 1, 0); break;
Bram Moolenaar51b0f372017-11-18 18:52:04 +0100824 case K_MOUSEMOVE: other = term_send_mouse(vterm, 0, 0); break;
Bram Moolenaara42ad572017-11-16 13:08:04 +0100825 case K_MIDDLEMOUSE: other = term_send_mouse(vterm, 2, 1); break;
826 case K_MIDDLEDRAG: other = term_send_mouse(vterm, 2, 1); break;
827 case K_MIDDLERELEASE: other = term_send_mouse(vterm, 2, 0); break;
828 case K_RIGHTMOUSE: other = term_send_mouse(vterm, 3, 1); break;
829 case K_RIGHTDRAG: other = term_send_mouse(vterm, 3, 1); break;
830 case K_RIGHTRELEASE: other = term_send_mouse(vterm, 3, 0); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200831 case K_X1MOUSE: /* TODO */ return 0;
832 case K_X1DRAG: /* TODO */ return 0;
833 case K_X1RELEASE: /* TODO */ return 0;
834 case K_X2MOUSE: /* TODO */ return 0;
835 case K_X2DRAG: /* TODO */ return 0;
836 case K_X2RELEASE: /* TODO */ return 0;
837
838 case K_IGNORE: return 0;
839 case K_NOP: return 0;
840 case K_UNDO: return 0;
841 case K_HELP: return 0;
842 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
843 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
844 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
845 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
846 case K_SELECT: return 0;
847#ifdef FEAT_GUI
848 case K_VER_SCROLLBAR: return 0;
849 case K_HOR_SCROLLBAR: return 0;
850#endif
851#ifdef FEAT_GUI_TABLINE
852 case K_TABLINE: return 0;
853 case K_TABMENU: return 0;
854#endif
855#ifdef FEAT_NETBEANS_INTG
856 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
857#endif
858#ifdef FEAT_DND
859 case K_DROP: return 0;
860#endif
861#ifdef FEAT_AUTOCMD
862 case K_CURSORHOLD: return 0;
863#endif
Bram Moolenaara42ad572017-11-16 13:08:04 +0100864 case K_PS: vterm_keyboard_start_paste(vterm);
865 other = TRUE;
866 break;
867 case K_PE: vterm_keyboard_end_paste(vterm);
868 other = TRUE;
869 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200870 }
871
872 /*
873 * Convert special keys to vterm keys:
874 * - Write keys to vterm: vterm_keyboard_key()
875 * - Write output to channel.
876 * TODO: use mod_mask
877 */
878 if (key != VTERM_KEY_NONE)
879 /* Special key, let vterm convert it. */
880 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +0100881 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200882 /* Normal character, let vterm convert it. */
883 vterm_keyboard_unichar(vterm, c, mod);
884
885 /* Read back the converted escape sequence. */
886 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
887}
888
889/*
890 * Return TRUE if the job for "term" is still running.
891 */
892 int
893term_job_running(term_T *term)
894{
895 /* Also consider the job finished when the channel is closed, to avoid a
896 * race condition when updating the title. */
897 return term != NULL
898 && term->tl_job != NULL
899 && channel_is_open(term->tl_job->jv_channel)
900 && (term->tl_job->jv_status == JOB_STARTED
901 || term->tl_job->jv_channel->ch_keep_open);
902}
903
904/*
905 * Return TRUE if "term" has an active channel and used ":term NONE".
906 */
907 int
908term_none_open(term_T *term)
909{
910 /* Also consider the job finished when the channel is closed, to avoid a
911 * race condition when updating the title. */
912 return term != NULL
913 && term->tl_job != NULL
914 && channel_is_open(term->tl_job->jv_channel)
915 && term->tl_job->jv_channel->ch_keep_open;
916}
917
918/*
919 * Add the last line of the scrollback buffer to the buffer in the window.
920 */
921 static void
922add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
923{
924 buf_T *buf = term->tl_buffer;
925 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
926 linenr_T lnum = buf->b_ml.ml_line_count;
927
928#ifdef WIN3264
929 if (!enc_utf8 && enc_codepage > 0)
930 {
931 WCHAR *ret = NULL;
932 int length = 0;
933
934 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
935 &ret, &length);
936 if (ret != NULL)
937 {
938 WideCharToMultiByte_alloc(enc_codepage, 0,
939 ret, length, (char **)&text, &len, 0, 0);
940 vim_free(ret);
941 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
942 vim_free(text);
943 }
944 }
945 else
946#endif
947 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
948 if (empty)
949 {
950 /* Delete the empty line that was in the empty buffer. */
951 curbuf = buf;
952 ml_delete(1, FALSE);
953 curbuf = curwin->w_buffer;
954 }
955}
956
957 static void
958cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
959{
960 attr->width = cell->width;
961 attr->attrs = cell->attrs;
962 attr->fg = cell->fg;
963 attr->bg = cell->bg;
964}
965
966 static int
967equal_celattr(cellattr_T *a, cellattr_T *b)
968{
969 /* Comparing the colors should be sufficient. */
970 return a->fg.red == b->fg.red
971 && a->fg.green == b->fg.green
972 && a->fg.blue == b->fg.blue
973 && a->bg.red == b->bg.red
974 && a->bg.green == b->bg.green
975 && a->bg.blue == b->bg.blue;
976}
977
978
979/*
980 * Add the current lines of the terminal to scrollback and to the buffer.
981 * Called after the job has ended and when switching to Terminal-Normal mode.
982 */
983 static void
984move_terminal_to_buffer(term_T *term)
985{
986 win_T *wp;
987 int len;
988 int lines_skipped = 0;
989 VTermPos pos;
990 VTermScreenCell cell;
991 cellattr_T fill_attr, new_fill_attr;
992 cellattr_T *p;
993 VTermScreen *screen;
994
995 if (term->tl_vterm == NULL)
996 return;
997 screen = vterm_obtain_screen(term->tl_vterm);
998 fill_attr = new_fill_attr = term->tl_default_color;
999
1000 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1001 {
1002 len = 0;
1003 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1004 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1005 && cell.chars[0] != NUL)
1006 {
1007 len = pos.col + 1;
1008 new_fill_attr = term->tl_default_color;
1009 }
1010 else
1011 /* Assume the last attr is the filler attr. */
1012 cell2cellattr(&cell, &new_fill_attr);
1013
1014 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1015 ++lines_skipped;
1016 else
1017 {
1018 while (lines_skipped > 0)
1019 {
1020 /* Line was skipped, add an empty line. */
1021 --lines_skipped;
1022 if (ga_grow(&term->tl_scrollback, 1) == OK)
1023 {
1024 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1025 + term->tl_scrollback.ga_len;
1026
1027 line->sb_cols = 0;
1028 line->sb_cells = NULL;
1029 line->sb_fill_attr = fill_attr;
1030 ++term->tl_scrollback.ga_len;
1031
1032 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1033 }
1034 }
1035
1036 if (len == 0)
1037 p = NULL;
1038 else
1039 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
1040 if ((p != NULL || len == 0)
1041 && ga_grow(&term->tl_scrollback, 1) == OK)
1042 {
1043 garray_T ga;
1044 int width;
1045 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1046 + term->tl_scrollback.ga_len;
1047
1048 ga_init2(&ga, 1, 100);
1049 for (pos.col = 0; pos.col < len; pos.col += width)
1050 {
1051 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1052 {
1053 width = 1;
1054 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1055 if (ga_grow(&ga, 1) == OK)
1056 ga.ga_len += utf_char2bytes(' ',
1057 (char_u *)ga.ga_data + ga.ga_len);
1058 }
1059 else
1060 {
1061 width = cell.width;
1062
1063 cell2cellattr(&cell, &p[pos.col]);
1064
1065 if (ga_grow(&ga, MB_MAXBYTES) == OK)
1066 {
1067 int i;
1068 int c;
1069
1070 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1071 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1072 (char_u *)ga.ga_data + ga.ga_len);
1073 }
1074 }
1075 }
1076 line->sb_cols = len;
1077 line->sb_cells = p;
1078 line->sb_fill_attr = new_fill_attr;
1079 fill_attr = new_fill_attr;
1080 ++term->tl_scrollback.ga_len;
1081
1082 if (ga_grow(&ga, 1) == FAIL)
1083 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1084 else
1085 {
1086 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1087 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1088 }
1089 ga_clear(&ga);
1090 }
1091 else
1092 vim_free(p);
1093 }
1094 }
1095
1096 /* Obtain the current background color. */
1097 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1098 &term->tl_default_color.fg, &term->tl_default_color.bg);
1099
1100 FOR_ALL_WINDOWS(wp)
1101 {
1102 if (wp->w_buffer == term->tl_buffer)
1103 {
1104 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1105 wp->w_cursor.col = 0;
1106 wp->w_valid = 0;
1107 if (wp->w_cursor.lnum >= wp->w_height)
1108 {
1109 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
1110
1111 if (wp->w_topline < min_topline)
1112 wp->w_topline = min_topline;
1113 }
1114 redraw_win_later(wp, NOT_VALID);
1115 }
1116 }
1117}
1118
1119 static void
1120set_terminal_mode(term_T *term, int normal_mode)
1121{
1122 term->tl_normal_mode = normal_mode;
1123 vim_free(term->tl_status_text);
1124 term->tl_status_text = NULL;
1125 if (term->tl_buffer == curbuf)
1126 maketitle();
1127}
1128
1129/*
1130 * Called after the job if finished and Terminal mode is not active:
1131 * Move the vterm contents into the scrollback buffer and free the vterm.
1132 */
1133 static void
1134cleanup_vterm(term_T *term)
1135{
1136 if (term->tl_finish != 'c')
1137 move_terminal_to_buffer(term);
1138 term_free_vterm(term);
1139 set_terminal_mode(term, FALSE);
1140}
1141
1142/*
1143 * Switch from Terminal-Job mode to Terminal-Normal mode.
1144 * Suspends updating the terminal window.
1145 */
1146 static void
1147term_enter_normal_mode(void)
1148{
1149 term_T *term = curbuf->b_term;
1150
1151 /* Append the current terminal contents to the buffer. */
1152 move_terminal_to_buffer(term);
1153
1154 set_terminal_mode(term, TRUE);
1155
1156 /* Move the window cursor to the position of the cursor in the
1157 * terminal. */
1158 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1159 + term->tl_cursor_pos.row + 1;
1160 check_cursor();
1161 coladvance(term->tl_cursor_pos.col);
1162
1163 /* Display the same lines as in the terminal. */
1164 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1165}
1166
1167/*
1168 * Returns TRUE if the current window contains a terminal and we are in
1169 * Terminal-Normal mode.
1170 */
1171 int
1172term_in_normal_mode(void)
1173{
1174 term_T *term = curbuf->b_term;
1175
1176 return term != NULL && term->tl_normal_mode;
1177}
1178
1179/*
1180 * Switch from Terminal-Normal mode to Terminal-Job mode.
1181 * Restores updating the terminal window.
1182 */
1183 void
1184term_enter_job_mode()
1185{
1186 term_T *term = curbuf->b_term;
1187 sb_line_T *line;
1188 garray_T *gap;
1189
1190 /* Remove the terminal contents from the scrollback and the buffer. */
1191 gap = &term->tl_scrollback;
1192 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1193 && gap->ga_len > 0)
1194 {
1195 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1196 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1197 vim_free(line->sb_cells);
1198 --gap->ga_len;
1199 }
1200 check_cursor();
1201
1202 set_terminal_mode(term, FALSE);
1203
1204 if (term->tl_channel_closed)
1205 cleanup_vterm(term);
1206 redraw_buf_and_status_later(curbuf, NOT_VALID);
1207}
1208
1209/*
1210 * Get a key from the user without mapping.
1211 * Note: while waiting a terminal may be closed and freed if the channel is
1212 * closed and ++close was used.
1213 * Uses terminal mode mappings.
1214 */
1215 static int
1216term_vgetc()
1217{
1218 int c;
1219 int save_State = State;
1220
1221 State = TERMINAL;
1222 got_int = FALSE;
1223#ifdef WIN3264
1224 ctrl_break_was_pressed = FALSE;
1225#endif
1226 c = vgetc();
1227 got_int = FALSE;
1228 State = save_State;
1229 return c;
1230}
1231
1232/*
1233 * Get the part that is connected to the tty. Normally this is PART_IN, but
1234 * when writing buffer lines to the job it can be another. This makes it
1235 * possible to do "1,5term vim -".
1236 */
1237 static ch_part_T
1238get_tty_part(term_T *term)
1239{
1240#ifdef UNIX
1241 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
1242 int i;
1243
1244 for (i = 0; i < 3; ++i)
1245 {
1246 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
1247
1248 if (isatty(fd))
1249 return parts[i];
1250 }
1251#endif
1252 return PART_IN;
1253}
1254
1255/*
1256 * Send keys to terminal.
1257 * Return FAIL when the key needs to be handled in Normal mode.
1258 * Return OK when the key was dropped or sent to the terminal.
1259 */
1260 int
1261send_keys_to_term(term_T *term, int c, int typed)
1262{
1263 char msg[KEY_BUF_LEN];
1264 size_t len;
1265 static int mouse_was_outside = FALSE;
1266 int dragging_outside = FALSE;
1267
1268 /* Catch keys that need to be handled as in Normal mode. */
1269 switch (c)
1270 {
1271 case NUL:
1272 case K_ZERO:
1273 if (typed)
1274 stuffcharReadbuff(c);
1275 return FAIL;
1276
1277 case K_IGNORE:
1278 return FAIL;
1279
1280 case K_LEFTDRAG:
1281 case K_MIDDLEDRAG:
1282 case K_RIGHTDRAG:
1283 case K_X1DRAG:
1284 case K_X2DRAG:
1285 dragging_outside = mouse_was_outside;
1286 /* FALLTHROUGH */
1287 case K_LEFTMOUSE:
1288 case K_LEFTMOUSE_NM:
1289 case K_LEFTRELEASE:
1290 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001291 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001292 case K_MIDDLEMOUSE:
1293 case K_MIDDLERELEASE:
1294 case K_RIGHTMOUSE:
1295 case K_RIGHTRELEASE:
1296 case K_X1MOUSE:
1297 case K_X1RELEASE:
1298 case K_X2MOUSE:
1299 case K_X2RELEASE:
1300
1301 case K_MOUSEUP:
1302 case K_MOUSEDOWN:
1303 case K_MOUSELEFT:
1304 case K_MOUSERIGHT:
1305 if (mouse_row < W_WINROW(curwin)
Bram Moolenaar73675fb2017-11-20 21:49:19 +01001306 || mouse_row > (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02001307 || mouse_col < curwin->w_wincol
Bram Moolenaar73675fb2017-11-20 21:49:19 +01001308 || mouse_col > W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001309 || dragging_outside)
1310 {
1311 /* click or scroll outside the current window */
1312 if (typed)
1313 {
1314 stuffcharReadbuff(c);
1315 mouse_was_outside = TRUE;
1316 }
1317 return FAIL;
1318 }
1319 }
1320 if (typed)
1321 mouse_was_outside = FALSE;
1322
1323 /* Convert the typed key to a sequence of bytes for the job. */
1324 len = term_convert_key(term, c, msg);
1325 if (len > 0)
1326 /* TODO: if FAIL is returned, stop? */
1327 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1328 (char_u *)msg, (int)len, NULL);
1329
1330 return OK;
1331}
1332
1333 static void
1334position_cursor(win_T *wp, VTermPos *pos)
1335{
1336 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1337 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1338 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1339}
1340
1341/*
1342 * Handle CTRL-W "": send register contents to the job.
1343 */
1344 static void
1345term_paste_register(int prev_c UNUSED)
1346{
1347 int c;
1348 list_T *l;
1349 listitem_T *item;
1350 long reglen = 0;
1351 int type;
1352
1353#ifdef FEAT_CMDL_INFO
1354 if (add_to_showcmd(prev_c))
1355 if (add_to_showcmd('"'))
1356 out_flush();
1357#endif
1358 c = term_vgetc();
1359#ifdef FEAT_CMDL_INFO
1360 clear_showcmd();
1361#endif
1362 if (!term_use_loop())
1363 /* job finished while waiting for a character */
1364 return;
1365
1366 /* CTRL-W "= prompt for expression to evaluate. */
1367 if (c == '=' && get_expr_register() != '=')
1368 return;
1369 if (!term_use_loop())
1370 /* job finished while waiting for a character */
1371 return;
1372
1373 l = (list_T *)get_reg_contents(c, GREG_LIST);
1374 if (l != NULL)
1375 {
1376 type = get_reg_type(c, &reglen);
1377 for (item = l->lv_first; item != NULL; item = item->li_next)
1378 {
1379 char_u *s = get_tv_string(&item->li_tv);
1380#ifdef WIN3264
1381 char_u *tmp = s;
1382
1383 if (!enc_utf8 && enc_codepage > 0)
1384 {
1385 WCHAR *ret = NULL;
1386 int length = 0;
1387
1388 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
1389 (int)STRLEN(s), &ret, &length);
1390 if (ret != NULL)
1391 {
1392 WideCharToMultiByte_alloc(CP_UTF8, 0,
1393 ret, length, (char **)&s, &length, 0, 0);
1394 vim_free(ret);
1395 }
1396 }
1397#endif
1398 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1399 s, (int)STRLEN(s), NULL);
1400#ifdef WIN3264
1401 if (tmp != s)
1402 vim_free(s);
1403#endif
1404
1405 if (item->li_next != NULL || type == MLINE)
1406 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
1407 (char_u *)"\r", 1, NULL);
1408 }
1409 list_free(l);
1410 }
1411}
1412
1413#if defined(FEAT_GUI) || defined(PROTO)
1414/*
1415 * Return TRUE when the cursor of the terminal should be displayed.
1416 */
1417 int
1418terminal_is_active()
1419{
1420 return in_terminal_loop != NULL;
1421}
1422
1423 cursorentry_T *
1424term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
1425{
1426 term_T *term = in_terminal_loop;
1427 static cursorentry_T entry;
1428
1429 vim_memset(&entry, 0, sizeof(entry));
1430 entry.shape = entry.mshape =
1431 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
1432 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
1433 SHAPE_BLOCK;
1434 entry.percentage = 20;
1435 if (term->tl_cursor_blink)
1436 {
1437 entry.blinkwait = 700;
1438 entry.blinkon = 400;
1439 entry.blinkoff = 250;
1440 }
1441 *fg = gui.back_pixel;
1442 if (term->tl_cursor_color == NULL)
1443 *bg = gui.norm_pixel;
1444 else
1445 *bg = color_name2handle(term->tl_cursor_color);
1446 entry.name = "n";
1447 entry.used_for = SHAPE_CURSOR;
1448
1449 return &entry;
1450}
1451#endif
1452
1453static int did_change_cursor = FALSE;
1454
1455 static void
1456may_set_cursor_props(term_T *term)
1457{
1458#ifdef FEAT_GUI
1459 /* For the GUI the cursor properties are obtained with
1460 * term_get_cursor_shape(). */
1461 if (gui.in_use)
1462 return;
1463#endif
1464 if (in_terminal_loop == term)
1465 {
1466 did_change_cursor = TRUE;
1467 if (term->tl_cursor_color != NULL)
1468 term_cursor_color(term->tl_cursor_color);
1469 else
1470 term_cursor_color((char_u *)"");
1471 term_cursor_shape(term->tl_cursor_shape, term->tl_cursor_blink);
1472 }
1473}
1474
1475 static void
1476may_restore_cursor_props(void)
1477{
1478#ifdef FEAT_GUI
1479 if (gui.in_use)
1480 return;
1481#endif
1482 if (did_change_cursor)
1483 {
1484 did_change_cursor = FALSE;
1485 term_cursor_color((char_u *)"");
1486 /* this will restore the initial cursor style, if possible */
1487 ui_cursor_shape_forced(TRUE);
1488 }
1489}
1490
1491/*
1492 * Returns TRUE if the current window contains a terminal and we are sending
1493 * keys to the job.
1494 */
1495 int
1496term_use_loop(void)
1497{
1498 term_T *term = curbuf->b_term;
1499
1500 return term != NULL
1501 && !term->tl_normal_mode
1502 && term->tl_vterm != NULL
1503 && term_job_running(term);
1504}
1505
1506/*
1507 * Wait for input and send it to the job.
1508 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
1509 * when there is no more typahead.
1510 * Return when the start of a CTRL-W command is typed or anything else that
1511 * should be handled as a Normal mode command.
1512 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
1513 * the terminal was closed.
1514 */
1515 int
1516terminal_loop(int blocking)
1517{
1518 int c;
1519 int termkey = 0;
1520 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01001521#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001522 int tty_fd = curbuf->b_term->tl_job->jv_channel
1523 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01001524#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001525
1526 /* Remember the terminal we are sending keys to. However, the terminal
1527 * might be closed while waiting for a character, e.g. typing "exit" in a
1528 * shell and ++close was used. Therefore use curbuf->b_term instead of a
1529 * stored reference. */
1530 in_terminal_loop = curbuf->b_term;
1531
1532 if (*curwin->w_p_tk != NUL)
1533 termkey = string_to_key(curwin->w_p_tk, TRUE);
1534 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
1535 may_set_cursor_props(curbuf->b_term);
1536
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001537 while (blocking || vpeekc() != NUL)
1538 {
1539 /* TODO: skip screen update when handling a sequence of keys. */
1540 /* Repeat redrawing in case a message is received while redrawing. */
1541 while (must_redraw != 0)
1542 if (update_screen(0) == FAIL)
1543 break;
1544 update_cursor(curbuf->b_term, FALSE);
1545
1546 c = term_vgetc();
1547 if (!term_use_loop())
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001548 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001549 /* Job finished while waiting for a character. Push back the
1550 * received character. */
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001551 if (c != K_IGNORE)
1552 vungetc(c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001553 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01001554 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001555 if (c == K_IGNORE)
1556 continue;
1557
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001558#ifdef UNIX
1559 /*
1560 * The shell or another program may change the tty settings. Getting
1561 * them for every typed character is a bit of overhead, but it's needed
1562 * for the first character typed, e.g. when Vim starts in a shell.
1563 */
1564 if (isatty(tty_fd))
1565 {
1566 ttyinfo_T info;
1567
1568 /* Get the current backspace character of the pty. */
1569 if (get_tty_info(tty_fd, &info) == OK)
1570 term_backspace_char = info.backspace;
1571 }
1572#endif
1573
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001574#ifdef WIN3264
1575 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
1576 * Use CTRL-BREAK to kill the job. */
1577 if (ctrl_break_was_pressed)
1578 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
1579#endif
1580 /* Was either CTRL-W (termkey) or CTRL-\ pressed? */
1581 if (c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL)
1582 {
1583 int prev_c = c;
1584
1585#ifdef FEAT_CMDL_INFO
1586 if (add_to_showcmd(c))
1587 out_flush();
1588#endif
1589 c = term_vgetc();
1590#ifdef FEAT_CMDL_INFO
1591 clear_showcmd();
1592#endif
1593 if (!term_use_loop())
1594 /* job finished while waiting for a character */
1595 break;
1596
1597 if (prev_c == Ctrl_BSL)
1598 {
1599 if (c == Ctrl_N)
1600 {
1601 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
1602 term_enter_normal_mode();
1603 ret = FAIL;
1604 goto theend;
1605 }
1606 /* Send both keys to the terminal. */
1607 send_keys_to_term(curbuf->b_term, prev_c, TRUE);
1608 }
1609 else if (c == Ctrl_C)
1610 {
1611 /* "CTRL-W CTRL-C" or 'termkey' CTRL-C: end the job */
1612 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
1613 }
1614 else if (termkey == 0 && c == '.')
1615 {
1616 /* "CTRL-W .": send CTRL-W to the job */
1617 c = Ctrl_W;
1618 }
1619 else if (c == 'N')
1620 {
1621 /* CTRL-W N : go to Terminal-Normal mode. */
1622 term_enter_normal_mode();
1623 ret = FAIL;
1624 goto theend;
1625 }
1626 else if (c == '"')
1627 {
1628 term_paste_register(prev_c);
1629 continue;
1630 }
1631 else if (termkey == 0 || c != termkey)
1632 {
1633 stuffcharReadbuff(Ctrl_W);
1634 stuffcharReadbuff(c);
1635 ret = OK;
1636 goto theend;
1637 }
1638 }
1639# ifdef WIN3264
1640 if (!enc_utf8 && has_mbyte && c >= 0x80)
1641 {
1642 WCHAR wc;
1643 char_u mb[3];
1644
1645 mb[0] = (unsigned)c >> 8;
1646 mb[1] = c;
1647 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
1648 c = wc;
1649 }
1650# endif
1651 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
1652 {
1653 ret = OK;
1654 goto theend;
1655 }
1656 }
1657 ret = FAIL;
1658
1659theend:
1660 in_terminal_loop = NULL;
1661 may_restore_cursor_props();
1662 return ret;
1663}
1664
1665/*
1666 * Called when a job has finished.
1667 * This updates the title and status, but does not close the vterm, because
1668 * there might still be pending output in the channel.
1669 */
1670 void
1671term_job_ended(job_T *job)
1672{
1673 term_T *term;
1674 int did_one = FALSE;
1675
1676 for (term = first_term; term != NULL; term = term->tl_next)
1677 if (term->tl_job == job)
1678 {
1679 vim_free(term->tl_title);
1680 term->tl_title = NULL;
1681 vim_free(term->tl_status_text);
1682 term->tl_status_text = NULL;
1683 redraw_buf_and_status_later(term->tl_buffer, VALID);
1684 did_one = TRUE;
1685 }
1686 if (did_one)
1687 redraw_statuslines();
1688 if (curbuf->b_term != NULL)
1689 {
1690 if (curbuf->b_term->tl_job == job)
1691 maketitle();
1692 update_cursor(curbuf->b_term, TRUE);
1693 }
1694}
1695
1696 static void
1697may_toggle_cursor(term_T *term)
1698{
1699 if (in_terminal_loop == term)
1700 {
1701 if (term->tl_cursor_visible)
1702 cursor_on();
1703 else
1704 cursor_off();
1705 }
1706}
1707
1708/*
1709 * Reverse engineer the RGB value into a cterm color index.
1710 * First color is 1. Return 0 if no match found.
1711 */
1712 static int
1713color2index(VTermColor *color, int fg, int *boldp)
1714{
1715 int red = color->red;
1716 int blue = color->blue;
1717 int green = color->green;
1718
1719 /* The argument for lookup_color() is for the color_names[] table. */
1720 if (red == 0)
1721 {
1722 if (green == 0)
1723 {
1724 if (blue == 0)
1725 return lookup_color(0, fg, boldp) + 1; /* black */
1726 if (blue == 224)
1727 return lookup_color(1, fg, boldp) + 1; /* dark blue */
1728 }
1729 else if (green == 224)
1730 {
1731 if (blue == 0)
1732 return lookup_color(2, fg, boldp) + 1; /* dark green */
1733 if (blue == 224)
1734 return lookup_color(3, fg, boldp) + 1; /* dark cyan */
1735 }
1736 }
1737 else if (red == 224)
1738 {
1739 if (green == 0)
1740 {
1741 if (blue == 0)
1742 return lookup_color(4, fg, boldp) + 1; /* dark red */
1743 if (blue == 224)
1744 return lookup_color(5, fg, boldp) + 1; /* dark magenta */
1745 }
1746 else if (green == 224)
1747 {
1748 if (blue == 0)
1749 return lookup_color(6, fg, boldp) + 1; /* dark yellow / brown */
1750 if (blue == 224)
1751 return lookup_color(8, fg, boldp) + 1; /* white / light grey */
1752 }
1753 }
1754 else if (red == 128)
1755 {
1756 if (green == 128 && blue == 128)
1757 return lookup_color(12, fg, boldp) + 1; /* dark grey */
1758 }
1759 else if (red == 255)
1760 {
1761 if (green == 64)
1762 {
1763 if (blue == 64)
1764 return lookup_color(20, fg, boldp) + 1; /* light red */
1765 if (blue == 255)
1766 return lookup_color(22, fg, boldp) + 1; /* light magenta */
1767 }
1768 else if (green == 255)
1769 {
1770 if (blue == 64)
1771 return lookup_color(24, fg, boldp) + 1; /* yellow */
1772 if (blue == 255)
1773 return lookup_color(26, fg, boldp) + 1; /* white */
1774 }
1775 }
1776 else if (red == 64)
1777 {
1778 if (green == 64)
1779 {
1780 if (blue == 255)
1781 return lookup_color(14, fg, boldp) + 1; /* light blue */
1782 }
1783 else if (green == 255)
1784 {
1785 if (blue == 64)
1786 return lookup_color(16, fg, boldp) + 1; /* light green */
1787 if (blue == 255)
1788 return lookup_color(18, fg, boldp) + 1; /* light cyan */
1789 }
1790 }
1791 if (t_colors >= 256)
1792 {
1793 if (red == blue && red == green)
1794 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001795 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001796 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001797 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
1798 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
1799 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001800 int i;
1801
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001802 if (red < 5)
1803 return 17; /* 00/00/00 */
1804 if (red > 245) /* ff/ff/ff */
1805 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001806 for (i = 0; i < 23; ++i)
1807 if (red < cutoff[i])
1808 return i + 233;
1809 return 256;
1810 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001811 {
1812 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
1813 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001814
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02001815 /* 216-color cube */
1816 for (ri = 0; ri < 5; ++ri)
1817 if (red < cutoff[ri])
1818 break;
1819 for (gi = 0; gi < 5; ++gi)
1820 if (green < cutoff[gi])
1821 break;
1822 for (bi = 0; bi < 5; ++bi)
1823 if (blue < cutoff[bi])
1824 break;
1825 return 17 + ri * 36 + gi * 6 + bi;
1826 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001827 }
1828 return 0;
1829}
1830
1831/*
1832 * Convert the attributes of a vterm cell into an attribute index.
1833 */
1834 static int
1835cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
1836{
1837 int attr = 0;
1838
1839 if (cellattrs.bold)
1840 attr |= HL_BOLD;
1841 if (cellattrs.underline)
1842 attr |= HL_UNDERLINE;
1843 if (cellattrs.italic)
1844 attr |= HL_ITALIC;
1845 if (cellattrs.strike)
1846 attr |= HL_STRIKETHROUGH;
1847 if (cellattrs.reverse)
1848 attr |= HL_INVERSE;
1849
1850#ifdef FEAT_GUI
1851 if (gui.in_use)
1852 {
1853 guicolor_T fg, bg;
1854
1855 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
1856 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
1857 return get_gui_attr_idx(attr, fg, bg);
1858 }
1859 else
1860#endif
1861#ifdef FEAT_TERMGUICOLORS
1862 if (p_tgc)
1863 {
1864 guicolor_T fg, bg;
1865
1866 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
1867 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
1868
1869 return get_tgc_attr_idx(attr, fg, bg);
1870 }
1871 else
1872#endif
1873 {
1874 int bold = MAYBE;
1875 int fg = color2index(&cellfg, TRUE, &bold);
1876 int bg = color2index(&cellbg, FALSE, &bold);
1877
1878 /* with 8 colors set the bold attribute to get a bright foreground */
1879 if (bold == TRUE)
1880 attr |= HL_BOLD;
1881 return get_cterm_attr_idx(attr, fg, bg);
1882 }
1883 return 0;
1884}
1885
1886 static int
1887handle_damage(VTermRect rect, void *user)
1888{
1889 term_T *term = (term_T *)user;
1890
1891 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
1892 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
1893 redraw_buf_later(term->tl_buffer, NOT_VALID);
1894 return 1;
1895}
1896
1897 static int
1898handle_moverect(VTermRect dest, VTermRect src, void *user)
1899{
1900 term_T *term = (term_T *)user;
1901
1902 /* Scrolling up is done much more efficiently by deleting lines instead of
1903 * redrawing the text. */
1904 if (dest.start_col == src.start_col
1905 && dest.end_col == src.end_col
1906 && dest.start_row < src.start_row)
1907 {
1908 win_T *wp;
1909 VTermColor fg, bg;
1910 VTermScreenCellAttrs attr;
1911 int clear_attr;
1912
1913 /* Set the color to clear lines with. */
1914 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1915 &fg, &bg);
1916 vim_memset(&attr, 0, sizeof(attr));
1917 clear_attr = cell2attr(attr, fg, bg);
1918
1919 FOR_ALL_WINDOWS(wp)
1920 {
1921 if (wp->w_buffer == term->tl_buffer)
1922 win_del_lines(wp, dest.start_row,
1923 src.start_row - dest.start_row, FALSE, FALSE,
1924 clear_attr);
1925 }
1926 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02001927
1928 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
1929 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
1930
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001931 redraw_buf_later(term->tl_buffer, NOT_VALID);
1932 return 1;
1933}
1934
1935 static int
1936handle_movecursor(
1937 VTermPos pos,
1938 VTermPos oldpos UNUSED,
1939 int visible,
1940 void *user)
1941{
1942 term_T *term = (term_T *)user;
1943 win_T *wp;
1944
1945 term->tl_cursor_pos = pos;
1946 term->tl_cursor_visible = visible;
1947
1948 FOR_ALL_WINDOWS(wp)
1949 {
1950 if (wp->w_buffer == term->tl_buffer)
1951 position_cursor(wp, &pos);
1952 }
1953 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
1954 {
1955 may_toggle_cursor(term);
1956 update_cursor(term, term->tl_cursor_visible);
1957 }
1958
1959 return 1;
1960}
1961
1962 static int
1963handle_settermprop(
1964 VTermProp prop,
1965 VTermValue *value,
1966 void *user)
1967{
1968 term_T *term = (term_T *)user;
1969
1970 switch (prop)
1971 {
1972 case VTERM_PROP_TITLE:
1973 vim_free(term->tl_title);
1974 /* a blank title isn't useful, make it empty, so that "running" is
1975 * displayed */
1976 if (*skipwhite((char_u *)value->string) == NUL)
1977 term->tl_title = NULL;
1978#ifdef WIN3264
1979 else if (!enc_utf8 && enc_codepage > 0)
1980 {
1981 WCHAR *ret = NULL;
1982 int length = 0;
1983
1984 MultiByteToWideChar_alloc(CP_UTF8, 0,
1985 (char*)value->string, (int)STRLEN(value->string),
1986 &ret, &length);
1987 if (ret != NULL)
1988 {
1989 WideCharToMultiByte_alloc(enc_codepage, 0,
1990 ret, length, (char**)&term->tl_title,
1991 &length, 0, 0);
1992 vim_free(ret);
1993 }
1994 }
1995#endif
1996 else
1997 term->tl_title = vim_strsave((char_u *)value->string);
1998 vim_free(term->tl_status_text);
1999 term->tl_status_text = NULL;
2000 if (term == curbuf->b_term)
2001 maketitle();
2002 break;
2003
2004 case VTERM_PROP_CURSORVISIBLE:
2005 term->tl_cursor_visible = value->boolean;
2006 may_toggle_cursor(term);
2007 out_flush();
2008 break;
2009
2010 case VTERM_PROP_CURSORBLINK:
2011 term->tl_cursor_blink = value->boolean;
2012 may_set_cursor_props(term);
2013 break;
2014
2015 case VTERM_PROP_CURSORSHAPE:
2016 term->tl_cursor_shape = value->number;
2017 may_set_cursor_props(term);
2018 break;
2019
2020 case VTERM_PROP_CURSORCOLOR:
2021 vim_free(term->tl_cursor_color);
2022 if (*value->string == NUL)
2023 term->tl_cursor_color = NULL;
2024 else
2025 term->tl_cursor_color = vim_strsave((char_u *)value->string);
2026 may_set_cursor_props(term);
2027 break;
2028
2029 case VTERM_PROP_ALTSCREEN:
2030 /* TODO: do anything else? */
2031 term->tl_using_altscreen = value->boolean;
2032 break;
2033
2034 default:
2035 break;
2036 }
2037 /* Always return 1, otherwise vterm doesn't store the value internally. */
2038 return 1;
2039}
2040
2041/*
2042 * The job running in the terminal resized the terminal.
2043 */
2044 static int
2045handle_resize(int rows, int cols, void *user)
2046{
2047 term_T *term = (term_T *)user;
2048 win_T *wp;
2049
2050 term->tl_rows = rows;
2051 term->tl_cols = cols;
2052 if (term->tl_vterm_size_changed)
2053 /* Size was set by vterm_set_size(), don't set the window size. */
2054 term->tl_vterm_size_changed = FALSE;
2055 else
2056 {
2057 FOR_ALL_WINDOWS(wp)
2058 {
2059 if (wp->w_buffer == term->tl_buffer)
2060 {
2061 win_setheight_win(rows, wp);
2062 win_setwidth_win(cols, wp);
2063 }
2064 }
2065 redraw_buf_later(term->tl_buffer, NOT_VALID);
2066 }
2067 return 1;
2068}
2069
2070/*
2071 * Handle a line that is pushed off the top of the screen.
2072 */
2073 static int
2074handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2075{
2076 term_T *term = (term_T *)user;
2077
2078 /* TODO: Limit the number of lines that are stored. */
2079 if (ga_grow(&term->tl_scrollback, 1) == OK)
2080 {
2081 cellattr_T *p = NULL;
2082 int len = 0;
2083 int i;
2084 int c;
2085 int col;
2086 sb_line_T *line;
2087 garray_T ga;
2088 cellattr_T fill_attr = term->tl_default_color;
2089
2090 /* do not store empty cells at the end */
2091 for (i = 0; i < cols; ++i)
2092 if (cells[i].chars[0] != 0)
2093 len = i + 1;
2094 else
2095 cell2cellattr(&cells[i], &fill_attr);
2096
2097 ga_init2(&ga, 1, 100);
2098 if (len > 0)
2099 p = (cellattr_T *)alloc((int)sizeof(cellattr_T) * len);
2100 if (p != NULL)
2101 {
2102 for (col = 0; col < len; col += cells[col].width)
2103 {
2104 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
2105 {
2106 ga.ga_len = 0;
2107 break;
2108 }
2109 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
2110 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
2111 (char_u *)ga.ga_data + ga.ga_len);
2112 cell2cellattr(&cells[col], &p[col]);
2113 }
2114 }
2115 if (ga_grow(&ga, 1) == FAIL)
2116 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2117 else
2118 {
2119 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
2120 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
2121 }
2122 ga_clear(&ga);
2123
2124 line = (sb_line_T *)term->tl_scrollback.ga_data
2125 + term->tl_scrollback.ga_len;
2126 line->sb_cols = len;
2127 line->sb_cells = p;
2128 line->sb_fill_attr = fill_attr;
2129 ++term->tl_scrollback.ga_len;
2130 ++term->tl_scrollback_scrolled;
2131 }
2132 return 0; /* ignored */
2133}
2134
2135static VTermScreenCallbacks screen_callbacks = {
2136 handle_damage, /* damage */
2137 handle_moverect, /* moverect */
2138 handle_movecursor, /* movecursor */
2139 handle_settermprop, /* settermprop */
2140 NULL, /* bell */
2141 handle_resize, /* resize */
2142 handle_pushline, /* sb_pushline */
2143 NULL /* sb_popline */
2144};
2145
2146/*
2147 * Called when a channel has been closed.
2148 * If this was a channel for a terminal window then finish it up.
2149 */
2150 void
2151term_channel_closed(channel_T *ch)
2152{
2153 term_T *term;
2154 int did_one = FALSE;
2155
2156 for (term = first_term; term != NULL; term = term->tl_next)
2157 if (term->tl_job == ch->ch_job)
2158 {
2159 term->tl_channel_closed = TRUE;
2160 did_one = TRUE;
2161
2162 vim_free(term->tl_title);
2163 term->tl_title = NULL;
2164 vim_free(term->tl_status_text);
2165 term->tl_status_text = NULL;
2166
2167 /* Unless in Terminal-Normal mode: clear the vterm. */
2168 if (!term->tl_normal_mode)
2169 {
2170 int fnum = term->tl_buffer->b_fnum;
2171
2172 cleanup_vterm(term);
2173
2174 if (term->tl_finish == 'c')
2175 {
Bram Moolenaarff546792017-11-21 14:47:57 +01002176 aco_save_T aco;
2177
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002178 /* ++close or term_finish == "close" */
2179 ch_log(NULL, "terminal job finished, closing window");
Bram Moolenaarff546792017-11-21 14:47:57 +01002180 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002181 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaarff546792017-11-21 14:47:57 +01002182 aucmd_restbuf(&aco);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002183 break;
2184 }
2185 if (term->tl_finish == 'o' && term->tl_buffer->b_nwindows == 0)
2186 {
2187 char buf[50];
2188
2189 /* TODO: use term_opencmd */
2190 ch_log(NULL, "terminal job finished, opening window");
2191 vim_snprintf(buf, sizeof(buf),
2192 term->tl_opencmd == NULL
2193 ? "botright sbuf %d"
2194 : (char *)term->tl_opencmd, fnum);
2195 do_cmdline_cmd((char_u *)buf);
2196 }
2197 else
2198 ch_log(NULL, "terminal job finished");
2199 }
2200
2201 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
2202 }
2203 if (did_one)
2204 {
2205 redraw_statuslines();
2206
2207 /* Need to break out of vgetc(). */
2208 ins_char_typebuf(K_IGNORE);
2209 typebuf_was_filled = TRUE;
2210
2211 term = curbuf->b_term;
2212 if (term != NULL)
2213 {
2214 if (term->tl_job == ch->ch_job)
2215 maketitle();
2216 update_cursor(term, term->tl_cursor_visible);
2217 }
2218 }
2219}
2220
2221/*
2222 * Called to update a window that contains an active terminal.
2223 * Returns FAIL when there is no terminal running in this window or in
2224 * Terminal-Normal mode.
2225 */
2226 int
2227term_update_window(win_T *wp)
2228{
2229 term_T *term = wp->w_buffer->b_term;
2230 VTerm *vterm;
2231 VTermScreen *screen;
2232 VTermState *state;
2233 VTermPos pos;
2234
2235 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode)
2236 return FAIL;
2237
2238 vterm = term->tl_vterm;
2239 screen = vterm_obtain_screen(vterm);
2240 state = vterm_obtain_state(vterm);
2241
Bram Moolenaar54e5dbf2017-10-07 17:35:09 +02002242 if (wp->w_redr_type >= SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02002243 {
2244 term->tl_dirty_row_start = 0;
2245 term->tl_dirty_row_end = MAX_ROW;
2246 }
2247
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002248 /*
2249 * If the window was resized a redraw will be triggered and we get here.
2250 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
2251 */
2252 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
2253 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
2254 {
2255 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
2256 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
2257 win_T *twp;
2258
2259 FOR_ALL_WINDOWS(twp)
2260 {
2261 /* When more than one window shows the same terminal, use the
2262 * smallest size. */
2263 if (twp->w_buffer == term->tl_buffer)
2264 {
2265 if (!term->tl_rows_fixed && rows > twp->w_height)
2266 rows = twp->w_height;
2267 if (!term->tl_cols_fixed && cols > twp->w_width)
2268 cols = twp->w_width;
2269 }
2270 }
2271
2272 term->tl_vterm_size_changed = TRUE;
2273 vterm_set_size(vterm, rows, cols);
2274 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
2275 rows);
2276 term_report_winsize(term, rows, cols);
2277 }
2278
2279 /* The cursor may have been moved when resizing. */
2280 vterm_state_get_cursorpos(state, &pos);
2281 position_cursor(wp, &pos);
2282
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002283 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
2284 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002285 {
2286 int off = screen_get_current_line_off();
2287 int max_col = MIN(wp->w_width, term->tl_cols);
2288
2289 if (pos.row < term->tl_rows)
2290 {
2291 for (pos.col = 0; pos.col < max_col; )
2292 {
2293 VTermScreenCell cell;
2294 int c;
2295
2296 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
2297 vim_memset(&cell, 0, sizeof(cell));
2298
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002299 c = cell.chars[0];
2300 if (c == NUL)
2301 {
2302 ScreenLines[off] = ' ';
2303 if (enc_utf8)
2304 ScreenLinesUC[off] = NUL;
2305 }
2306 else
2307 {
2308 if (enc_utf8)
2309 {
Bram Moolenaar6daeef12017-10-15 22:56:49 +02002310 int i;
2311
2312 /* composing chars */
2313 for (i = 0; i < Screen_mco
2314 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
2315 {
2316 ScreenLinesC[i][off] = cell.chars[i + 1];
2317 if (cell.chars[i + 1] == 0)
2318 break;
2319 }
2320 if (c >= 0x80 || (Screen_mco > 0
2321 && ScreenLinesC[0][off] != 0))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002322 {
2323 ScreenLines[off] = ' ';
2324 ScreenLinesUC[off] = c;
2325 }
2326 else
2327 {
2328 ScreenLines[off] = c;
2329 ScreenLinesUC[off] = NUL;
2330 }
2331 }
2332#ifdef WIN3264
2333 else if (has_mbyte && c >= 0x80)
2334 {
2335 char_u mb[MB_MAXBYTES+1];
2336 WCHAR wc = c;
2337
2338 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
2339 (char*)mb, 2, 0, 0) > 1)
2340 {
2341 ScreenLines[off] = mb[0];
2342 ScreenLines[off + 1] = mb[1];
2343 cell.width = mb_ptr2cells(mb);
2344 }
2345 else
2346 ScreenLines[off] = c;
2347 }
2348#endif
2349 else
2350 ScreenLines[off] = c;
2351 }
2352 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
2353
2354 ++pos.col;
2355 ++off;
2356 if (cell.width == 2)
2357 {
2358 if (enc_utf8)
2359 ScreenLinesUC[off] = NUL;
2360
2361 /* don't set the second byte to NUL for a DBCS encoding, it
2362 * has been set above */
2363 if (enc_utf8 || !has_mbyte)
2364 ScreenLines[off] = NUL;
2365
2366 ++pos.col;
2367 ++off;
2368 }
2369 }
2370 }
2371 else
2372 pos.col = 0;
2373
2374 screen_line(wp->w_winrow + pos.row, wp->w_wincol,
2375 pos.col, wp->w_width, FALSE);
2376 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002377 term->tl_dirty_row_start = MAX_ROW;
2378 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002379
2380 return OK;
2381}
2382
2383/*
2384 * Return TRUE if "wp" is a terminal window where the job has finished.
2385 */
2386 int
2387term_is_finished(buf_T *buf)
2388{
2389 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
2390}
2391
2392/*
2393 * Return TRUE if "wp" is a terminal window where the job has finished or we
2394 * are in Terminal-Normal mode, thus we show the buffer contents.
2395 */
2396 int
2397term_show_buffer(buf_T *buf)
2398{
2399 term_T *term = buf->b_term;
2400
2401 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
2402}
2403
2404/*
2405 * The current buffer is going to be changed. If there is terminal
2406 * highlighting remove it now.
2407 */
2408 void
2409term_change_in_curbuf(void)
2410{
2411 term_T *term = curbuf->b_term;
2412
2413 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
2414 {
2415 free_scrollback(term);
2416 redraw_buf_later(term->tl_buffer, NOT_VALID);
2417
2418 /* The buffer is now like a normal buffer, it cannot be easily
2419 * abandoned when changed. */
2420 set_string_option_direct((char_u *)"buftype", -1,
2421 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
2422 }
2423}
2424
2425/*
2426 * Get the screen attribute for a position in the buffer.
2427 * Use a negative "col" to get the filler background color.
2428 */
2429 int
2430term_get_attr(buf_T *buf, linenr_T lnum, int col)
2431{
2432 term_T *term = buf->b_term;
2433 sb_line_T *line;
2434 cellattr_T *cellattr;
2435
2436 if (lnum > term->tl_scrollback.ga_len)
2437 cellattr = &term->tl_default_color;
2438 else
2439 {
2440 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
2441 if (col < 0 || col >= line->sb_cols)
2442 cellattr = &line->sb_fill_attr;
2443 else
2444 cellattr = line->sb_cells + col;
2445 }
2446 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
2447}
2448
2449static VTermColor ansi_table[16] = {
2450 { 0, 0, 0}, /* black */
2451 {224, 0, 0}, /* dark red */
2452 { 0, 224, 0}, /* dark green */
2453 {224, 224, 0}, /* dark yellow / brown */
2454 { 0, 0, 224}, /* dark blue */
2455 {224, 0, 224}, /* dark magenta */
2456 { 0, 224, 224}, /* dark cyan */
2457 {224, 224, 224}, /* light grey */
2458
2459 {128, 128, 128}, /* dark grey */
2460 {255, 64, 64}, /* light red */
2461 { 64, 255, 64}, /* light green */
2462 {255, 255, 64}, /* yellow */
2463 { 64, 64, 255}, /* light blue */
2464 {255, 64, 255}, /* light magenta */
2465 { 64, 255, 255}, /* light cyan */
2466 {255, 255, 255}, /* white */
2467};
2468
2469static int cube_value[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002470 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002471};
2472
2473static int grey_ramp[] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002474 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76,
2475 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002476};
2477
2478/*
2479 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002480 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002481 */
2482 static void
2483cterm_color2rgb(int nr, VTermColor *rgb)
2484{
2485 int idx;
2486
2487 if (nr < 16)
2488 {
2489 *rgb = ansi_table[nr];
2490 }
2491 else if (nr < 232)
2492 {
2493 /* 216 color cube */
2494 idx = nr - 16;
2495 rgb->blue = cube_value[idx % 6];
2496 rgb->green = cube_value[idx / 6 % 6];
2497 rgb->red = cube_value[idx / 36 % 6];
2498 }
2499 else if (nr < 256)
2500 {
2501 /* 24 grey scale ramp */
2502 idx = nr - 232;
2503 rgb->blue = grey_ramp[idx];
2504 rgb->green = grey_ramp[idx];
2505 rgb->red = grey_ramp[idx];
2506 }
2507}
2508
2509/*
2510 * Create a new vterm and initialize it.
2511 */
2512 static void
2513create_vterm(term_T *term, int rows, int cols)
2514{
2515 VTerm *vterm;
2516 VTermScreen *screen;
2517 VTermValue value;
2518 VTermColor *fg, *bg;
2519 int fgval, bgval;
2520 int id;
2521
2522 vterm = vterm_new(rows, cols);
2523 term->tl_vterm = vterm;
2524 screen = vterm_obtain_screen(vterm);
2525 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
2526 /* TODO: depends on 'encoding'. */
2527 vterm_set_utf8(vterm, 1);
2528
2529 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
2530 term->tl_default_color.width = 1;
2531 fg = &term->tl_default_color.fg;
2532 bg = &term->tl_default_color.bg;
2533
2534 /* Vterm uses a default black background. Set it to white when
2535 * 'background' is "light". */
2536 if (*p_bg == 'l')
2537 {
2538 fgval = 0;
2539 bgval = 255;
2540 }
2541 else
2542 {
2543 fgval = 255;
2544 bgval = 0;
2545 }
2546 fg->red = fg->green = fg->blue = fgval;
2547 bg->red = bg->green = bg->blue = bgval;
2548
2549 /* The "Terminal" highlight group overrules the defaults. */
2550 id = syn_name2id((char_u *)"Terminal");
2551
2552 /* Use the actual color for the GUI and when 'guitermcolors' is set. */
2553#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
2554 if (0
2555# ifdef FEAT_GUI
2556 || gui.in_use
2557# endif
2558# ifdef FEAT_TERMGUICOLORS
2559 || p_tgc
2560# endif
2561 )
2562 {
2563 guicolor_T fg_rgb = INVALCOLOR;
2564 guicolor_T bg_rgb = INVALCOLOR;
2565
2566 if (id != 0)
2567 syn_id2colors(id, &fg_rgb, &bg_rgb);
2568
2569# ifdef FEAT_GUI
2570 if (gui.in_use)
2571 {
2572 if (fg_rgb == INVALCOLOR)
2573 fg_rgb = gui.norm_pixel;
2574 if (bg_rgb == INVALCOLOR)
2575 bg_rgb = gui.back_pixel;
2576 }
2577# ifdef FEAT_TERMGUICOLORS
2578 else
2579# endif
2580# endif
2581# ifdef FEAT_TERMGUICOLORS
2582 {
2583 if (fg_rgb == INVALCOLOR)
2584 fg_rgb = cterm_normal_fg_gui_color;
2585 if (bg_rgb == INVALCOLOR)
2586 bg_rgb = cterm_normal_bg_gui_color;
2587 }
2588# endif
2589 if (fg_rgb != INVALCOLOR)
2590 {
2591 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
2592
2593 fg->red = (unsigned)(rgb >> 16);
2594 fg->green = (unsigned)(rgb >> 8) & 255;
2595 fg->blue = (unsigned)rgb & 255;
2596 }
2597 if (bg_rgb != INVALCOLOR)
2598 {
2599 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
2600
2601 bg->red = (unsigned)(rgb >> 16);
2602 bg->green = (unsigned)(rgb >> 8) & 255;
2603 bg->blue = (unsigned)rgb & 255;
2604 }
2605 }
2606 else
2607#endif
2608 if (id != 0 && t_colors >= 16)
2609 {
2610 int cterm_fg, cterm_bg;
2611
2612 syn_id2cterm_bg(id, &cterm_fg, &cterm_bg);
2613 if (cterm_fg >= 0)
2614 cterm_color2rgb(cterm_fg, fg);
2615 if (cterm_bg >= 0)
2616 cterm_color2rgb(cterm_bg, bg);
2617 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002618 else
2619 {
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002620#if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002621 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002622#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002623
2624 /* In an MS-Windows console we know the normal colors. */
2625 if (cterm_normal_fg_color > 0)
2626 {
2627 cterm_color2rgb(cterm_normal_fg_color - 1, fg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002628# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002629 tmp = fg->red;
2630 fg->red = fg->blue;
2631 fg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002632# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002633 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02002634# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002635 else
2636 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02002637# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002638
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002639 if (cterm_normal_bg_color > 0)
2640 {
2641 cterm_color2rgb(cterm_normal_bg_color - 1, bg);
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002642# if defined(WIN3264) && !defined(FEAT_GUI_W32)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002643 tmp = bg->red;
2644 bg->red = bg->blue;
2645 bg->blue = tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002646# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002647 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02002648# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02002649 else
2650 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02002651# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002652 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002653
2654 vterm_state_set_default_colors(vterm_obtain_state(vterm), fg, bg);
2655
2656 /* Required to initialize most things. */
2657 vterm_screen_reset(screen, 1 /* hard */);
2658
2659 /* Allow using alternate screen. */
2660 vterm_screen_enable_altscreen(screen, 1);
2661
2662 /* For unix do not use a blinking cursor. In an xterm this causes the
2663 * cursor to blink if it's blinking in the xterm.
2664 * For Windows we respect the system wide setting. */
2665#ifdef WIN3264
2666 if (GetCaretBlinkTime() == INFINITE)
2667 value.boolean = 0;
2668 else
2669 value.boolean = 1;
2670#else
2671 value.boolean = 0;
2672#endif
2673 vterm_state_set_termprop(vterm_obtain_state(vterm),
2674 VTERM_PROP_CURSORBLINK, &value);
2675}
2676
2677/*
2678 * Return the text to show for the buffer name and status.
2679 */
2680 char_u *
2681term_get_status_text(term_T *term)
2682{
2683 if (term->tl_status_text == NULL)
2684 {
2685 char_u *txt;
2686 size_t len;
2687
2688 if (term->tl_normal_mode)
2689 {
2690 if (term_job_running(term))
2691 txt = (char_u *)_("Terminal");
2692 else
2693 txt = (char_u *)_("Terminal-finished");
2694 }
2695 else if (term->tl_title != NULL)
2696 txt = term->tl_title;
2697 else if (term_none_open(term))
2698 txt = (char_u *)_("active");
2699 else if (term_job_running(term))
2700 txt = (char_u *)_("running");
2701 else
2702 txt = (char_u *)_("finished");
2703 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
2704 term->tl_status_text = alloc((int)len);
2705 if (term->tl_status_text != NULL)
2706 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
2707 term->tl_buffer->b_fname, txt);
2708 }
2709 return term->tl_status_text;
2710}
2711
2712/*
2713 * Mark references in jobs of terminals.
2714 */
2715 int
2716set_ref_in_term(int copyID)
2717{
2718 int abort = FALSE;
2719 term_T *term;
2720 typval_T tv;
2721
2722 for (term = first_term; term != NULL; term = term->tl_next)
2723 if (term->tl_job != NULL)
2724 {
2725 tv.v_type = VAR_JOB;
2726 tv.vval.v_job = term->tl_job;
2727 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
2728 }
2729 return abort;
2730}
2731
2732/*
2733 * Get the buffer from the first argument in "argvars".
2734 * Returns NULL when the buffer is not for a terminal window.
2735 */
2736 static buf_T *
2737term_get_buf(typval_T *argvars)
2738{
2739 buf_T *buf;
2740
2741 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
2742 ++emsg_off;
2743 buf = get_buf_tv(&argvars[0], FALSE);
2744 --emsg_off;
2745 if (buf == NULL || buf->b_term == NULL)
2746 return NULL;
2747 return buf;
2748}
2749
2750/*
2751 * "term_getaltscreen(buf)" function
2752 */
2753 void
2754f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
2755{
2756 buf_T *buf = term_get_buf(argvars);
2757
2758 if (buf == NULL)
2759 return;
2760 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
2761}
2762
2763/*
2764 * "term_getattr(attr, name)" function
2765 */
2766 void
2767f_term_getattr(typval_T *argvars, typval_T *rettv)
2768{
2769 int attr;
2770 size_t i;
2771 char_u *name;
2772
2773 static struct {
2774 char *name;
2775 int attr;
2776 } attrs[] = {
2777 {"bold", HL_BOLD},
2778 {"italic", HL_ITALIC},
2779 {"underline", HL_UNDERLINE},
2780 {"strike", HL_STRIKETHROUGH},
2781 {"reverse", HL_INVERSE},
2782 };
2783
2784 attr = get_tv_number(&argvars[0]);
2785 name = get_tv_string_chk(&argvars[1]);
2786 if (name == NULL)
2787 return;
2788
2789 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
2790 if (STRCMP(name, attrs[i].name) == 0)
2791 {
2792 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
2793 break;
2794 }
2795}
2796
2797/*
2798 * "term_getcursor(buf)" function
2799 */
2800 void
2801f_term_getcursor(typval_T *argvars, typval_T *rettv)
2802{
2803 buf_T *buf = term_get_buf(argvars);
2804 term_T *term;
2805 list_T *l;
2806 dict_T *d;
2807
2808 if (rettv_list_alloc(rettv) == FAIL)
2809 return;
2810 if (buf == NULL)
2811 return;
2812 term = buf->b_term;
2813
2814 l = rettv->vval.v_list;
2815 list_append_number(l, term->tl_cursor_pos.row + 1);
2816 list_append_number(l, term->tl_cursor_pos.col + 1);
2817
2818 d = dict_alloc();
2819 if (d != NULL)
2820 {
2821 dict_add_nr_str(d, "visible", term->tl_cursor_visible, NULL);
2822 dict_add_nr_str(d, "blink", blink_state_is_inverted()
2823 ? !term->tl_cursor_blink : term->tl_cursor_blink, NULL);
2824 dict_add_nr_str(d, "shape", term->tl_cursor_shape, NULL);
2825 dict_add_nr_str(d, "color", 0L, term->tl_cursor_color == NULL
2826 ? (char_u *)"" : term->tl_cursor_color);
2827 list_append_dict(l, d);
2828 }
2829}
2830
2831/*
2832 * "term_getjob(buf)" function
2833 */
2834 void
2835f_term_getjob(typval_T *argvars, typval_T *rettv)
2836{
2837 buf_T *buf = term_get_buf(argvars);
2838
2839 rettv->v_type = VAR_JOB;
2840 rettv->vval.v_job = NULL;
2841 if (buf == NULL)
2842 return;
2843
2844 rettv->vval.v_job = buf->b_term->tl_job;
2845 if (rettv->vval.v_job != NULL)
2846 ++rettv->vval.v_job->jv_refcount;
2847}
2848
2849 static int
2850get_row_number(typval_T *tv, term_T *term)
2851{
2852 if (tv->v_type == VAR_STRING
2853 && tv->vval.v_string != NULL
2854 && STRCMP(tv->vval.v_string, ".") == 0)
2855 return term->tl_cursor_pos.row;
2856 return (int)get_tv_number(tv) - 1;
2857}
2858
2859/*
2860 * "term_getline(buf, row)" function
2861 */
2862 void
2863f_term_getline(typval_T *argvars, typval_T *rettv)
2864{
2865 buf_T *buf = term_get_buf(argvars);
2866 term_T *term;
2867 int row;
2868
2869 rettv->v_type = VAR_STRING;
2870 if (buf == NULL)
2871 return;
2872 term = buf->b_term;
2873 row = get_row_number(&argvars[1], term);
2874
2875 if (term->tl_vterm == NULL)
2876 {
2877 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
2878
2879 /* vterm is finished, get the text from the buffer */
2880 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
2881 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
2882 }
2883 else
2884 {
2885 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
2886 VTermRect rect;
2887 int len;
2888 char_u *p;
2889
2890 if (row < 0 || row >= term->tl_rows)
2891 return;
2892 len = term->tl_cols * MB_MAXBYTES + 1;
2893 p = alloc(len);
2894 if (p == NULL)
2895 return;
2896 rettv->vval.v_string = p;
2897
2898 rect.start_col = 0;
2899 rect.end_col = term->tl_cols;
2900 rect.start_row = row;
2901 rect.end_row = row + 1;
2902 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
2903 }
2904}
2905
2906/*
2907 * "term_getscrolled(buf)" function
2908 */
2909 void
2910f_term_getscrolled(typval_T *argvars, typval_T *rettv)
2911{
2912 buf_T *buf = term_get_buf(argvars);
2913
2914 if (buf == NULL)
2915 return;
2916 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
2917}
2918
2919/*
2920 * "term_getsize(buf)" function
2921 */
2922 void
2923f_term_getsize(typval_T *argvars, typval_T *rettv)
2924{
2925 buf_T *buf = term_get_buf(argvars);
2926 list_T *l;
2927
2928 if (rettv_list_alloc(rettv) == FAIL)
2929 return;
2930 if (buf == NULL)
2931 return;
2932
2933 l = rettv->vval.v_list;
2934 list_append_number(l, buf->b_term->tl_rows);
2935 list_append_number(l, buf->b_term->tl_cols);
2936}
2937
2938/*
2939 * "term_getstatus(buf)" function
2940 */
2941 void
2942f_term_getstatus(typval_T *argvars, typval_T *rettv)
2943{
2944 buf_T *buf = term_get_buf(argvars);
2945 term_T *term;
2946 char_u val[100];
2947
2948 rettv->v_type = VAR_STRING;
2949 if (buf == NULL)
2950 return;
2951 term = buf->b_term;
2952
2953 if (term_job_running(term))
2954 STRCPY(val, "running");
2955 else
2956 STRCPY(val, "finished");
2957 if (term->tl_normal_mode)
2958 STRCAT(val, ",normal");
2959 rettv->vval.v_string = vim_strsave(val);
2960}
2961
2962/*
2963 * "term_gettitle(buf)" function
2964 */
2965 void
2966f_term_gettitle(typval_T *argvars, typval_T *rettv)
2967{
2968 buf_T *buf = term_get_buf(argvars);
2969
2970 rettv->v_type = VAR_STRING;
2971 if (buf == NULL)
2972 return;
2973
2974 if (buf->b_term->tl_title != NULL)
2975 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
2976}
2977
2978/*
2979 * "term_gettty(buf)" function
2980 */
2981 void
2982f_term_gettty(typval_T *argvars, typval_T *rettv)
2983{
2984 buf_T *buf = term_get_buf(argvars);
2985 char_u *p;
2986 int num = 0;
2987
2988 rettv->v_type = VAR_STRING;
2989 if (buf == NULL)
2990 return;
2991 if (argvars[1].v_type != VAR_UNKNOWN)
2992 num = get_tv_number(&argvars[1]);
2993
2994 switch (num)
2995 {
2996 case 0:
2997 if (buf->b_term->tl_job != NULL)
2998 p = buf->b_term->tl_job->jv_tty_out;
2999 else
3000 p = buf->b_term->tl_tty_out;
3001 break;
3002 case 1:
3003 if (buf->b_term->tl_job != NULL)
3004 p = buf->b_term->tl_job->jv_tty_in;
3005 else
3006 p = buf->b_term->tl_tty_in;
3007 break;
3008 default:
3009 EMSG2(_(e_invarg2), get_tv_string(&argvars[1]));
3010 return;
3011 }
3012 if (p != NULL)
3013 rettv->vval.v_string = vim_strsave(p);
3014}
3015
3016/*
3017 * "term_list()" function
3018 */
3019 void
3020f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
3021{
3022 term_T *tp;
3023 list_T *l;
3024
3025 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
3026 return;
3027
3028 l = rettv->vval.v_list;
3029 for (tp = first_term; tp != NULL; tp = tp->tl_next)
3030 if (tp != NULL && tp->tl_buffer != NULL)
3031 if (list_append_number(l,
3032 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
3033 return;
3034}
3035
3036/*
3037 * "term_scrape(buf, row)" function
3038 */
3039 void
3040f_term_scrape(typval_T *argvars, typval_T *rettv)
3041{
3042 buf_T *buf = term_get_buf(argvars);
3043 VTermScreen *screen = NULL;
3044 VTermPos pos;
3045 list_T *l;
3046 term_T *term;
3047 char_u *p;
3048 sb_line_T *line;
3049
3050 if (rettv_list_alloc(rettv) == FAIL)
3051 return;
3052 if (buf == NULL)
3053 return;
3054 term = buf->b_term;
3055
3056 l = rettv->vval.v_list;
3057 pos.row = get_row_number(&argvars[1], term);
3058
3059 if (term->tl_vterm != NULL)
3060 {
3061 screen = vterm_obtain_screen(term->tl_vterm);
3062 p = NULL;
3063 line = NULL;
3064 }
3065 else
3066 {
3067 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
3068
3069 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
3070 return;
3071 p = ml_get_buf(buf, lnum + 1, FALSE);
3072 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
3073 }
3074
3075 for (pos.col = 0; pos.col < term->tl_cols; )
3076 {
3077 dict_T *dcell;
3078 int width;
3079 VTermScreenCellAttrs attrs;
3080 VTermColor fg, bg;
3081 char_u rgb[8];
3082 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
3083 int off = 0;
3084 int i;
3085
3086 if (screen == NULL)
3087 {
3088 cellattr_T *cellattr;
3089 int len;
3090
3091 /* vterm has finished, get the cell from scrollback */
3092 if (pos.col >= line->sb_cols)
3093 break;
3094 cellattr = line->sb_cells + pos.col;
3095 width = cellattr->width;
3096 attrs = cellattr->attrs;
3097 fg = cellattr->fg;
3098 bg = cellattr->bg;
3099 len = MB_PTR2LEN(p);
3100 mch_memmove(mbs, p, len);
3101 mbs[len] = NUL;
3102 p += len;
3103 }
3104 else
3105 {
3106 VTermScreenCell cell;
3107 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
3108 break;
3109 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
3110 {
3111 if (cell.chars[i] == 0)
3112 break;
3113 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
3114 }
3115 mbs[off] = NUL;
3116 width = cell.width;
3117 attrs = cell.attrs;
3118 fg = cell.fg;
3119 bg = cell.bg;
3120 }
3121 dcell = dict_alloc();
3122 list_append_dict(l, dcell);
3123
3124 dict_add_nr_str(dcell, "chars", 0, mbs);
3125
3126 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
3127 fg.red, fg.green, fg.blue);
3128 dict_add_nr_str(dcell, "fg", 0, rgb);
3129 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
3130 bg.red, bg.green, bg.blue);
3131 dict_add_nr_str(dcell, "bg", 0, rgb);
3132
3133 dict_add_nr_str(dcell, "attr",
3134 cell2attr(attrs, fg, bg), NULL);
3135 dict_add_nr_str(dcell, "width", width, NULL);
3136
3137 ++pos.col;
3138 if (width == 2)
3139 ++pos.col;
3140 }
3141}
3142
3143/*
3144 * "term_sendkeys(buf, keys)" function
3145 */
3146 void
3147f_term_sendkeys(typval_T *argvars, typval_T *rettv)
3148{
3149 buf_T *buf = term_get_buf(argvars);
3150 char_u *msg;
3151 term_T *term;
3152
3153 rettv->v_type = VAR_UNKNOWN;
3154 if (buf == NULL)
3155 return;
3156
3157 msg = get_tv_string_chk(&argvars[1]);
3158 if (msg == NULL)
3159 return;
3160 term = buf->b_term;
3161 if (term->tl_vterm == NULL)
3162 return;
3163
3164 while (*msg != NUL)
3165 {
3166 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
Bram Moolenaar6daeef12017-10-15 22:56:49 +02003167 msg += MB_CPTR2LEN(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003168 }
3169}
3170
3171/*
3172 * "term_start(command, options)" function
3173 */
3174 void
3175f_term_start(typval_T *argvars, typval_T *rettv)
3176{
3177 jobopt_T opt;
3178 buf_T *buf;
3179
3180 init_job_options(&opt);
3181 if (argvars[1].v_type != VAR_UNKNOWN
3182 && get_job_options(&argvars[1], &opt,
3183 JO_TIMEOUT_ALL + JO_STOPONEXIT
3184 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
3185 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
3186 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
3187 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
3188 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS) == FAIL)
3189 return;
3190
3191 if (opt.jo_vertical)
3192 cmdmod.split = WSP_VERT;
3193 buf = term_start(&argvars[0], &opt, FALSE);
3194
3195 if (buf != NULL && buf->b_term != NULL)
3196 rettv->vval.v_number = buf->b_fnum;
3197}
3198
3199/*
3200 * "term_wait" function
3201 */
3202 void
3203f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
3204{
3205 buf_T *buf = term_get_buf(argvars);
3206
3207 if (buf == NULL)
3208 {
3209 ch_log(NULL, "term_wait(): invalid argument");
3210 return;
3211 }
3212 if (buf->b_term->tl_job == NULL)
3213 {
3214 ch_log(NULL, "term_wait(): no job to wait for");
3215 return;
3216 }
3217 if (buf->b_term->tl_job->jv_channel == NULL)
3218 /* channel is closed, nothing to do */
3219 return;
3220
3221 /* Get the job status, this will detect a job that finished. */
3222 if ((buf->b_term->tl_job->jv_channel == NULL
3223 || !buf->b_term->tl_job->jv_channel->ch_keep_open)
3224 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
3225 {
3226 /* The job is dead, keep reading channel I/O until the channel is
3227 * closed. buf->b_term may become NULL if the terminal was closed while
3228 * waiting. */
3229 ch_log(NULL, "term_wait(): waiting for channel to close");
3230 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
3231 {
3232 mch_check_messages();
3233 parse_queued_messages();
Bram Moolenaare5182262017-11-19 15:05:44 +01003234 if (!buf_valid(buf))
3235 /* If the terminal is closed when the channel is closed the
3236 * buffer disappears. */
3237 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003238 ui_delay(10L, FALSE);
3239 }
3240 mch_check_messages();
3241 parse_queued_messages();
3242 }
3243 else
3244 {
3245 long wait = 10L;
3246
3247 mch_check_messages();
3248 parse_queued_messages();
3249
3250 /* Wait for some time for any channel I/O. */
3251 if (argvars[1].v_type != VAR_UNKNOWN)
3252 wait = get_tv_number(&argvars[1]);
3253 ui_delay(wait, TRUE);
3254 mch_check_messages();
3255
3256 /* Flushing messages on channels is hopefully sufficient.
3257 * TODO: is there a better way? */
3258 parse_queued_messages();
3259 }
3260}
3261
3262/*
3263 * Called when a channel has sent all the lines to a terminal.
3264 * Send a CTRL-D to mark the end of the text.
3265 */
3266 void
3267term_send_eof(channel_T *ch)
3268{
3269 term_T *term;
3270
3271 for (term = first_term; term != NULL; term = term->tl_next)
3272 if (term->tl_job == ch->ch_job)
3273 {
3274 if (term->tl_eof_chars != NULL)
3275 {
3276 channel_send(ch, PART_IN, term->tl_eof_chars,
3277 (int)STRLEN(term->tl_eof_chars), NULL);
3278 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
3279 }
3280# ifdef WIN3264
3281 else
3282 /* Default: CTRL-D */
3283 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
3284# endif
3285 }
3286}
3287
3288# if defined(WIN3264) || defined(PROTO)
3289
3290/**************************************
3291 * 2. MS-Windows implementation.
3292 */
3293
3294# ifndef PROTO
3295
3296#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
3297#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
3298#define WINPTY_MOUSE_MODE_FORCE 2
3299
3300void* (*winpty_config_new)(UINT64, void*);
3301void* (*winpty_open)(void*, void*);
3302void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
3303BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
3304void (*winpty_config_set_mouse_mode)(void*, int);
3305void (*winpty_config_set_initial_size)(void*, int, int);
3306LPCWSTR (*winpty_conin_name)(void*);
3307LPCWSTR (*winpty_conout_name)(void*);
3308LPCWSTR (*winpty_conerr_name)(void*);
3309void (*winpty_free)(void*);
3310void (*winpty_config_free)(void*);
3311void (*winpty_spawn_config_free)(void*);
3312void (*winpty_error_free)(void*);
3313LPCWSTR (*winpty_error_msg)(void*);
3314BOOL (*winpty_set_size)(void*, int, int, void*);
3315HANDLE (*winpty_agent_process)(void*);
3316
3317#define WINPTY_DLL "winpty.dll"
3318
3319static HINSTANCE hWinPtyDLL = NULL;
3320# endif
3321
3322 static int
3323dyn_winpty_init(int verbose)
3324{
3325 int i;
3326 static struct
3327 {
3328 char *name;
3329 FARPROC *ptr;
3330 } winpty_entry[] =
3331 {
3332 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
3333 {"winpty_config_free", (FARPROC*)&winpty_config_free},
3334 {"winpty_config_new", (FARPROC*)&winpty_config_new},
3335 {"winpty_config_set_mouse_mode",
3336 (FARPROC*)&winpty_config_set_mouse_mode},
3337 {"winpty_config_set_initial_size",
3338 (FARPROC*)&winpty_config_set_initial_size},
3339 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
3340 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
3341 {"winpty_error_free", (FARPROC*)&winpty_error_free},
3342 {"winpty_free", (FARPROC*)&winpty_free},
3343 {"winpty_open", (FARPROC*)&winpty_open},
3344 {"winpty_spawn", (FARPROC*)&winpty_spawn},
3345 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
3346 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
3347 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
3348 {"winpty_set_size", (FARPROC*)&winpty_set_size},
3349 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
3350 {NULL, NULL}
3351 };
3352
3353 /* No need to initialize twice. */
3354 if (hWinPtyDLL)
3355 return OK;
3356 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
3357 * winpty.dll. */
3358 if (*p_winptydll != NUL)
3359 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
3360 if (!hWinPtyDLL)
3361 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
3362 if (!hWinPtyDLL)
3363 {
3364 if (verbose)
3365 EMSG2(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
3366 : (char_u *)WINPTY_DLL);
3367 return FAIL;
3368 }
3369 for (i = 0; winpty_entry[i].name != NULL
3370 && winpty_entry[i].ptr != NULL; ++i)
3371 {
3372 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
3373 winpty_entry[i].name)) == NULL)
3374 {
3375 if (verbose)
3376 EMSG2(_(e_loadfunc), winpty_entry[i].name);
3377 return FAIL;
3378 }
3379 }
3380
3381 return OK;
3382}
3383
3384/*
3385 * Create a new terminal of "rows" by "cols" cells.
3386 * Store a reference in "term".
3387 * Return OK or FAIL.
3388 */
3389 static int
3390term_and_job_init(
3391 term_T *term,
3392 typval_T *argvar,
3393 jobopt_T *opt)
3394{
3395 WCHAR *cmd_wchar = NULL;
3396 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01003397 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003398 channel_T *channel = NULL;
3399 job_T *job = NULL;
3400 DWORD error;
3401 HANDLE jo = NULL;
3402 HANDLE child_process_handle;
3403 HANDLE child_thread_handle;
3404 void *winpty_err;
3405 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01003406 garray_T ga_cmd, ga_env;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003407 char_u *cmd;
3408
3409 if (dyn_winpty_init(TRUE) == FAIL)
3410 return FAIL;
3411
3412 if (argvar->v_type == VAR_STRING)
3413 cmd = argvar->vval.v_string;
3414 else
3415 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01003416 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
3417 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003418 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01003419 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003420 }
3421
3422 cmd_wchar = enc_to_utf16(cmd, NULL);
3423 if (cmd_wchar == NULL)
3424 return FAIL;
3425 if (opt->jo_cwd != NULL)
3426 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01003427
3428 ga_init2(&ga_env, (int)sizeof(char*), 20);
3429 win32_build_env(opt->jo_env, &ga_env, TRUE);
3430 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003431
3432 job = job_alloc();
3433 if (job == NULL)
3434 goto failed;
3435
3436 channel = add_channel();
3437 if (channel == NULL)
3438 goto failed;
3439
3440 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
3441 if (term->tl_winpty_config == NULL)
3442 goto failed;
3443
3444 winpty_config_set_mouse_mode(term->tl_winpty_config,
3445 WINPTY_MOUSE_MODE_FORCE);
3446 winpty_config_set_initial_size(term->tl_winpty_config,
3447 term->tl_cols, term->tl_rows);
3448 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
3449 if (term->tl_winpty == NULL)
3450 goto failed;
3451
3452 spawn_config = winpty_spawn_config_new(
3453 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
3454 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
3455 NULL,
3456 cmd_wchar,
3457 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01003458 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003459 &winpty_err);
3460 if (spawn_config == NULL)
3461 goto failed;
3462
3463 channel = add_channel();
3464 if (channel == NULL)
3465 goto failed;
3466
3467 job = job_alloc();
3468 if (job == NULL)
3469 goto failed;
3470
3471 if (opt->jo_set & JO_IN_BUF)
3472 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
3473
3474 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
3475 &child_thread_handle, &error, &winpty_err))
3476 goto failed;
3477
3478 channel_set_pipes(channel,
3479 (sock_T)CreateFileW(
3480 winpty_conin_name(term->tl_winpty),
3481 GENERIC_WRITE, 0, NULL,
3482 OPEN_EXISTING, 0, NULL),
3483 (sock_T)CreateFileW(
3484 winpty_conout_name(term->tl_winpty),
3485 GENERIC_READ, 0, NULL,
3486 OPEN_EXISTING, 0, NULL),
3487 (sock_T)CreateFileW(
3488 winpty_conerr_name(term->tl_winpty),
3489 GENERIC_READ, 0, NULL,
3490 OPEN_EXISTING, 0, NULL));
3491
3492 /* Write lines with CR instead of NL. */
3493 channel->ch_write_text_mode = TRUE;
3494
3495 jo = CreateJobObject(NULL, NULL);
3496 if (jo == NULL)
3497 goto failed;
3498
3499 if (!AssignProcessToJobObject(jo, child_process_handle))
3500 {
3501 /* Failed, switch the way to terminate process with TerminateProcess. */
3502 CloseHandle(jo);
3503 jo = NULL;
3504 }
3505
3506 winpty_spawn_config_free(spawn_config);
3507 vim_free(cmd_wchar);
3508 vim_free(cwd_wchar);
3509
3510 create_vterm(term, term->tl_rows, term->tl_cols);
3511
3512 channel_set_job(channel, job, opt);
3513 job_set_options(job, opt);
3514
3515 job->jv_channel = channel;
3516 job->jv_proc_info.hProcess = child_process_handle;
3517 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
3518 job->jv_job_object = jo;
3519 job->jv_status = JOB_STARTED;
3520 job->jv_tty_in = utf16_to_enc(
3521 (short_u*)winpty_conin_name(term->tl_winpty), NULL);
3522 job->jv_tty_out = utf16_to_enc(
3523 (short_u*)winpty_conout_name(term->tl_winpty), NULL);
3524 ++job->jv_refcount;
3525 term->tl_job = job;
3526
3527 return OK;
3528
3529failed:
3530 if (argvar->v_type == VAR_LIST)
Bram Moolenaarba6febd2017-10-30 21:56:23 +01003531 vim_free(ga_cmd.ga_data);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01003532 vim_free(ga_env.ga_data);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003533 vim_free(cmd_wchar);
3534 vim_free(cwd_wchar);
3535 if (spawn_config != NULL)
3536 winpty_spawn_config_free(spawn_config);
3537 if (channel != NULL)
3538 channel_clear(channel);
3539 if (job != NULL)
3540 {
3541 job->jv_channel = NULL;
3542 job_cleanup(job);
3543 }
3544 term->tl_job = NULL;
3545 if (jo != NULL)
3546 CloseHandle(jo);
3547 if (term->tl_winpty != NULL)
3548 winpty_free(term->tl_winpty);
3549 term->tl_winpty = NULL;
3550 if (term->tl_winpty_config != NULL)
3551 winpty_config_free(term->tl_winpty_config);
3552 term->tl_winpty_config = NULL;
3553 if (winpty_err != NULL)
3554 {
3555 char_u *msg = utf16_to_enc(
3556 (short_u *)winpty_error_msg(winpty_err), NULL);
3557
3558 EMSG(msg);
3559 winpty_error_free(winpty_err);
3560 }
3561 return FAIL;
3562}
3563
3564 static int
3565create_pty_only(term_T *term, jobopt_T *options)
3566{
3567 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
3568 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
3569 char in_name[80], out_name[80];
3570 channel_T *channel = NULL;
3571
3572 create_vterm(term, term->tl_rows, term->tl_cols);
3573
3574 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
3575 GetCurrentProcessId(),
3576 curbuf->b_fnum);
3577 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
3578 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
3579 PIPE_UNLIMITED_INSTANCES,
3580 0, 0, NMPWAIT_NOWAIT, NULL);
3581 if (hPipeIn == INVALID_HANDLE_VALUE)
3582 goto failed;
3583
3584 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
3585 GetCurrentProcessId(),
3586 curbuf->b_fnum);
3587 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
3588 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
3589 PIPE_UNLIMITED_INSTANCES,
3590 0, 0, 0, NULL);
3591 if (hPipeOut == INVALID_HANDLE_VALUE)
3592 goto failed;
3593
3594 ConnectNamedPipe(hPipeIn, NULL);
3595 ConnectNamedPipe(hPipeOut, NULL);
3596
3597 term->tl_job = job_alloc();
3598 if (term->tl_job == NULL)
3599 goto failed;
3600 ++term->tl_job->jv_refcount;
3601
3602 /* behave like the job is already finished */
3603 term->tl_job->jv_status = JOB_FINISHED;
3604
3605 channel = add_channel();
3606 if (channel == NULL)
3607 goto failed;
3608 term->tl_job->jv_channel = channel;
3609 channel->ch_keep_open = TRUE;
3610 channel->ch_named_pipe = TRUE;
3611
3612 channel_set_pipes(channel,
3613 (sock_T)hPipeIn,
3614 (sock_T)hPipeOut,
3615 (sock_T)hPipeOut);
3616 channel_set_job(channel, term->tl_job, options);
3617 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
3618 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
3619
3620 return OK;
3621
3622failed:
3623 if (hPipeIn != NULL)
3624 CloseHandle(hPipeIn);
3625 if (hPipeOut != NULL)
3626 CloseHandle(hPipeOut);
3627 return FAIL;
3628}
3629
3630/*
3631 * Free the terminal emulator part of "term".
3632 */
3633 static void
3634term_free_vterm(term_T *term)
3635{
3636 if (term->tl_winpty != NULL)
3637 winpty_free(term->tl_winpty);
3638 term->tl_winpty = NULL;
3639 if (term->tl_winpty_config != NULL)
3640 winpty_config_free(term->tl_winpty_config);
3641 term->tl_winpty_config = NULL;
3642 if (term->tl_vterm != NULL)
3643 vterm_free(term->tl_vterm);
3644 term->tl_vterm = NULL;
3645}
3646
3647/*
3648 * Request size to terminal.
3649 */
3650 static void
3651term_report_winsize(term_T *term, int rows, int cols)
3652{
3653 if (term->tl_winpty)
3654 winpty_set_size(term->tl_winpty, cols, rows, NULL);
3655}
3656
3657 int
3658terminal_enabled(void)
3659{
3660 return dyn_winpty_init(FALSE) == OK;
3661}
3662
3663# else
3664
3665/**************************************
3666 * 3. Unix-like implementation.
3667 */
3668
3669/*
3670 * Create a new terminal of "rows" by "cols" cells.
3671 * Start job for "cmd".
3672 * Store the pointers in "term".
3673 * Return OK or FAIL.
3674 */
3675 static int
3676term_and_job_init(
3677 term_T *term,
3678 typval_T *argvar,
3679 jobopt_T *opt)
3680{
3681 create_vterm(term, term->tl_rows, term->tl_cols);
3682
3683 term->tl_job = job_start(argvar, opt);
3684 if (term->tl_job != NULL)
3685 ++term->tl_job->jv_refcount;
3686
3687 return term->tl_job != NULL
3688 && term->tl_job->jv_channel != NULL
3689 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
3690}
3691
3692 static int
3693create_pty_only(term_T *term, jobopt_T *opt)
3694{
3695 create_vterm(term, term->tl_rows, term->tl_cols);
3696
3697 term->tl_job = job_alloc();
3698 if (term->tl_job == NULL)
3699 return FAIL;
3700 ++term->tl_job->jv_refcount;
3701
3702 /* behave like the job is already finished */
3703 term->tl_job->jv_status = JOB_FINISHED;
3704
3705 return mch_create_pty_channel(term->tl_job, opt);
3706}
3707
3708/*
3709 * Free the terminal emulator part of "term".
3710 */
3711 static void
3712term_free_vterm(term_T *term)
3713{
3714 if (term->tl_vterm != NULL)
3715 vterm_free(term->tl_vterm);
3716 term->tl_vterm = NULL;
3717}
3718
3719/*
3720 * Request size to terminal.
3721 */
3722 static void
3723term_report_winsize(term_T *term, int rows, int cols)
3724{
3725 /* Use an ioctl() to report the new window size to the job. */
3726 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
3727 {
3728 int fd = -1;
3729 int part;
3730
3731 for (part = PART_OUT; part < PART_COUNT; ++part)
3732 {
3733 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
3734 if (isatty(fd))
3735 break;
3736 }
3737 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
3738 mch_signal_job(term->tl_job, (char_u *)"winch");
3739 }
3740}
3741
3742# endif
3743
3744#endif /* FEAT_TERMINAL */