blob: a3ca993394584fc9311cd9e1706520b2af6d4050 [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.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020039 */
40
41#include "vim.h"
42
43#if defined(FEAT_TERMINAL) || defined(PROTO)
44
45#ifndef MIN
46# define MIN(x,y) ((x) < (y) ? (x) : (y))
47#endif
48#ifndef MAX
49# define MAX(x,y) ((x) > (y) ? (x) : (y))
50#endif
51
52#include "libvterm/include/vterm.h"
53
54/* This is VTermScreenCell without the characters, thus much smaller. */
55typedef struct {
56 VTermScreenCellAttrs attrs;
57 char width;
Bram Moolenaard96ff162018-02-18 22:13:29 +010058 VTermColor fg;
59 VTermColor bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020060} cellattr_T;
61
62typedef struct sb_line_S {
Bram Moolenaar29ae2232019-02-14 21:22:01 +010063 int sb_cols; // can differ per line
64 cellattr_T *sb_cells; // allocated
65 cellattr_T sb_fill_attr; // for short line
66 char_u *sb_text; // for tl_scrollback_postponed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020067} sb_line_T;
68
Bram Moolenaar4f974752019-02-17 17:44:42 +010069#ifdef MSWIN
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +010070# ifndef HPCON
71# define HPCON VOID*
72# endif
73# ifndef EXTENDED_STARTUPINFO_PRESENT
74# define EXTENDED_STARTUPINFO_PRESENT 0x00080000
75# endif
76# ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
77# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
78# endif
79typedef struct _DYN_STARTUPINFOEXW
80{
81 STARTUPINFOW StartupInfo;
82 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
83} DYN_STARTUPINFOEXW, *PDYN_STARTUPINFOEXW;
84#endif
85
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020086/* typedef term_T in structs.h */
87struct terminal_S {
88 term_T *tl_next;
89
90 VTerm *tl_vterm;
91 job_T *tl_job;
92 buf_T *tl_buffer;
Bram Moolenaar13568252018-03-16 20:46:58 +010093#if defined(FEAT_GUI)
94 int tl_system; /* when non-zero used for :!cmd output */
95 int tl_toprow; /* row with first line of system terminal */
96#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020097
98 /* Set when setting the size of a vterm, reset after redrawing. */
99 int tl_vterm_size_changed;
100
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200101 int tl_normal_mode; /* TRUE: Terminal-Normal mode */
102 int tl_channel_closed;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +0200103 int tl_channel_recently_closed; // still need to handle tl_finish
104
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100105 int tl_finish;
106#define TL_FINISH_UNSET NUL
107#define TL_FINISH_CLOSE 'c' /* ++close or :terminal without argument */
108#define TL_FINISH_NOCLOSE 'n' /* ++noclose */
109#define TL_FINISH_OPEN 'o' /* ++open */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200110 char_u *tl_opencmd;
111 char_u *tl_eof_chars;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200112 char_u *tl_api; // prefix for terminal API function
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200113
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100114 char_u *tl_arg0_cmd; // To format the status bar
115
Bram Moolenaar4f974752019-02-17 17:44:42 +0100116#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200117 void *tl_winpty_config;
118 void *tl_winpty;
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200119
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100120 HPCON tl_conpty;
121 DYN_STARTUPINFOEXW tl_siex; // Structure that always needs to be hold
122
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200123 FILE *tl_out_fd;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200124#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100125#if defined(FEAT_SESSION)
126 char_u *tl_command;
127#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100128 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200129
130 /* last known vterm size */
131 int tl_rows;
132 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200133
134 char_u *tl_title; /* NULL or allocated */
135 char_u *tl_status_text; /* NULL or allocated */
136
137 /* Range of screen rows to update. Zero based. */
Bram Moolenaar3a497e12017-09-30 20:40:27 +0200138 int tl_dirty_row_start; /* MAX_ROW if nothing dirty */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200139 int tl_dirty_row_end; /* row below last one to update */
Bram Moolenaar56bc8e22018-05-10 18:05:56 +0200140 int tl_dirty_snapshot; /* text updated after making snapshot */
141#ifdef FEAT_TIMERS
142 int tl_timer_set;
143 proftime_T tl_timer_due;
144#endif
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200145 int tl_postponed_scroll; /* to be scrolled up */
146
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200147 garray_T tl_scrollback;
148 int tl_scrollback_scrolled;
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100149 garray_T tl_scrollback_postponed;
150
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200151 cellattr_T tl_default_color;
152
Bram Moolenaard96ff162018-02-18 22:13:29 +0100153 linenr_T tl_top_diff_rows; /* rows of top diff file or zero */
154 linenr_T tl_bot_diff_rows; /* rows of bottom diff file */
155
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200156 VTermPos tl_cursor_pos;
157 int tl_cursor_visible;
158 int tl_cursor_blink;
159 int tl_cursor_shape; /* 1: block, 2: underline, 3: bar */
160 char_u *tl_cursor_color; /* NULL or allocated */
161
162 int tl_using_altscreen;
163};
164
165#define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */
166#define TMODE_LOOP 2 /* CTRL-W N used */
167
168/*
169 * List of all active terminals.
170 */
171static term_T *first_term = NULL;
172
173/* Terminal active in terminal_loop(). */
174static term_T *in_terminal_loop = NULL;
175
Bram Moolenaar4f974752019-02-17 17:44:42 +0100176#ifdef MSWIN
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100177static BOOL has_winpty = FALSE;
178static BOOL has_conpty = FALSE;
179#endif
180
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200181#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
182#define KEY_BUF_LEN 200
183
184/*
185 * Functions with separate implementation for MS-Windows and Unix-like systems.
186 */
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200187static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt, jobopt_T *orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200188static int create_pty_only(term_T *term, jobopt_T *opt);
189static void term_report_winsize(term_T *term, int rows, int cols);
190static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100191#ifdef FEAT_GUI
192static void update_system_term(term_T *term);
193#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200194
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100195static void handle_postponed_scrollback(term_T *term);
196
Bram Moolenaar26d205d2017-11-09 17:33:11 +0100197/* The character that we know (or assume) that the terminal expects for the
198 * backspace key. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200199static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200200
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100201/* "Terminal" highlight group colors. */
202static int term_default_cterm_fg = -1;
203static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200204
Bram Moolenaard317b382018-02-08 22:33:31 +0100205/* Store the last set and the desired cursor properties, so that we only update
206 * them when needed. Doing it unnecessary may result in flicker. */
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200207static char_u *last_set_cursor_color = NULL;
208static char_u *desired_cursor_color = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +0100209static int last_set_cursor_shape = -1;
210static int desired_cursor_shape = -1;
211static int last_set_cursor_blink = -1;
212static int desired_cursor_blink = -1;
213
214
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200215/**************************************
216 * 1. Generic code for all systems.
217 */
218
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200219 static int
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200220cursor_color_equal(char_u *lhs_color, char_u *rhs_color)
221{
222 if (lhs_color != NULL && rhs_color != NULL)
223 return STRCMP(lhs_color, rhs_color) == 0;
224 return lhs_color == NULL && rhs_color == NULL;
225}
226
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200227 static void
228cursor_color_copy(char_u **to_color, char_u *from_color)
229{
230 // Avoid a free & alloc if the value is already right.
231 if (cursor_color_equal(*to_color, from_color))
232 return;
233 vim_free(*to_color);
234 *to_color = (from_color == NULL) ? NULL : vim_strsave(from_color);
235}
236
237 static char_u *
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200238cursor_color_get(char_u *color)
239{
240 return (color == NULL) ? (char_u *)"" : color;
241}
242
243
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200244/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200245 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200246 * current window.
247 * Sets "rows" and/or "cols" to zero when it should follow the window size.
248 * Return TRUE if the size is the minimum size: "24*80".
249 */
250 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200251parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200252{
253 int minsize = FALSE;
254
255 *rows = 0;
256 *cols = 0;
257
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200258 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200259 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200260 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200261
262 /* Syntax of value was already checked when it's set. */
263 if (p == NULL)
264 {
265 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200266 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200267 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200268 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200269 *cols = atoi((char *)p + 1);
270 }
271 return minsize;
272}
273
274/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200275 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200276 */
277 static void
278set_term_and_win_size(term_T *term)
279{
Bram Moolenaar13568252018-03-16 20:46:58 +0100280#ifdef FEAT_GUI
281 if (term->tl_system)
282 {
283 /* Use the whole screen for the system command. However, it will start
284 * at the command line and scroll up as needed, using tl_toprow. */
285 term->tl_rows = Rows;
286 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200287 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100288 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100289#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200290 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200291 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200292 if (term->tl_rows != 0)
293 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
294 if (term->tl_cols != 0)
295 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200296 }
297 if (term->tl_rows == 0)
298 term->tl_rows = curwin->w_height;
299 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200300 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200301 if (term->tl_cols == 0)
302 term->tl_cols = curwin->w_width;
303 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200304 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200305}
306
307/*
308 * Initialize job options for a terminal job.
309 * Caller may overrule some of them.
310 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100311 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200312init_job_options(jobopt_T *opt)
313{
314 clear_job_options(opt);
315
316 opt->jo_mode = MODE_RAW;
317 opt->jo_out_mode = MODE_RAW;
318 opt->jo_err_mode = MODE_RAW;
319 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
320}
321
322/*
323 * Set job options mandatory for a terminal job.
324 */
325 static void
326setup_job_options(jobopt_T *opt, int rows, int cols)
327{
Bram Moolenaar4f974752019-02-17 17:44:42 +0100328#ifndef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200329 /* Win32: Redirecting the job output won't work, thus always connect stdout
330 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200331 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200332#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200333 {
334 /* Connect stdout to the terminal. */
335 opt->jo_io[PART_OUT] = JIO_BUFFER;
336 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
337 opt->jo_modifiable[PART_OUT] = 0;
338 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
339 }
340
Bram Moolenaar4f974752019-02-17 17:44:42 +0100341#ifndef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200342 /* Win32: Redirecting the job output won't work, thus always connect stderr
343 * here. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200344 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200345#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200346 {
347 /* Connect stderr to the terminal. */
348 opt->jo_io[PART_ERR] = JIO_BUFFER;
349 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
350 opt->jo_modifiable[PART_ERR] = 0;
351 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
352 }
353
354 opt->jo_pty = TRUE;
355 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
356 opt->jo_term_rows = rows;
357 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
358 opt->jo_term_cols = cols;
359}
360
361/*
Bram Moolenaar5c381eb2019-06-25 06:50:31 +0200362 * Flush messages on channels.
363 */
364 static void
365term_flush_messages()
366{
367 mch_check_messages();
368 parse_queued_messages();
369}
370
371/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100372 * Close a terminal buffer (and its window). Used when creating the terminal
373 * fails.
374 */
375 static void
376term_close_buffer(buf_T *buf, buf_T *old_curbuf)
377{
378 free_terminal(buf);
379 if (old_curbuf != NULL)
380 {
381 --curbuf->b_nwindows;
382 curbuf = old_curbuf;
383 curwin->w_buffer = curbuf;
384 ++curbuf->b_nwindows;
385 }
386
387 /* Wiping out the buffer will also close the window and call
388 * free_terminal(). */
389 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
390}
391
392/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200393 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100394 * Use either "argvar" or "argv", the other must be NULL.
395 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
396 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200397 * Returns NULL when failed.
398 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100399 buf_T *
400term_start(
401 typval_T *argvar,
402 char **argv,
403 jobopt_T *opt,
404 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200405{
406 exarg_T split_ea;
407 win_T *old_curwin = curwin;
408 term_T *term;
409 buf_T *old_curbuf = NULL;
410 int res;
411 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100412 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200413 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200414
415 if (check_restricted() || check_secure())
416 return NULL;
417
418 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
419 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
420 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
421 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)))
422 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100423 emsg(_(e_invarg));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200424 return NULL;
425 }
426
Bram Moolenaarc799fe22019-05-28 23:08:19 +0200427 term = ALLOC_CLEAR_ONE(term_T);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200428 if (term == NULL)
429 return NULL;
430 term->tl_dirty_row_end = MAX_ROW;
431 term->tl_cursor_visible = TRUE;
432 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
433 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100434#ifdef FEAT_GUI
435 term->tl_system = (flags & TERM_START_SYSTEM);
436#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200437 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100438 ga_init2(&term->tl_scrollback_postponed, sizeof(sb_line_T), 300);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200439
440 vim_memset(&split_ea, 0, sizeof(split_ea));
441 if (opt->jo_curwin)
442 {
443 /* Create a new buffer in the current window. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100444 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200445 {
446 no_write_message();
447 vim_free(term);
448 return NULL;
449 }
450 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100451 ECMD_HIDE
452 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
453 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200454 {
455 vim_free(term);
456 return NULL;
457 }
458 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100459 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200460 {
461 buf_T *buf;
462
463 /* Create a new buffer without a window. Make it the current buffer for
464 * a moment to be able to do the initialisations. */
465 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
466 BLN_NEW | BLN_LISTED);
467 if (buf == NULL || ml_open(buf) == FAIL)
468 {
469 vim_free(term);
470 return NULL;
471 }
472 old_curbuf = curbuf;
473 --curbuf->b_nwindows;
474 curbuf = buf;
475 curwin->w_buffer = buf;
476 ++curbuf->b_nwindows;
477 }
478 else
479 {
480 /* Open a new window or tab. */
481 split_ea.cmdidx = CMD_new;
482 split_ea.cmd = (char_u *)"new";
483 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100484 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200485 {
486 split_ea.line2 = opt->jo_term_rows;
487 split_ea.addr_count = 1;
488 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100489 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200490 {
491 split_ea.line2 = opt->jo_term_cols;
492 split_ea.addr_count = 1;
493 }
494
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100495 if (vertical)
496 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200497 ex_splitview(&split_ea);
498 if (curwin == old_curwin)
499 {
500 /* split failed */
501 vim_free(term);
502 return NULL;
503 }
504 }
505 term->tl_buffer = curbuf;
506 curbuf->b_term = term;
507
508 if (!opt->jo_hidden)
509 {
Bram Moolenaarda650582018-02-20 15:51:40 +0100510 /* Only one size was taken care of with :new, do the other one. With
511 * "curwin" both need to be done. */
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100512 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200513 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100514 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200515 win_setwidth(opt->jo_term_cols);
516 }
517
518 /* Link the new terminal in the list of active terminals. */
519 term->tl_next = first_term;
520 first_term = term;
521
522 if (opt->jo_term_name != NULL)
523 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100524 else if (argv != NULL)
525 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200526 else
527 {
528 int i;
529 size_t len;
530 char_u *cmd, *p;
531
532 if (argvar->v_type == VAR_STRING)
533 {
534 cmd = argvar->vval.v_string;
535 if (cmd == NULL)
536 cmd = (char_u *)"";
537 else if (STRCMP(cmd, "NONE") == 0)
538 cmd = (char_u *)"pty";
539 }
540 else if (argvar->v_type != VAR_LIST
541 || argvar->vval.v_list == NULL
542 || argvar->vval.v_list->lv_len < 1
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100543 || (cmd = tv_get_string_chk(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200544 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
545 cmd = (char_u*)"";
546
547 len = STRLEN(cmd) + 10;
Bram Moolenaar51e14382019-05-25 20:21:28 +0200548 p = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200549
550 for (i = 0; p != NULL; ++i)
551 {
552 /* Prepend a ! to the command name to avoid the buffer name equals
553 * the executable, otherwise ":w!" would overwrite it. */
554 if (i == 0)
555 vim_snprintf((char *)p, len, "!%s", cmd);
556 else
557 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
558 if (buflist_findname(p) == NULL)
559 {
560 vim_free(curbuf->b_ffname);
561 curbuf->b_ffname = p;
562 break;
563 }
564 }
565 }
566 curbuf->b_fname = curbuf->b_ffname;
567
568 if (opt->jo_term_opencmd != NULL)
569 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
570
571 if (opt->jo_eof_chars != NULL)
572 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
573
574 set_string_option_direct((char_u *)"buftype", -1,
575 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar7da1fb52018-08-04 16:54:11 +0200576 // Avoid that 'buftype' is reset when this buffer is entered.
577 curbuf->b_p_initialized = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200578
579 /* Mark the buffer as not modifiable. It can only be made modifiable after
580 * the job finished. */
581 curbuf->b_p_ma = FALSE;
582
583 set_term_and_win_size(term);
Bram Moolenaar4f974752019-02-17 17:44:42 +0100584#ifdef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200585 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
586#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200587 setup_job_options(opt, term->tl_rows, term->tl_cols);
588
Bram Moolenaar13568252018-03-16 20:46:58 +0100589 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100590 return curbuf;
591
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100592#if defined(FEAT_SESSION)
593 /* Remember the command for the session file. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100594 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100595 {
596 term->tl_command = vim_strsave((char_u *)"NONE");
597 }
598 else if (argvar->v_type == VAR_STRING)
599 {
600 char_u *cmd = argvar->vval.v_string;
601
602 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
603 term->tl_command = vim_strsave(cmd);
604 }
605 else if (argvar->v_type == VAR_LIST
606 && argvar->vval.v_list != NULL
607 && argvar->vval.v_list->lv_len > 0)
608 {
609 garray_T ga;
610 listitem_T *item;
611
612 ga_init2(&ga, 1, 100);
613 for (item = argvar->vval.v_list->lv_first;
614 item != NULL; item = item->li_next)
615 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100616 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100617 char_u *p;
618
619 if (s == NULL)
620 break;
621 p = vim_strsave_fnameescape(s, FALSE);
622 if (p == NULL)
623 break;
624 ga_concat(&ga, p);
625 vim_free(p);
626 ga_append(&ga, ' ');
627 }
628 if (item == NULL)
629 {
630 ga_append(&ga, NUL);
631 term->tl_command = ga.ga_data;
632 }
633 else
634 ga_clear(&ga);
635 }
636#endif
637
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100638 if (opt->jo_term_kill != NULL)
639 {
640 char_u *p = skiptowhite(opt->jo_term_kill);
641
642 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
643 }
644
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200645 if (opt->jo_term_api != NULL)
646 term->tl_api = vim_strsave(opt->jo_term_api);
647 else
648 term->tl_api = vim_strsave((char_u *)"Tapi_");
649
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200650 /* System dependent: setup the vterm and maybe start the job in it. */
Bram Moolenaar13568252018-03-16 20:46:58 +0100651 if (argv == NULL
652 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200653 && argvar->vval.v_string != NULL
654 && STRCMP(argvar->vval.v_string, "NONE") == 0)
655 res = create_pty_only(term, opt);
656 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200657 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200658
659 newbuf = curbuf;
660 if (res == OK)
661 {
662 /* Get and remember the size we ended up with. Update the pty. */
663 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
664 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100665#ifdef FEAT_GUI
666 if (term->tl_system)
667 {
668 /* display first line below typed command */
669 term->tl_toprow = msg_row + 1;
670 term->tl_dirty_row_end = 0;
671 }
672#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200673
674 /* Make sure we don't get stuck on sending keys to the job, it leads to
675 * a deadlock if the job is waiting for Vim to read. */
676 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
677
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200678 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200679 {
680 --curbuf->b_nwindows;
681 curbuf = old_curbuf;
682 curwin->w_buffer = curbuf;
683 ++curbuf->b_nwindows;
684 }
685 }
686 else
687 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100688 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200689 return NULL;
690 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100691
Bram Moolenaar13568252018-03-16 20:46:58 +0100692 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar28ed4df2019-10-26 16:21:40 +0200693 if (!opt->jo_hidden && !(flags & TERM_START_SYSTEM))
694 apply_autocmds(EVENT_TERMINALWINOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200695 return newbuf;
696}
697
698/*
699 * ":terminal": open a terminal window and execute a job in it.
700 */
701 void
702ex_terminal(exarg_T *eap)
703{
704 typval_T argvar[2];
705 jobopt_T opt;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100706 int opt_shell = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200707 char_u *cmd;
708 char_u *tofree = NULL;
709
710 init_job_options(&opt);
711
712 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100713 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200714 {
715 char_u *p, *ep;
716
717 cmd += 2;
718 p = skiptowhite(cmd);
719 ep = vim_strchr(cmd, '=');
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200720 if (ep != NULL)
721 {
722 if (ep < p)
723 p = ep;
724 else
725 ep = NULL;
726 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200727
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200728# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
729 && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
730 if (OPTARG_HAS("close"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200731 opt.jo_term_finish = 'c';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200732 else if (OPTARG_HAS("noclose"))
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100733 opt.jo_term_finish = 'n';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200734 else if (OPTARG_HAS("open"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200735 opt.jo_term_finish = 'o';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200736 else if (OPTARG_HAS("curwin"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200737 opt.jo_curwin = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200738 else if (OPTARG_HAS("hidden"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200739 opt.jo_hidden = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200740 else if (OPTARG_HAS("norestore"))
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100741 opt.jo_term_norestore = 1;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100742 else if (OPTARG_HAS("shell"))
743 opt_shell = TRUE;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200744 else if (OPTARG_HAS("kill") && ep != NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100745 {
746 opt.jo_set2 |= JO2_TERM_KILL;
747 opt.jo_term_kill = ep + 1;
748 p = skiptowhite(cmd);
749 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200750 else if (OPTARG_HAS("api"))
751 {
752 opt.jo_set2 |= JO2_TERM_API;
753 if (ep != NULL)
754 {
755 opt.jo_term_api = ep + 1;
756 p = skiptowhite(cmd);
757 }
758 else
759 opt.jo_term_api = NULL;
760 }
761 else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200762 {
763 opt.jo_set2 |= JO2_TERM_ROWS;
764 opt.jo_term_rows = atoi((char *)ep + 1);
765 p = skiptowhite(cmd);
766 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200767 else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200768 {
769 opt.jo_set2 |= JO2_TERM_COLS;
770 opt.jo_term_cols = atoi((char *)ep + 1);
771 p = skiptowhite(cmd);
772 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200773 else if (OPTARG_HAS("eof") && ep != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200774 {
775 char_u *buf = NULL;
776 char_u *keys;
777
778 p = skiptowhite(cmd);
779 *p = NUL;
Bram Moolenaar459fd782019-10-13 16:43:39 +0200780 keys = replace_termcodes(ep + 1, &buf,
781 REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200782 opt.jo_set2 |= JO2_EOF_CHARS;
783 opt.jo_eof_chars = vim_strsave(keys);
784 vim_free(buf);
785 *p = ' ';
786 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100787#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100788 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "type", 4) == 0
789 && ep != NULL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100790 {
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100791 int tty_type = NUL;
792
793 p = skiptowhite(cmd);
794 if (STRNICMP(ep + 1, "winpty", p - (ep + 1)) == 0)
795 tty_type = 'w';
796 else if (STRNICMP(ep + 1, "conpty", p - (ep + 1)) == 0)
797 tty_type = 'c';
798 else
799 {
800 semsg(e_invargval, "type");
801 goto theend;
802 }
803 opt.jo_set2 |= JO2_TTY_TYPE;
804 opt.jo_tty_type = tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100805 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100806#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200807 else
808 {
809 if (*p)
810 *p = NUL;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100811 semsg(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100812 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200813 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200814# undef OPTARG_HAS
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200815 cmd = skipwhite(p);
816 }
817 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100818 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200819 /* Make a copy of 'shell', an autocommand may change the option. */
820 tofree = cmd = vim_strsave(p_sh);
821
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100822 /* default to close when the shell exits */
823 if (opt.jo_term_finish == NUL)
824 opt.jo_term_finish = 'c';
825 }
826
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200827 if (eap->addr_count > 0)
828 {
829 /* Write lines from current buffer to the job. */
830 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
831 opt.jo_io[PART_IN] = JIO_BUFFER;
832 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
833 opt.jo_in_top = eap->line1;
834 opt.jo_in_bot = eap->line2;
835 }
836
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100837 if (opt_shell && tofree == NULL)
838 {
839#ifdef UNIX
840 char **argv = NULL;
841 char_u *tofree1 = NULL;
842 char_u *tofree2 = NULL;
843
844 // :term ++shell command
845 if (unix_build_argv(cmd, &argv, &tofree1, &tofree2) == OK)
846 term_start(NULL, argv, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
847 vim_free(tofree1);
848 vim_free(tofree2);
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100849 goto theend;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100850#else
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100851# ifdef MSWIN
852 long_u cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
853 char_u *newcmd;
854
855 newcmd = alloc(cmdlen);
856 if (newcmd == NULL)
857 goto theend;
858 tofree = newcmd;
859 vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd);
860 cmd = newcmd;
861# else
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100862 emsg(_("E279: Sorry, ++shell is not supported on this system"));
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100863 goto theend;
864# endif
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100865#endif
866 }
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100867 argvar[0].v_type = VAR_STRING;
868 argvar[0].vval.v_string = cmd;
869 argvar[1].v_type = VAR_UNKNOWN;
870 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100871
872theend:
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100873 vim_free(tofree);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200874 vim_free(opt.jo_eof_chars);
875}
876
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100877#if defined(FEAT_SESSION) || defined(PROTO)
878/*
879 * Write a :terminal command to the session file to restore the terminal in
880 * window "wp".
881 * Return FAIL if writing fails.
882 */
883 int
884term_write_session(FILE *fd, win_T *wp)
885{
886 term_T *term = wp->w_buffer->b_term;
887
888 /* Create the terminal and run the command. This is not without
889 * risk, but let's assume the user only creates a session when this
890 * will be OK. */
891 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
892 term->tl_cols, term->tl_rows) < 0)
893 return FAIL;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100894#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100895 if (fprintf(fd, "++type=%s ", term->tl_job->jv_tty_type) < 0)
896 return FAIL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100897#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100898 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
899 return FAIL;
900
901 return put_eol(fd);
902}
903
904/*
905 * Return TRUE if "buf" has a terminal that should be restored.
906 */
907 int
908term_should_restore(buf_T *buf)
909{
910 term_T *term = buf->b_term;
911
912 return term != NULL && (term->tl_command == NULL
913 || STRCMP(term->tl_command, "NONE") != 0);
914}
915#endif
916
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200917/*
918 * Free the scrollback buffer for "term".
919 */
920 static void
921free_scrollback(term_T *term)
922{
923 int i;
924
925 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
926 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
927 ga_clear(&term->tl_scrollback);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100928 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
929 vim_free(((sb_line_T *)term->tl_scrollback_postponed.ga_data + i)->sb_cells);
930 ga_clear(&term->tl_scrollback_postponed);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200931}
932
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100933
934// Terminals that need to be freed soon.
Bram Moolenaar840d16f2019-09-10 21:27:18 +0200935static term_T *terminals_to_free = NULL;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100936
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200937/*
938 * Free a terminal and everything it refers to.
939 * Kills the job if there is one.
940 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100941 * The actual terminal structure is freed later in free_unused_terminals(),
942 * because callbacks may wipe out a buffer while the terminal is still
943 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200944 */
945 void
946free_terminal(buf_T *buf)
947{
948 term_T *term = buf->b_term;
949 term_T *tp;
950
951 if (term == NULL)
952 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100953
954 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200955 if (first_term == term)
956 first_term = term->tl_next;
957 else
958 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
959 if (tp->tl_next == term)
960 {
961 tp->tl_next = term->tl_next;
962 break;
963 }
964
965 if (term->tl_job != NULL)
966 {
967 if (term->tl_job->jv_status != JOB_ENDED
968 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100969 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200970 job_stop(term->tl_job, NULL, "kill");
971 job_unref(term->tl_job);
972 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100973 term->tl_next = terminals_to_free;
974 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200975
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200976 buf->b_term = NULL;
977 if (in_terminal_loop == term)
978 in_terminal_loop = NULL;
979}
980
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100981 void
982free_unused_terminals()
983{
984 while (terminals_to_free != NULL)
985 {
986 term_T *term = terminals_to_free;
987
988 terminals_to_free = term->tl_next;
989
990 free_scrollback(term);
991
992 term_free_vterm(term);
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200993 vim_free(term->tl_api);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100994 vim_free(term->tl_title);
995#ifdef FEAT_SESSION
996 vim_free(term->tl_command);
997#endif
998 vim_free(term->tl_kill);
999 vim_free(term->tl_status_text);
1000 vim_free(term->tl_opencmd);
1001 vim_free(term->tl_eof_chars);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001002 vim_free(term->tl_arg0_cmd);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001003#ifdef MSWIN
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001004 if (term->tl_out_fd != NULL)
1005 fclose(term->tl_out_fd);
1006#endif
1007 vim_free(term->tl_cursor_color);
1008 vim_free(term);
1009 }
1010}
1011
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001012/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001013 * Get the part that is connected to the tty. Normally this is PART_IN, but
1014 * when writing buffer lines to the job it can be another. This makes it
1015 * possible to do "1,5term vim -".
1016 */
1017 static ch_part_T
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02001018get_tty_part(term_T *term UNUSED)
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001019{
1020#ifdef UNIX
1021 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
1022 int i;
1023
1024 for (i = 0; i < 3; ++i)
1025 {
1026 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
1027
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01001028 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001029 return parts[i];
1030 }
1031#endif
1032 return PART_IN;
1033}
1034
1035/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001036 * Write job output "msg[len]" to the vterm.
1037 */
1038 static void
1039term_write_job_output(term_T *term, char_u *msg, size_t len)
1040{
1041 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001042 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001043
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001044 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001045
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001046 /* flush vterm buffer when vterm responded to control sequence */
1047 if (prevlen != vterm_output_get_buffer_current(vterm))
1048 {
1049 char buf[KEY_BUF_LEN];
1050 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
1051
1052 if (curlen > 0)
1053 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1054 (char_u *)buf, (int)curlen, NULL);
1055 }
1056
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001057 /* this invokes the damage callbacks */
1058 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
1059}
1060
1061 static void
1062update_cursor(term_T *term, int redraw)
1063{
1064 if (term->tl_normal_mode)
1065 return;
Bram Moolenaar13568252018-03-16 20:46:58 +01001066#ifdef FEAT_GUI
1067 if (term->tl_system)
1068 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
1069 term->tl_cursor_pos.col);
1070 else
1071#endif
1072 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001073 if (redraw)
1074 {
1075 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
1076 cursor_on();
1077 out_flush();
1078#ifdef FEAT_GUI
1079 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001080 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001081 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001082 gui_mch_flush();
1083 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001084#endif
1085 }
1086}
1087
1088/*
1089 * Invoked when "msg" output from a job was received. Write it to the terminal
1090 * of "buffer".
1091 */
1092 void
1093write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1094{
1095 size_t len = STRLEN(msg);
1096 term_T *term = buffer->b_term;
1097
Bram Moolenaar4f974752019-02-17 17:44:42 +01001098#ifdef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +02001099 /* Win32: Cannot redirect output of the job, intercept it here and write to
1100 * the file. */
1101 if (term->tl_out_fd != NULL)
1102 {
1103 ch_log(channel, "Writing %d bytes to output file", (int)len);
1104 fwrite(msg, len, 1, term->tl_out_fd);
1105 return;
1106 }
1107#endif
1108
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001109 if (term->tl_vterm == NULL)
1110 {
1111 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1112 return;
1113 }
1114 ch_log(channel, "writing %d bytes to terminal", (int)len);
1115 term_write_job_output(term, msg, len);
1116
Bram Moolenaar13568252018-03-16 20:46:58 +01001117#ifdef FEAT_GUI
1118 if (term->tl_system)
1119 {
1120 /* show system output, scrolling up the screen as needed */
1121 update_system_term(term);
1122 update_cursor(term, TRUE);
1123 }
1124 else
1125#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001126 /* In Terminal-Normal mode we are displaying the buffer, not the terminal
1127 * contents, thus no screen update is needed. */
1128 if (!term->tl_normal_mode)
1129 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001130 // Don't use update_screen() when editing the command line, it gets
1131 // cleared.
1132 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001133 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001134 if (buffer == curbuf && (State & CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001135 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001136 update_screen(VALID_NO_UPDATE);
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001137 /* update_screen() can be slow, check the terminal wasn't closed
1138 * already */
1139 if (buffer == curbuf && curbuf->b_term != NULL)
1140 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001141 }
1142 else
1143 redraw_after_callback(TRUE);
1144 }
1145}
1146
1147/*
1148 * Send a mouse position and click to the vterm
1149 */
1150 static int
1151term_send_mouse(VTerm *vterm, int button, int pressed)
1152{
1153 VTermModifier mod = VTERM_MOD_NONE;
1154
1155 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +02001156 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001157 if (button != 0)
1158 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001159 return TRUE;
1160}
1161
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001162static int enter_mouse_col = -1;
1163static int enter_mouse_row = -1;
1164
1165/*
1166 * Handle a mouse click, drag or release.
1167 * Return TRUE when a mouse event is sent to the terminal.
1168 */
1169 static int
1170term_mouse_click(VTerm *vterm, int key)
1171{
1172#if defined(FEAT_CLIPBOARD)
1173 /* For modeless selection mouse drag and release events are ignored, unless
1174 * they are preceded with a mouse down event */
1175 static int ignore_drag_release = TRUE;
1176 VTermMouseState mouse_state;
1177
1178 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1179 if (mouse_state.flags == 0)
1180 {
1181 /* Terminal is not using the mouse, use modeless selection. */
1182 switch (key)
1183 {
1184 case K_LEFTDRAG:
1185 case K_LEFTRELEASE:
1186 case K_RIGHTDRAG:
1187 case K_RIGHTRELEASE:
1188 /* Ignore drag and release events when the button-down wasn't
1189 * seen before. */
1190 if (ignore_drag_release)
1191 {
1192 int save_mouse_col, save_mouse_row;
1193
1194 if (enter_mouse_col < 0)
1195 break;
1196
1197 /* mouse click in the window gave us focus, handle that
1198 * click now */
1199 save_mouse_col = mouse_col;
1200 save_mouse_row = mouse_row;
1201 mouse_col = enter_mouse_col;
1202 mouse_row = enter_mouse_row;
1203 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1204 mouse_col = save_mouse_col;
1205 mouse_row = save_mouse_row;
1206 }
1207 /* FALLTHROUGH */
1208 case K_LEFTMOUSE:
1209 case K_RIGHTMOUSE:
1210 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1211 ignore_drag_release = TRUE;
1212 else
1213 ignore_drag_release = FALSE;
1214 /* Should we call mouse_has() here? */
1215 if (clip_star.available)
1216 {
1217 int button, is_click, is_drag;
1218
1219 button = get_mouse_button(KEY2TERMCAP1(key),
1220 &is_click, &is_drag);
1221 if (mouse_model_popup() && button == MOUSE_LEFT
1222 && (mod_mask & MOD_MASK_SHIFT))
1223 {
1224 /* Translate shift-left to right button. */
1225 button = MOUSE_RIGHT;
1226 mod_mask &= ~MOD_MASK_SHIFT;
1227 }
1228 clip_modeless(button, is_click, is_drag);
1229 }
1230 break;
1231
1232 case K_MIDDLEMOUSE:
1233 if (clip_star.available)
1234 insert_reg('*', TRUE);
1235 break;
1236 }
1237 enter_mouse_col = -1;
1238 return FALSE;
1239 }
1240#endif
1241 enter_mouse_col = -1;
1242
1243 switch (key)
1244 {
1245 case K_LEFTMOUSE:
1246 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1247 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1248 case K_LEFTRELEASE:
1249 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1250 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1251 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1252 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1253 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1254 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1255 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1256 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1257 }
1258 return TRUE;
1259}
1260
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001261/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001262 * Convert typed key "c" with modifiers "modmask" into bytes to send to the
1263 * job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001264 * Return the number of bytes in "buf".
1265 */
1266 static int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001267term_convert_key(term_T *term, int c, int modmask, char *buf)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001268{
1269 VTerm *vterm = term->tl_vterm;
1270 VTermKey key = VTERM_KEY_NONE;
1271 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001272 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001273
1274 switch (c)
1275 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001276 /* don't use VTERM_KEY_ENTER, it may do an unwanted conversion */
1277
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001278 /* don't use VTERM_KEY_BACKSPACE, it always
1279 * becomes 0x7f DEL */
1280 case K_BS: c = term_backspace_char; break;
1281
1282 case ESC: key = VTERM_KEY_ESCAPE; break;
1283 case K_DEL: key = VTERM_KEY_DEL; break;
1284 case K_DOWN: key = VTERM_KEY_DOWN; break;
1285 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1286 key = VTERM_KEY_DOWN; break;
1287 case K_END: key = VTERM_KEY_END; break;
1288 case K_S_END: mod = VTERM_MOD_SHIFT;
1289 key = VTERM_KEY_END; break;
1290 case K_C_END: mod = VTERM_MOD_CTRL;
1291 key = VTERM_KEY_END; break;
1292 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1293 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1294 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1295 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1296 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1297 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1298 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1299 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1300 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1301 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1302 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1303 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1304 case K_HOME: key = VTERM_KEY_HOME; break;
1305 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1306 key = VTERM_KEY_HOME; break;
1307 case K_C_HOME: mod = VTERM_MOD_CTRL;
1308 key = VTERM_KEY_HOME; break;
1309 case K_INS: key = VTERM_KEY_INS; break;
1310 case K_K0: key = VTERM_KEY_KP_0; break;
1311 case K_K1: key = VTERM_KEY_KP_1; break;
1312 case K_K2: key = VTERM_KEY_KP_2; break;
1313 case K_K3: key = VTERM_KEY_KP_3; break;
1314 case K_K4: key = VTERM_KEY_KP_4; break;
1315 case K_K5: key = VTERM_KEY_KP_5; break;
1316 case K_K6: key = VTERM_KEY_KP_6; break;
1317 case K_K7: key = VTERM_KEY_KP_7; break;
1318 case K_K8: key = VTERM_KEY_KP_8; break;
1319 case K_K9: key = VTERM_KEY_KP_9; break;
1320 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
1321 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
1322 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
1323 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
1324 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
1325 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
1326 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1327 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
1328 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
1329 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
1330 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1331 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1332 case K_LEFT: key = VTERM_KEY_LEFT; break;
1333 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1334 key = VTERM_KEY_LEFT; break;
1335 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1336 key = VTERM_KEY_LEFT; break;
1337 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1338 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1339 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1340 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1341 key = VTERM_KEY_RIGHT; break;
1342 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1343 key = VTERM_KEY_RIGHT; break;
1344 case K_UP: key = VTERM_KEY_UP; break;
1345 case K_S_UP: mod = VTERM_MOD_SHIFT;
1346 key = VTERM_KEY_UP; break;
1347 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001348 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1349 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001350
Bram Moolenaara42ad572017-11-16 13:08:04 +01001351 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1352 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001353 case K_MOUSELEFT: /* TODO */ return 0;
1354 case K_MOUSERIGHT: /* TODO */ return 0;
1355
1356 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001357 case K_LEFTMOUSE_NM:
1358 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001359 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001360 case K_LEFTRELEASE_NM:
1361 case K_MOUSEMOVE:
1362 case K_MIDDLEMOUSE:
1363 case K_MIDDLEDRAG:
1364 case K_MIDDLERELEASE:
1365 case K_RIGHTMOUSE:
1366 case K_RIGHTDRAG:
1367 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1368 return 0;
1369 other = TRUE;
1370 break;
1371
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001372 case K_X1MOUSE: /* TODO */ return 0;
1373 case K_X1DRAG: /* TODO */ return 0;
1374 case K_X1RELEASE: /* TODO */ return 0;
1375 case K_X2MOUSE: /* TODO */ return 0;
1376 case K_X2DRAG: /* TODO */ return 0;
1377 case K_X2RELEASE: /* TODO */ return 0;
1378
1379 case K_IGNORE: return 0;
1380 case K_NOP: return 0;
1381 case K_UNDO: return 0;
1382 case K_HELP: return 0;
1383 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1384 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1385 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1386 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1387 case K_SELECT: return 0;
1388#ifdef FEAT_GUI
1389 case K_VER_SCROLLBAR: return 0;
1390 case K_HOR_SCROLLBAR: return 0;
1391#endif
1392#ifdef FEAT_GUI_TABLINE
1393 case K_TABLINE: return 0;
1394 case K_TABMENU: return 0;
1395#endif
1396#ifdef FEAT_NETBEANS_INTG
1397 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1398#endif
1399#ifdef FEAT_DND
1400 case K_DROP: return 0;
1401#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001402 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001403 case K_PS: vterm_keyboard_start_paste(vterm);
1404 other = TRUE;
1405 break;
1406 case K_PE: vterm_keyboard_end_paste(vterm);
1407 other = TRUE;
1408 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001409 }
1410
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001411 // add modifiers for the typed key
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001412 if (modmask & MOD_MASK_SHIFT)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001413 mod |= VTERM_MOD_SHIFT;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001414 if (modmask & MOD_MASK_CTRL)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001415 mod |= VTERM_MOD_CTRL;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001416 if (modmask & (MOD_MASK_ALT | MOD_MASK_META))
Bram Moolenaar459fd782019-10-13 16:43:39 +02001417 mod |= VTERM_MOD_ALT;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001418
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001419 /*
1420 * Convert special keys to vterm keys:
1421 * - Write keys to vterm: vterm_keyboard_key()
1422 * - Write output to channel.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001423 */
1424 if (key != VTERM_KEY_NONE)
1425 /* Special key, let vterm convert it. */
1426 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001427 else if (!other)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001428 /* Normal character, let vterm convert it. */
1429 vterm_keyboard_unichar(vterm, c, mod);
1430
1431 /* Read back the converted escape sequence. */
1432 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1433}
1434
1435/*
1436 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001437 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001438 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001439 */
1440 static int
1441term_job_running_check(term_T *term, int check_job_status)
1442{
1443 /* Also consider the job finished when the channel is closed, to avoid a
1444 * race condition when updating the title. */
1445 if (term != NULL
1446 && term->tl_job != NULL
1447 && channel_is_open(term->tl_job->jv_channel))
1448 {
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001449 job_T *job = term->tl_job;
1450
1451 // Careful: Checking the job status may invoked callbacks, which close
1452 // the buffer and terminate "term". However, "job" will not be freed
1453 // yet.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001454 if (check_job_status)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001455 job_status(job);
1456 return (job->jv_status == JOB_STARTED
1457 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001458 }
1459 return FALSE;
1460}
1461
1462/*
1463 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001464 */
1465 int
1466term_job_running(term_T *term)
1467{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001468 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001469}
1470
1471/*
1472 * Return TRUE if "term" has an active channel and used ":term NONE".
1473 */
1474 int
1475term_none_open(term_T *term)
1476{
1477 /* Also consider the job finished when the channel is closed, to avoid a
1478 * race condition when updating the title. */
1479 return term != NULL
1480 && term->tl_job != NULL
1481 && channel_is_open(term->tl_job->jv_channel)
1482 && term->tl_job->jv_channel->ch_keep_open;
1483}
1484
1485/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001486 * Used when exiting: kill the job in "buf" if so desired.
1487 * Return OK when the job finished.
1488 * Return FAIL when the job is still running.
1489 */
1490 int
1491term_try_stop_job(buf_T *buf)
1492{
1493 int count;
1494 char *how = (char *)buf->b_term->tl_kill;
1495
1496#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1497 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1498 {
1499 char_u buff[DIALOG_MSG_SIZE];
1500 int ret;
1501
1502 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1503 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1504 if (ret == VIM_YES)
1505 how = "kill";
1506 else if (ret == VIM_CANCEL)
1507 return FAIL;
1508 }
1509#endif
1510 if (how == NULL || *how == NUL)
1511 return FAIL;
1512
1513 job_stop(buf->b_term->tl_job, NULL, how);
1514
Bram Moolenaar9172d232019-01-29 23:06:54 +01001515 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001516 for (count = 0; count < 100; ++count)
1517 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001518 job_T *job;
1519
1520 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001521 if (!buf_valid(buf)
1522 || buf->b_term == NULL
1523 || buf->b_term->tl_job == NULL)
1524 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001525 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001526
Bram Moolenaar9172d232019-01-29 23:06:54 +01001527 // Call job_status() to update jv_status. It may cause the job to be
1528 // cleaned up but it won't be freed.
1529 job_status(job);
1530 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001531 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001532
Bram Moolenaar8f7ab4b2019-10-23 23:16:45 +02001533 ui_delay(10L, TRUE);
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02001534 term_flush_messages();
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001535 }
1536 return FAIL;
1537}
1538
1539/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001540 * Add the last line of the scrollback buffer to the buffer in the window.
1541 */
1542 static void
1543add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1544{
1545 buf_T *buf = term->tl_buffer;
1546 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1547 linenr_T lnum = buf->b_ml.ml_line_count;
1548
Bram Moolenaar4f974752019-02-17 17:44:42 +01001549#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001550 if (!enc_utf8 && enc_codepage > 0)
1551 {
1552 WCHAR *ret = NULL;
1553 int length = 0;
1554
1555 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1556 &ret, &length);
1557 if (ret != NULL)
1558 {
1559 WideCharToMultiByte_alloc(enc_codepage, 0,
1560 ret, length, (char **)&text, &len, 0, 0);
1561 vim_free(ret);
1562 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1563 vim_free(text);
1564 }
1565 }
1566 else
1567#endif
1568 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1569 if (empty)
1570 {
1571 /* Delete the empty line that was in the empty buffer. */
1572 curbuf = buf;
1573 ml_delete(1, FALSE);
1574 curbuf = curwin->w_buffer;
1575 }
1576}
1577
1578 static void
1579cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1580{
1581 attr->width = cell->width;
1582 attr->attrs = cell->attrs;
1583 attr->fg = cell->fg;
1584 attr->bg = cell->bg;
1585}
1586
1587 static int
1588equal_celattr(cellattr_T *a, cellattr_T *b)
1589{
1590 /* Comparing the colors should be sufficient. */
1591 return a->fg.red == b->fg.red
1592 && a->fg.green == b->fg.green
1593 && a->fg.blue == b->fg.blue
1594 && a->bg.red == b->bg.red
1595 && a->bg.green == b->bg.green
1596 && a->bg.blue == b->bg.blue;
1597}
1598
Bram Moolenaard96ff162018-02-18 22:13:29 +01001599/*
1600 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1601 * line at this position. Otherwise at the end.
1602 */
1603 static int
1604add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1605{
1606 if (ga_grow(&term->tl_scrollback, 1) == OK)
1607 {
1608 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1609 + term->tl_scrollback.ga_len;
1610
1611 if (lnum > 0)
1612 {
1613 int i;
1614
1615 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1616 {
1617 *line = *(line - 1);
1618 --line;
1619 }
1620 }
1621 line->sb_cols = 0;
1622 line->sb_cells = NULL;
1623 line->sb_fill_attr = *fill_attr;
1624 ++term->tl_scrollback.ga_len;
1625 return OK;
1626 }
1627 return FALSE;
1628}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001629
1630/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001631 * Remove the terminal contents from the scrollback and the buffer.
1632 * Used before adding a new scrollback line or updating the buffer for lines
1633 * displayed in the terminal.
1634 */
1635 static void
1636cleanup_scrollback(term_T *term)
1637{
1638 sb_line_T *line;
1639 garray_T *gap;
1640
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001641 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001642 gap = &term->tl_scrollback;
1643 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1644 && gap->ga_len > 0)
1645 {
1646 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1647 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1648 vim_free(line->sb_cells);
1649 --gap->ga_len;
1650 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001651 curbuf = curwin->w_buffer;
1652 if (curbuf == term->tl_buffer)
1653 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001654}
1655
1656/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001657 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001658 */
1659 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001660update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001661{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001662 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001663 int len;
1664 int lines_skipped = 0;
1665 VTermPos pos;
1666 VTermScreenCell cell;
1667 cellattr_T fill_attr, new_fill_attr;
1668 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001669
1670 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1671 "Adding terminal window snapshot to buffer");
1672
1673 /* First remove the lines that were appended before, they might be
1674 * outdated. */
1675 cleanup_scrollback(term);
1676
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001677 screen = vterm_obtain_screen(term->tl_vterm);
1678 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001679 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1680 {
1681 len = 0;
1682 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1683 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1684 && cell.chars[0] != NUL)
1685 {
1686 len = pos.col + 1;
1687 new_fill_attr = term->tl_default_color;
1688 }
1689 else
1690 /* Assume the last attr is the filler attr. */
1691 cell2cellattr(&cell, &new_fill_attr);
1692
1693 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1694 ++lines_skipped;
1695 else
1696 {
1697 while (lines_skipped > 0)
1698 {
1699 /* Line was skipped, add an empty line. */
1700 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001701 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001702 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001703 }
1704
1705 if (len == 0)
1706 p = NULL;
1707 else
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001708 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001709 if ((p != NULL || len == 0)
1710 && ga_grow(&term->tl_scrollback, 1) == OK)
1711 {
1712 garray_T ga;
1713 int width;
1714 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1715 + term->tl_scrollback.ga_len;
1716
1717 ga_init2(&ga, 1, 100);
1718 for (pos.col = 0; pos.col < len; pos.col += width)
1719 {
1720 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1721 {
1722 width = 1;
1723 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1724 if (ga_grow(&ga, 1) == OK)
1725 ga.ga_len += utf_char2bytes(' ',
1726 (char_u *)ga.ga_data + ga.ga_len);
1727 }
1728 else
1729 {
1730 width = cell.width;
1731
1732 cell2cellattr(&cell, &p[pos.col]);
1733
Bram Moolenaara79fd562018-12-20 20:47:32 +01001734 // Each character can be up to 6 bytes.
1735 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001736 {
1737 int i;
1738 int c;
1739
1740 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1741 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1742 (char_u *)ga.ga_data + ga.ga_len);
1743 }
1744 }
1745 }
1746 line->sb_cols = len;
1747 line->sb_cells = p;
1748 line->sb_fill_attr = new_fill_attr;
1749 fill_attr = new_fill_attr;
1750 ++term->tl_scrollback.ga_len;
1751
1752 if (ga_grow(&ga, 1) == FAIL)
1753 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1754 else
1755 {
1756 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1757 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1758 }
1759 ga_clear(&ga);
1760 }
1761 else
1762 vim_free(p);
1763 }
1764 }
1765
Bram Moolenaarf3aea592018-11-11 22:18:21 +01001766 // Add trailing empty lines.
1767 for (pos.row = term->tl_scrollback.ga_len;
1768 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1769 ++pos.row)
1770 {
1771 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1772 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1773 }
1774
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001775 term->tl_dirty_snapshot = FALSE;
1776#ifdef FEAT_TIMERS
1777 term->tl_timer_set = FALSE;
1778#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001779}
1780
1781/*
1782 * If needed, add the current lines of the terminal to scrollback and to the
1783 * buffer. Called after the job has ended and when switching to
1784 * Terminal-Normal mode.
1785 * When "redraw" is TRUE redraw the windows that show the terminal.
1786 */
1787 static void
1788may_move_terminal_to_buffer(term_T *term, int redraw)
1789{
1790 win_T *wp;
1791
1792 if (term->tl_vterm == NULL)
1793 return;
1794
1795 /* Update the snapshot only if something changes or the buffer does not
1796 * have all the lines. */
1797 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
1798 <= term->tl_scrollback_scrolled)
1799 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001800
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001801 /* Obtain the current background color. */
1802 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1803 &term->tl_default_color.fg, &term->tl_default_color.bg);
1804
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001805 if (redraw)
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001806 FOR_ALL_WINDOWS(wp)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001807 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001808 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001809 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001810 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1811 wp->w_cursor.col = 0;
1812 wp->w_valid = 0;
1813 if (wp->w_cursor.lnum >= wp->w_height)
1814 {
1815 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001816
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001817 if (wp->w_topline < min_topline)
1818 wp->w_topline = min_topline;
1819 }
1820 redraw_win_later(wp, NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001821 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001822 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001823}
1824
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001825#if defined(FEAT_TIMERS) || defined(PROTO)
1826/*
1827 * Check if any terminal timer expired. If so, copy text from the terminal to
1828 * the buffer.
1829 * Return the time until the next timer will expire.
1830 */
1831 int
1832term_check_timers(int next_due_arg, proftime_T *now)
1833{
1834 term_T *term;
1835 int next_due = next_due_arg;
1836
1837 for (term = first_term; term != NULL; term = term->tl_next)
1838 {
1839 if (term->tl_timer_set && !term->tl_normal_mode)
1840 {
1841 long this_due = proftime_time_left(&term->tl_timer_due, now);
1842
1843 if (this_due <= 1)
1844 {
1845 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001846 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001847 }
1848 else if (next_due == -1 || next_due > this_due)
1849 next_due = this_due;
1850 }
1851 }
1852
1853 return next_due;
1854}
1855#endif
1856
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001857/*
1858 * When "normal_mode" is TRUE set the terminal to Terminal-Normal mode,
1859 * otherwise end it.
1860 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001861 static void
1862set_terminal_mode(term_T *term, int normal_mode)
1863{
1864 term->tl_normal_mode = normal_mode;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001865 if (!normal_mode)
1866 handle_postponed_scrollback(term);
Bram Moolenaard23a8232018-02-10 18:45:26 +01001867 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001868 if (term->tl_buffer == curbuf)
1869 maketitle();
1870}
1871
1872/*
1873 * Called after the job if finished and Terminal mode is not active:
1874 * Move the vterm contents into the scrollback buffer and free the vterm.
1875 */
1876 static void
1877cleanup_vterm(term_T *term)
1878{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001879 set_terminal_mode(term, FALSE);
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001880 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001881 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001882 term_free_vterm(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001883}
1884
1885/*
1886 * Switch from Terminal-Job mode to Terminal-Normal mode.
1887 * Suspends updating the terminal window.
1888 */
1889 static void
1890term_enter_normal_mode(void)
1891{
1892 term_T *term = curbuf->b_term;
1893
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001894 set_terminal_mode(term, TRUE);
1895
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001896 /* Append the current terminal contents to the buffer. */
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001897 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001898
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001899 /* Move the window cursor to the position of the cursor in the
1900 * terminal. */
1901 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1902 + term->tl_cursor_pos.row + 1;
1903 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02001904 if (coladvance(term->tl_cursor_pos.col) == FAIL)
1905 coladvance(MAXCOL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001906
1907 /* Display the same lines as in the terminal. */
1908 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1909}
1910
1911/*
1912 * Returns TRUE if the current window contains a terminal and we are in
1913 * Terminal-Normal mode.
1914 */
1915 int
1916term_in_normal_mode(void)
1917{
1918 term_T *term = curbuf->b_term;
1919
1920 return term != NULL && term->tl_normal_mode;
1921}
1922
1923/*
1924 * Switch from Terminal-Normal mode to Terminal-Job mode.
1925 * Restores updating the terminal window.
1926 */
1927 void
1928term_enter_job_mode()
1929{
1930 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001931
1932 set_terminal_mode(term, FALSE);
1933
1934 if (term->tl_channel_closed)
1935 cleanup_vterm(term);
1936 redraw_buf_and_status_later(curbuf, NOT_VALID);
1937}
1938
1939/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001940 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001941 * Note: while waiting a terminal may be closed and freed if the channel is
1942 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001943 */
1944 static int
1945term_vgetc()
1946{
1947 int c;
1948 int save_State = State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001949 int modify_other_keys =
1950 vterm_is_modify_other_keys(curbuf->b_term->tl_vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001951
1952 State = TERMINAL;
1953 got_int = FALSE;
Bram Moolenaar4f974752019-02-17 17:44:42 +01001954#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001955 ctrl_break_was_pressed = FALSE;
1956#endif
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001957 if (modify_other_keys)
1958 ++no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001959 c = vgetc();
1960 got_int = FALSE;
1961 State = save_State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001962 if (modify_other_keys)
1963 --no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001964 return c;
1965}
1966
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001967static int mouse_was_outside = FALSE;
1968
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001969/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001970 * Send key "c" with modifiers "modmask" to terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001971 * Return FAIL when the key needs to be handled in Normal mode.
1972 * Return OK when the key was dropped or sent to the terminal.
1973 */
1974 int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001975send_keys_to_term(term_T *term, int c, int modmask, int typed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001976{
1977 char msg[KEY_BUF_LEN];
1978 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001979 int dragging_outside = FALSE;
1980
1981 /* Catch keys that need to be handled as in Normal mode. */
1982 switch (c)
1983 {
1984 case NUL:
1985 case K_ZERO:
1986 if (typed)
1987 stuffcharReadbuff(c);
1988 return FAIL;
1989
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001990 case K_TABLINE:
1991 stuffcharReadbuff(c);
1992 return FAIL;
1993
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001994 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001995 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001996 return FAIL;
1997
1998 case K_LEFTDRAG:
1999 case K_MIDDLEDRAG:
2000 case K_RIGHTDRAG:
2001 case K_X1DRAG:
2002 case K_X2DRAG:
2003 dragging_outside = mouse_was_outside;
2004 /* FALLTHROUGH */
2005 case K_LEFTMOUSE:
2006 case K_LEFTMOUSE_NM:
2007 case K_LEFTRELEASE:
2008 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01002009 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002010 case K_MIDDLEMOUSE:
2011 case K_MIDDLERELEASE:
2012 case K_RIGHTMOUSE:
2013 case K_RIGHTRELEASE:
2014 case K_X1MOUSE:
2015 case K_X1RELEASE:
2016 case K_X2MOUSE:
2017 case K_X2RELEASE:
2018
2019 case K_MOUSEUP:
2020 case K_MOUSEDOWN:
2021 case K_MOUSELEFT:
2022 case K_MOUSERIGHT:
2023 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01002024 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02002025 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01002026 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002027 || dragging_outside)
2028 {
Bram Moolenaarce6179c2017-12-05 13:06:16 +01002029 /* click or scroll outside the current window or on status line
2030 * or vertical separator */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002031 if (typed)
2032 {
2033 stuffcharReadbuff(c);
2034 mouse_was_outside = TRUE;
2035 }
2036 return FAIL;
2037 }
2038 }
2039 if (typed)
2040 mouse_was_outside = FALSE;
2041
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002042 // Convert the typed key to a sequence of bytes for the job.
2043 len = term_convert_key(term, c, modmask, msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002044 if (len > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002045 // TODO: if FAIL is returned, stop?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002046 channel_send(term->tl_job->jv_channel, get_tty_part(term),
2047 (char_u *)msg, (int)len, NULL);
2048
2049 return OK;
2050}
2051
2052 static void
2053position_cursor(win_T *wp, VTermPos *pos)
2054{
2055 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
2056 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
2057 wp->w_valid |= (VALID_WCOL|VALID_WROW);
2058}
2059
2060/*
2061 * Handle CTRL-W "": send register contents to the job.
2062 */
2063 static void
2064term_paste_register(int prev_c UNUSED)
2065{
2066 int c;
2067 list_T *l;
2068 listitem_T *item;
2069 long reglen = 0;
2070 int type;
2071
2072#ifdef FEAT_CMDL_INFO
2073 if (add_to_showcmd(prev_c))
2074 if (add_to_showcmd('"'))
2075 out_flush();
2076#endif
2077 c = term_vgetc();
2078#ifdef FEAT_CMDL_INFO
2079 clear_showcmd();
2080#endif
2081 if (!term_use_loop())
2082 /* job finished while waiting for a character */
2083 return;
2084
2085 /* CTRL-W "= prompt for expression to evaluate. */
2086 if (c == '=' && get_expr_register() != '=')
2087 return;
2088 if (!term_use_loop())
2089 /* job finished while waiting for a character */
2090 return;
2091
2092 l = (list_T *)get_reg_contents(c, GREG_LIST);
2093 if (l != NULL)
2094 {
2095 type = get_reg_type(c, &reglen);
2096 for (item = l->lv_first; item != NULL; item = item->li_next)
2097 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01002098 char_u *s = tv_get_string(&item->li_tv);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002099#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002100 char_u *tmp = s;
2101
2102 if (!enc_utf8 && enc_codepage > 0)
2103 {
2104 WCHAR *ret = NULL;
2105 int length = 0;
2106
2107 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2108 (int)STRLEN(s), &ret, &length);
2109 if (ret != NULL)
2110 {
2111 WideCharToMultiByte_alloc(CP_UTF8, 0,
2112 ret, length, (char **)&s, &length, 0, 0);
2113 vim_free(ret);
2114 }
2115 }
2116#endif
2117 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2118 s, (int)STRLEN(s), NULL);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002119#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002120 if (tmp != s)
2121 vim_free(s);
2122#endif
2123
2124 if (item->li_next != NULL || type == MLINE)
2125 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2126 (char_u *)"\r", 1, NULL);
2127 }
2128 list_free(l);
2129 }
2130}
2131
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002132/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002133 * Return TRUE when waiting for a character in the terminal, the cursor of the
2134 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002135 */
2136 int
2137terminal_is_active()
2138{
2139 return in_terminal_loop != NULL;
2140}
2141
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002142#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002143 cursorentry_T *
2144term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2145{
2146 term_T *term = in_terminal_loop;
2147 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002148 int id;
2149 guicolor_T term_fg, term_bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002150
2151 vim_memset(&entry, 0, sizeof(entry));
2152 entry.shape = entry.mshape =
2153 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2154 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2155 SHAPE_BLOCK;
2156 entry.percentage = 20;
2157 if (term->tl_cursor_blink)
2158 {
2159 entry.blinkwait = 700;
2160 entry.blinkon = 400;
2161 entry.blinkoff = 250;
2162 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002163
2164 /* The "Terminal" highlight group overrules the defaults. */
2165 id = syn_name2id((char_u *)"Terminal");
2166 if (id != 0)
2167 {
2168 syn_id2colors(id, &term_fg, &term_bg);
2169 *fg = term_bg;
2170 }
2171 else
2172 *fg = gui.back_pixel;
2173
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002174 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002175 {
2176 if (id != 0)
2177 *bg = term_fg;
2178 else
2179 *bg = gui.norm_pixel;
2180 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002181 else
2182 *bg = color_name2handle(term->tl_cursor_color);
2183 entry.name = "n";
2184 entry.used_for = SHAPE_CURSOR;
2185
2186 return &entry;
2187}
2188#endif
2189
Bram Moolenaard317b382018-02-08 22:33:31 +01002190 static void
2191may_output_cursor_props(void)
2192{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002193 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
Bram Moolenaard317b382018-02-08 22:33:31 +01002194 || last_set_cursor_shape != desired_cursor_shape
2195 || last_set_cursor_blink != desired_cursor_blink)
2196 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002197 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002198 last_set_cursor_shape = desired_cursor_shape;
2199 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002200 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002201 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
2202 /* this will restore the initial cursor style, if possible */
2203 ui_cursor_shape_forced(TRUE);
2204 else
2205 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2206 }
2207}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002208
Bram Moolenaard317b382018-02-08 22:33:31 +01002209/*
2210 * Set the cursor color and shape, if not last set to these.
2211 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002212 static void
2213may_set_cursor_props(term_T *term)
2214{
2215#ifdef FEAT_GUI
2216 /* For the GUI the cursor properties are obtained with
2217 * term_get_cursor_shape(). */
2218 if (gui.in_use)
2219 return;
2220#endif
2221 if (in_terminal_loop == term)
2222 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002223 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002224 desired_cursor_shape = term->tl_cursor_shape;
2225 desired_cursor_blink = term->tl_cursor_blink;
2226 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002227 }
2228}
2229
Bram Moolenaard317b382018-02-08 22:33:31 +01002230/*
2231 * Reset the desired cursor properties and restore them when needed.
2232 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002233 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002234prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002235{
2236#ifdef FEAT_GUI
2237 if (gui.in_use)
2238 return;
2239#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002240 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002241 desired_cursor_shape = -1;
2242 desired_cursor_blink = -1;
2243 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002244}
2245
2246/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002247 * Returns TRUE if the current window contains a terminal and we are sending
2248 * keys to the job.
2249 * If "check_job_status" is TRUE update the job status.
2250 */
2251 static int
2252term_use_loop_check(int check_job_status)
2253{
2254 term_T *term = curbuf->b_term;
2255
2256 return term != NULL
2257 && !term->tl_normal_mode
2258 && term->tl_vterm != NULL
2259 && term_job_running_check(term, check_job_status);
2260}
2261
2262/*
2263 * Returns TRUE if the current window contains a terminal and we are sending
2264 * keys to the job.
2265 */
2266 int
2267term_use_loop(void)
2268{
2269 return term_use_loop_check(FALSE);
2270}
2271
2272/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002273 * Called when entering a window with the mouse. If this is a terminal window
2274 * we may want to change state.
2275 */
2276 void
2277term_win_entered()
2278{
2279 term_T *term = curbuf->b_term;
2280
2281 if (term != NULL)
2282 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002283 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002284 {
2285 reset_VIsual_and_resel();
2286 if (State & INSERT)
2287 stop_insert_mode = TRUE;
2288 }
2289 mouse_was_outside = FALSE;
2290 enter_mouse_col = mouse_col;
2291 enter_mouse_row = mouse_row;
2292 }
2293}
2294
2295/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002296 * vgetc() may not include CTRL in the key when modify_other_keys is set.
2297 * Return the Ctrl-key value in that case.
2298 */
2299 static int
2300raw_c_to_ctrl(int c)
2301{
2302 if ((mod_mask & MOD_MASK_CTRL)
2303 && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')))
2304 return c & 0x1f;
2305 return c;
2306}
2307
2308/*
2309 * When modify_other_keys is set then do the reverse of raw_c_to_ctrl().
2310 * May set "mod_mask".
2311 */
2312 static int
2313ctrl_to_raw_c(int c)
2314{
2315 if (c < 0x20 && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
2316 {
2317 mod_mask |= MOD_MASK_CTRL;
2318 return c + '@';
2319 }
2320 return c;
2321}
2322
2323/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002324 * Wait for input and send it to the job.
2325 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2326 * when there is no more typahead.
2327 * Return when the start of a CTRL-W command is typed or anything else that
2328 * should be handled as a Normal mode command.
2329 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2330 * the terminal was closed.
2331 */
2332 int
2333terminal_loop(int blocking)
2334{
2335 int c;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002336 int raw_c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002337 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002338 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002339#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002340 int tty_fd = curbuf->b_term->tl_job->jv_channel
2341 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002342#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002343 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002344
2345 /* Remember the terminal we are sending keys to. However, the terminal
2346 * might be closed while waiting for a character, e.g. typing "exit" in a
2347 * shell and ++close was used. Therefore use curbuf->b_term instead of a
2348 * stored reference. */
2349 in_terminal_loop = curbuf->b_term;
2350
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002351 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002352 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002353 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002354 if (termwinkey == Ctrl_W)
2355 termwinkey = 0;
2356 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002357 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2358 may_set_cursor_props(curbuf->b_term);
2359
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002360 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002361 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002362#ifdef FEAT_GUI
2363 if (!curbuf->b_term->tl_system)
2364#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002365 // TODO: skip screen update when handling a sequence of keys.
2366 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002367 while (must_redraw != 0)
2368 if (update_screen(0) == FAIL)
2369 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002370 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002371 /* job finished while redrawing */
2372 break;
2373
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002374 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002375 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002376
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002377 raw_c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002378 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002379 {
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002380 /* Job finished while waiting for a character. Push back the
2381 * received character. */
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002382 if (raw_c != K_IGNORE)
2383 vungetc(raw_c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002384 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002385 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002386 if (raw_c == K_IGNORE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002387 continue;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002388 c = raw_c_to_ctrl(raw_c);
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002389
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002390#ifdef UNIX
2391 /*
2392 * The shell or another program may change the tty settings. Getting
2393 * them for every typed character is a bit of overhead, but it's needed
2394 * for the first character typed, e.g. when Vim starts in a shell.
2395 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002396 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002397 {
2398 ttyinfo_T info;
2399
2400 /* Get the current backspace character of the pty. */
2401 if (get_tty_info(tty_fd, &info) == OK)
2402 term_backspace_char = info.backspace;
2403 }
2404#endif
2405
Bram Moolenaar4f974752019-02-17 17:44:42 +01002406#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002407 /* On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2408 * Use CTRL-BREAK to kill the job. */
2409 if (ctrl_break_was_pressed)
2410 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2411#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002412 /* Was either CTRL-W (termwinkey) or CTRL-\ pressed?
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002413 * Not in a system terminal. */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002414 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002415#ifdef FEAT_GUI
2416 && !curbuf->b_term->tl_system
2417#endif
2418 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002419 {
2420 int prev_c = c;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002421 int prev_raw_c = raw_c;
2422 int prev_mod_mask = mod_mask;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002423
2424#ifdef FEAT_CMDL_INFO
2425 if (add_to_showcmd(c))
2426 out_flush();
2427#endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002428 raw_c = term_vgetc();
2429 c = raw_c_to_ctrl(raw_c);
2430
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002431#ifdef FEAT_CMDL_INFO
2432 clear_showcmd();
2433#endif
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002434 if (!term_use_loop_check(TRUE)
2435 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002436 /* job finished while waiting for a character */
2437 break;
2438
2439 if (prev_c == Ctrl_BSL)
2440 {
2441 if (c == Ctrl_N)
2442 {
2443 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */
2444 term_enter_normal_mode();
2445 ret = FAIL;
2446 goto theend;
2447 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002448 // Send both keys to the terminal, first one here, second one
2449 // below.
2450 send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask,
2451 TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002452 }
2453 else if (c == Ctrl_C)
2454 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002455 /* "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002456 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2457 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002458 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002459 {
2460 /* "CTRL-W .": send CTRL-W to the job */
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002461 /* "'termwinkey' .": send 'termwinkey' to the job */
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002462 raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002463 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002464 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002465 {
2466 /* "CTRL-W CTRL-\": send CTRL-\ to the job */
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002467 raw_c = ctrl_to_raw_c(Ctrl_BSL);
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002468 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002469 else if (c == 'N')
2470 {
2471 /* CTRL-W N : go to Terminal-Normal mode. */
2472 term_enter_normal_mode();
2473 ret = FAIL;
2474 goto theend;
2475 }
2476 else if (c == '"')
2477 {
2478 term_paste_register(prev_c);
2479 continue;
2480 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002481 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002482 {
Bram Moolenaara4b26992019-08-15 20:58:54 +02002483 char_u buf[MB_MAXBYTES + 2];
2484
2485 // Put the command into the typeahead buffer, when using the
2486 // stuff buffer KeyStuffed is set and 'langmap' won't be used.
2487 buf[0] = Ctrl_W;
2488 buf[(*mb_char2bytes)(c, buf + 1) + 1] = NUL;
2489 ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002490 ret = OK;
2491 goto theend;
2492 }
2493 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01002494# ifdef MSWIN
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002495 if (!enc_utf8 && has_mbyte && raw_c >= 0x80)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002496 {
2497 WCHAR wc;
2498 char_u mb[3];
2499
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002500 mb[0] = (unsigned)raw_c >> 8;
2501 mb[1] = raw_c;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002502 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002503 raw_c = wc;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002504 }
2505# endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002506 if (send_keys_to_term(curbuf->b_term, raw_c, mod_mask, TRUE) != OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002507 {
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002508 if (raw_c == K_MOUSEMOVE)
Bram Moolenaard317b382018-02-08 22:33:31 +01002509 /* We are sure to come back here, don't reset the cursor color
2510 * and shape to avoid flickering. */
2511 restore_cursor = FALSE;
2512
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002513 ret = OK;
2514 goto theend;
2515 }
2516 }
2517 ret = FAIL;
2518
2519theend:
2520 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002521 if (restore_cursor)
2522 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002523
2524 /* Move a snapshot of the screen contents to the buffer, so that completion
2525 * works in other buffers. */
Bram Moolenaar620020e2018-05-13 19:06:12 +02002526 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2527 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002528
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002529 return ret;
2530}
2531
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002532 static void
2533may_toggle_cursor(term_T *term)
2534{
2535 if (in_terminal_loop == term)
2536 {
2537 if (term->tl_cursor_visible)
2538 cursor_on();
2539 else
2540 cursor_off();
2541 }
2542}
2543
2544/*
2545 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002546 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002547 */
2548 static int
2549color2index(VTermColor *color, int fg, int *boldp)
2550{
2551 int red = color->red;
2552 int blue = color->blue;
2553 int green = color->green;
2554
Bram Moolenaar46359e12017-11-29 22:33:38 +01002555 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002556 {
Bram Moolenaar1d79ce82019-04-12 22:27:39 +02002557 // The first 16 colors and default: use the ANSI index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002558 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002559 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002560 case 0: return 0;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01002561 case 1: return lookup_color( 0, fg, boldp) + 1; /* black */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002562 case 2: return lookup_color( 4, fg, boldp) + 1; /* dark red */
2563 case 3: return lookup_color( 2, fg, boldp) + 1; /* dark green */
2564 case 4: return lookup_color( 6, fg, boldp) + 1; /* brown */
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002565 case 5: return lookup_color( 1, fg, boldp) + 1; /* dark blue */
Bram Moolenaar46359e12017-11-29 22:33:38 +01002566 case 6: return lookup_color( 5, fg, boldp) + 1; /* dark magenta */
2567 case 7: return lookup_color( 3, fg, boldp) + 1; /* dark cyan */
2568 case 8: return lookup_color( 8, fg, boldp) + 1; /* light grey */
2569 case 9: return lookup_color(12, fg, boldp) + 1; /* dark grey */
2570 case 10: return lookup_color(20, fg, boldp) + 1; /* red */
2571 case 11: return lookup_color(16, fg, boldp) + 1; /* green */
2572 case 12: return lookup_color(24, fg, boldp) + 1; /* yellow */
2573 case 13: return lookup_color(14, fg, boldp) + 1; /* blue */
2574 case 14: return lookup_color(22, fg, boldp) + 1; /* magenta */
2575 case 15: return lookup_color(18, fg, boldp) + 1; /* cyan */
2576 case 16: return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002577 }
2578 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002579
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002580 if (t_colors >= 256)
2581 {
2582 if (red == blue && red == green)
2583 {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002584 /* 24-color greyscale plus white and black */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002585 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002586 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2587 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2588 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002589 int i;
2590
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002591 if (red < 5)
2592 return 17; /* 00/00/00 */
2593 if (red > 245) /* ff/ff/ff */
2594 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002595 for (i = 0; i < 23; ++i)
2596 if (red < cutoff[i])
2597 return i + 233;
2598 return 256;
2599 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002600 {
2601 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2602 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002603
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002604 /* 216-color cube */
2605 for (ri = 0; ri < 5; ++ri)
2606 if (red < cutoff[ri])
2607 break;
2608 for (gi = 0; gi < 5; ++gi)
2609 if (green < cutoff[gi])
2610 break;
2611 for (bi = 0; bi < 5; ++bi)
2612 if (blue < cutoff[bi])
2613 break;
2614 return 17 + ri * 36 + gi * 6 + bi;
2615 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002616 }
2617 return 0;
2618}
2619
2620/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002621 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002622 */
2623 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002624vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002625{
2626 int attr = 0;
2627
2628 if (cellattrs.bold)
2629 attr |= HL_BOLD;
2630 if (cellattrs.underline)
2631 attr |= HL_UNDERLINE;
2632 if (cellattrs.italic)
2633 attr |= HL_ITALIC;
2634 if (cellattrs.strike)
2635 attr |= HL_STRIKETHROUGH;
2636 if (cellattrs.reverse)
2637 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002638 return attr;
2639}
2640
2641/*
2642 * Store Vterm attributes in "cell" from highlight flags.
2643 */
2644 static void
2645hl2vtermAttr(int attr, cellattr_T *cell)
2646{
2647 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2648 if (attr & HL_BOLD)
2649 cell->attrs.bold = 1;
2650 if (attr & HL_UNDERLINE)
2651 cell->attrs.underline = 1;
2652 if (attr & HL_ITALIC)
2653 cell->attrs.italic = 1;
2654 if (attr & HL_STRIKETHROUGH)
2655 cell->attrs.strike = 1;
2656 if (attr & HL_INVERSE)
2657 cell->attrs.reverse = 1;
2658}
2659
2660/*
2661 * Convert the attributes of a vterm cell into an attribute index.
2662 */
2663 static int
2664cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2665{
2666 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002667
2668#ifdef FEAT_GUI
2669 if (gui.in_use)
2670 {
2671 guicolor_T fg, bg;
2672
2673 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2674 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2675 return get_gui_attr_idx(attr, fg, bg);
2676 }
2677 else
2678#endif
2679#ifdef FEAT_TERMGUICOLORS
2680 if (p_tgc)
2681 {
2682 guicolor_T fg, bg;
2683
2684 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2685 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2686
2687 return get_tgc_attr_idx(attr, fg, bg);
2688 }
2689 else
2690#endif
2691 {
2692 int bold = MAYBE;
2693 int fg = color2index(&cellfg, TRUE, &bold);
2694 int bg = color2index(&cellbg, FALSE, &bold);
2695
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002696 /* Use the "Terminal" highlighting for the default colors. */
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002697 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002698 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002699 if (fg == 0 && term_default_cterm_fg >= 0)
2700 fg = term_default_cterm_fg + 1;
2701 if (bg == 0 && term_default_cterm_bg >= 0)
2702 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002703 }
2704
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002705 /* with 8 colors set the bold attribute to get a bright foreground */
2706 if (bold == TRUE)
2707 attr |= HL_BOLD;
2708 return get_cterm_attr_idx(attr, fg, bg);
2709 }
2710 return 0;
2711}
2712
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002713 static void
2714set_dirty_snapshot(term_T *term)
2715{
2716 term->tl_dirty_snapshot = TRUE;
2717#ifdef FEAT_TIMERS
2718 if (!term->tl_normal_mode)
2719 {
2720 /* Update the snapshot after 100 msec of not getting updates. */
2721 profile_setlimit(100L, &term->tl_timer_due);
2722 term->tl_timer_set = TRUE;
2723 }
2724#endif
2725}
2726
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002727 static int
2728handle_damage(VTermRect rect, void *user)
2729{
2730 term_T *term = (term_T *)user;
2731
2732 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2733 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002734 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002735 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002736 return 1;
2737}
2738
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002739 static void
2740term_scroll_up(term_T *term, int start_row, int count)
2741{
2742 win_T *wp;
2743 VTermColor fg, bg;
2744 VTermScreenCellAttrs attr;
2745 int clear_attr;
2746
2747 /* Set the color to clear lines with. */
2748 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2749 &fg, &bg);
2750 vim_memset(&attr, 0, sizeof(attr));
2751 clear_attr = cell2attr(attr, fg, bg);
2752
2753 FOR_ALL_WINDOWS(wp)
2754 {
2755 if (wp->w_buffer == term->tl_buffer)
2756 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2757 }
2758}
2759
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002760 static int
2761handle_moverect(VTermRect dest, VTermRect src, void *user)
2762{
2763 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002764 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002765
2766 /* Scrolling up is done much more efficiently by deleting lines instead of
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002767 * redrawing the text. But avoid doing this multiple times, postpone until
2768 * the redraw happens. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002769 if (dest.start_col == src.start_col
2770 && dest.end_col == src.end_col
2771 && dest.start_row < src.start_row)
2772 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002773 if (dest.start_row == 0)
2774 term->tl_postponed_scroll += count;
2775 else
2776 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002777 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002778
2779 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2780 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002781 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002782
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002783 /* Note sure if the scrolling will work correctly, let's do a complete
2784 * redraw later. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002785 redraw_buf_later(term->tl_buffer, NOT_VALID);
2786 return 1;
2787}
2788
2789 static int
2790handle_movecursor(
2791 VTermPos pos,
2792 VTermPos oldpos UNUSED,
2793 int visible,
2794 void *user)
2795{
2796 term_T *term = (term_T *)user;
2797 win_T *wp;
2798
2799 term->tl_cursor_pos = pos;
2800 term->tl_cursor_visible = visible;
2801
2802 FOR_ALL_WINDOWS(wp)
2803 {
2804 if (wp->w_buffer == term->tl_buffer)
2805 position_cursor(wp, &pos);
2806 }
2807 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2808 {
2809 may_toggle_cursor(term);
2810 update_cursor(term, term->tl_cursor_visible);
2811 }
2812
2813 return 1;
2814}
2815
2816 static int
2817handle_settermprop(
2818 VTermProp prop,
2819 VTermValue *value,
2820 void *user)
2821{
2822 term_T *term = (term_T *)user;
2823
2824 switch (prop)
2825 {
2826 case VTERM_PROP_TITLE:
2827 vim_free(term->tl_title);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002828 // a blank title isn't useful, make it empty, so that "running" is
2829 // displayed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002830 if (*skipwhite((char_u *)value->string) == NUL)
2831 term->tl_title = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002832 // Same as blank
2833 else if (term->tl_arg0_cmd != NULL
2834 && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string,
2835 (int)STRLEN(term->tl_arg0_cmd)) == 0)
2836 term->tl_title = NULL;
2837 // Empty corrupted data of winpty
2838 else if (STRNCMP(" - ", (char_u *)value->string, 4) == 0)
2839 term->tl_title = NULL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01002840#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002841 else if (!enc_utf8 && enc_codepage > 0)
2842 {
2843 WCHAR *ret = NULL;
2844 int length = 0;
2845
2846 MultiByteToWideChar_alloc(CP_UTF8, 0,
2847 (char*)value->string, (int)STRLEN(value->string),
2848 &ret, &length);
2849 if (ret != NULL)
2850 {
2851 WideCharToMultiByte_alloc(enc_codepage, 0,
2852 ret, length, (char**)&term->tl_title,
2853 &length, 0, 0);
2854 vim_free(ret);
2855 }
2856 }
2857#endif
2858 else
2859 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002860 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002861 if (term == curbuf->b_term)
2862 maketitle();
2863 break;
2864
2865 case VTERM_PROP_CURSORVISIBLE:
2866 term->tl_cursor_visible = value->boolean;
2867 may_toggle_cursor(term);
2868 out_flush();
2869 break;
2870
2871 case VTERM_PROP_CURSORBLINK:
2872 term->tl_cursor_blink = value->boolean;
2873 may_set_cursor_props(term);
2874 break;
2875
2876 case VTERM_PROP_CURSORSHAPE:
2877 term->tl_cursor_shape = value->number;
2878 may_set_cursor_props(term);
2879 break;
2880
2881 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002882 cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002883 may_set_cursor_props(term);
2884 break;
2885
2886 case VTERM_PROP_ALTSCREEN:
2887 /* TODO: do anything else? */
2888 term->tl_using_altscreen = value->boolean;
2889 break;
2890
2891 default:
2892 break;
2893 }
2894 /* Always return 1, otherwise vterm doesn't store the value internally. */
2895 return 1;
2896}
2897
2898/*
2899 * The job running in the terminal resized the terminal.
2900 */
2901 static int
2902handle_resize(int rows, int cols, void *user)
2903{
2904 term_T *term = (term_T *)user;
2905 win_T *wp;
2906
2907 term->tl_rows = rows;
2908 term->tl_cols = cols;
2909 if (term->tl_vterm_size_changed)
2910 /* Size was set by vterm_set_size(), don't set the window size. */
2911 term->tl_vterm_size_changed = FALSE;
2912 else
2913 {
2914 FOR_ALL_WINDOWS(wp)
2915 {
2916 if (wp->w_buffer == term->tl_buffer)
2917 {
2918 win_setheight_win(rows, wp);
2919 win_setwidth_win(cols, wp);
2920 }
2921 }
2922 redraw_buf_later(term->tl_buffer, NOT_VALID);
2923 }
2924 return 1;
2925}
2926
2927/*
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002928 * If the number of lines that are stored goes over 'termscrollback' then
2929 * delete the first 10%.
2930 * "gap" points to tl_scrollback or tl_scrollback_postponed.
2931 * "update_buffer" is TRUE when the buffer should be updated.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002932 */
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002933 static void
2934limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002935{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002936 if (gap->ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002937 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002938 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002939 int i;
2940
2941 curbuf = term->tl_buffer;
2942 for (i = 0; i < todo; ++i)
2943 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002944 vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells);
2945 if (update_buffer)
2946 ml_delete(1, FALSE);
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002947 }
2948 curbuf = curwin->w_buffer;
2949
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002950 gap->ga_len -= todo;
2951 mch_memmove(gap->ga_data,
2952 (sb_line_T *)gap->ga_data + todo,
2953 sizeof(sb_line_T) * gap->ga_len);
2954 if (update_buffer)
2955 term->tl_scrollback_scrolled -= todo;
2956 }
2957}
2958
2959/*
2960 * Handle a line that is pushed off the top of the screen.
2961 */
2962 static int
2963handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2964{
2965 term_T *term = (term_T *)user;
2966 garray_T *gap;
2967 int update_buffer;
2968
2969 if (term->tl_normal_mode)
2970 {
2971 // In Terminal-Normal mode the user interacts with the buffer, thus we
2972 // must not change it. Postpone adding the scrollback lines.
2973 gap = &term->tl_scrollback_postponed;
2974 update_buffer = FALSE;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002975 }
2976 else
2977 {
2978 // First remove the lines that were appended before, the pushed line
2979 // goes above it.
2980 cleanup_scrollback(term);
2981 gap = &term->tl_scrollback;
2982 update_buffer = TRUE;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002983 }
2984
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002985 limit_scrollback(term, gap, update_buffer);
2986
2987 if (ga_grow(gap, 1) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002988 {
2989 cellattr_T *p = NULL;
2990 int len = 0;
2991 int i;
2992 int c;
2993 int col;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002994 int text_len;
2995 char_u *text;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002996 sb_line_T *line;
2997 garray_T ga;
2998 cellattr_T fill_attr = term->tl_default_color;
2999
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003000 // do not store empty cells at the end
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003001 for (i = 0; i < cols; ++i)
3002 if (cells[i].chars[0] != 0)
3003 len = i + 1;
3004 else
3005 cell2cellattr(&cells[i], &fill_attr);
3006
3007 ga_init2(&ga, 1, 100);
3008 if (len > 0)
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003009 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003010 if (p != NULL)
3011 {
3012 for (col = 0; col < len; col += cells[col].width)
3013 {
3014 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
3015 {
3016 ga.ga_len = 0;
3017 break;
3018 }
3019 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
3020 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
3021 (char_u *)ga.ga_data + ga.ga_len);
3022 cell2cellattr(&cells[col], &p[col]);
3023 }
3024 }
3025 if (ga_grow(&ga, 1) == FAIL)
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003026 {
3027 if (update_buffer)
3028 text = (char_u *)"";
3029 else
3030 text = vim_strsave((char_u *)"");
3031 text_len = 0;
3032 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003033 else
3034 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003035 text = ga.ga_data;
3036 text_len = ga.ga_len;
3037 *(text + text_len) = NUL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003038 }
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003039 if (update_buffer)
3040 add_scrollback_line_to_buffer(term, text, text_len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003041
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003042 line = (sb_line_T *)gap->ga_data + gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003043 line->sb_cols = len;
3044 line->sb_cells = p;
3045 line->sb_fill_attr = fill_attr;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003046 if (update_buffer)
3047 {
3048 line->sb_text = NULL;
3049 ++term->tl_scrollback_scrolled;
3050 ga_clear(&ga); // free the text
3051 }
3052 else
3053 {
3054 line->sb_text = text;
3055 ga_init(&ga); // text is kept in tl_scrollback_postponed
3056 }
3057 ++gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003058 }
3059 return 0; /* ignored */
3060}
3061
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003062/*
3063 * Called when leaving Terminal-Normal mode: deal with any scrollback that was
3064 * received and stored in tl_scrollback_postponed.
3065 */
3066 static void
3067handle_postponed_scrollback(term_T *term)
3068{
3069 int i;
3070
Bram Moolenaar8376c3d2019-03-19 20:50:43 +01003071 if (term->tl_scrollback_postponed.ga_len == 0)
3072 return;
3073 ch_log(NULL, "Moving postponed scrollback to scrollback");
3074
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003075 // First remove the lines that were appended before, the pushed lines go
3076 // above it.
3077 cleanup_scrollback(term);
3078
3079 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
3080 {
3081 char_u *text;
3082 sb_line_T *pp_line;
3083 sb_line_T *line;
3084
3085 if (ga_grow(&term->tl_scrollback, 1) == FAIL)
3086 break;
3087 pp_line = (sb_line_T *)term->tl_scrollback_postponed.ga_data + i;
3088
3089 text = pp_line->sb_text;
3090 if (text == NULL)
3091 text = (char_u *)"";
3092 add_scrollback_line_to_buffer(term, text, (int)STRLEN(text));
3093 vim_free(pp_line->sb_text);
3094
3095 line = (sb_line_T *)term->tl_scrollback.ga_data
3096 + term->tl_scrollback.ga_len;
3097 line->sb_cols = pp_line->sb_cols;
3098 line->sb_cells = pp_line->sb_cells;
3099 line->sb_fill_attr = pp_line->sb_fill_attr;
3100 line->sb_text = NULL;
3101 ++term->tl_scrollback_scrolled;
3102 ++term->tl_scrollback.ga_len;
3103 }
3104
3105 ga_clear(&term->tl_scrollback_postponed);
3106 limit_scrollback(term, &term->tl_scrollback, TRUE);
3107}
3108
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003109static VTermScreenCallbacks screen_callbacks = {
3110 handle_damage, /* damage */
3111 handle_moverect, /* moverect */
3112 handle_movecursor, /* movecursor */
3113 handle_settermprop, /* settermprop */
3114 NULL, /* bell */
3115 handle_resize, /* resize */
3116 handle_pushline, /* sb_pushline */
3117 NULL /* sb_popline */
3118};
3119
3120/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003121 * Do the work after the channel of a terminal was closed.
3122 * Must be called only when updating_screen is FALSE.
3123 * Returns TRUE when a buffer was closed (list of terminals may have changed).
3124 */
3125 static int
3126term_after_channel_closed(term_T *term)
3127{
3128 /* Unless in Terminal-Normal mode: clear the vterm. */
3129 if (!term->tl_normal_mode)
3130 {
3131 int fnum = term->tl_buffer->b_fnum;
3132
3133 cleanup_vterm(term);
3134
3135 if (term->tl_finish == TL_FINISH_CLOSE)
3136 {
3137 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003138 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003139
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003140 // If this is the last normal window: exit Vim.
3141 if (term->tl_buffer->b_nwindows > 0 && only_one_window())
3142 {
3143 exarg_T ea;
3144
3145 vim_memset(&ea, 0, sizeof(ea));
3146 ex_quit(&ea);
3147 return TRUE;
3148 }
3149
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003150 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003151 ch_log(NULL, "terminal job finished, closing window");
3152 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003153 // Avoid closing the window if we temporarily use it.
Bram Moolenaar517f71a2019-06-17 22:40:41 +02003154 if (curwin == aucmd_win)
3155 do_set_w_closing = TRUE;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003156 if (do_set_w_closing)
3157 curwin->w_closing = TRUE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003158 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003159 if (do_set_w_closing)
3160 curwin->w_closing = FALSE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003161 aucmd_restbuf(&aco);
3162 return TRUE;
3163 }
3164 if (term->tl_finish == TL_FINISH_OPEN
3165 && term->tl_buffer->b_nwindows == 0)
3166 {
3167 char buf[50];
3168
3169 /* TODO: use term_opencmd */
3170 ch_log(NULL, "terminal job finished, opening window");
3171 vim_snprintf(buf, sizeof(buf),
3172 term->tl_opencmd == NULL
3173 ? "botright sbuf %d"
3174 : (char *)term->tl_opencmd, fnum);
3175 do_cmdline_cmd((char_u *)buf);
3176 }
3177 else
3178 ch_log(NULL, "terminal job finished");
3179 }
3180
3181 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
3182 return FALSE;
3183}
3184
3185/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003186 * Called when a channel has been closed.
3187 * If this was a channel for a terminal window then finish it up.
3188 */
3189 void
3190term_channel_closed(channel_T *ch)
3191{
3192 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003193 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003194 int did_one = FALSE;
3195
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003196 for (term = first_term; term != NULL; term = next_term)
3197 {
3198 next_term = term->tl_next;
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02003199 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003200 {
3201 term->tl_channel_closed = TRUE;
3202 did_one = TRUE;
3203
Bram Moolenaard23a8232018-02-10 18:45:26 +01003204 VIM_CLEAR(term->tl_title);
3205 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar4f974752019-02-17 17:44:42 +01003206#ifdef MSWIN
Bram Moolenaar402c8392018-05-06 22:01:42 +02003207 if (term->tl_out_fd != NULL)
3208 {
3209 fclose(term->tl_out_fd);
3210 term->tl_out_fd = NULL;
3211 }
3212#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003213
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003214 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003215 {
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003216 /* Cannot open or close windows now. Can happen when
3217 * 'lazyredraw' is set. */
3218 term->tl_channel_recently_closed = TRUE;
3219 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003220 }
3221
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003222 if (term_after_channel_closed(term))
3223 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003224 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003225 }
3226
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003227 if (did_one)
3228 {
3229 redraw_statuslines();
3230
3231 /* Need to break out of vgetc(). */
3232 ins_char_typebuf(K_IGNORE);
3233 typebuf_was_filled = TRUE;
3234
3235 term = curbuf->b_term;
3236 if (term != NULL)
3237 {
3238 if (term->tl_job == ch->ch_job)
3239 maketitle();
3240 update_cursor(term, term->tl_cursor_visible);
3241 }
3242 }
3243}
3244
3245/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003246 * To be called after resetting updating_screen: handle any terminal where the
3247 * channel was closed.
3248 */
3249 void
3250term_check_channel_closed_recently()
3251{
3252 term_T *term;
3253 term_T *next_term;
3254
3255 for (term = first_term; term != NULL; term = next_term)
3256 {
3257 next_term = term->tl_next;
3258 if (term->tl_channel_recently_closed)
3259 {
3260 term->tl_channel_recently_closed = FALSE;
3261 if (term_after_channel_closed(term))
3262 // start over, the list may have changed
3263 next_term = first_term;
3264 }
3265 }
3266}
3267
3268/*
Bram Moolenaar13568252018-03-16 20:46:58 +01003269 * Fill one screen line from a line of the terminal.
3270 * Advances "pos" to past the last column.
3271 */
3272 static void
3273term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
3274{
3275 int off = screen_get_current_line_off();
3276
3277 for (pos->col = 0; pos->col < max_col; )
3278 {
3279 VTermScreenCell cell;
3280 int c;
3281
3282 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
3283 vim_memset(&cell, 0, sizeof(cell));
3284
3285 c = cell.chars[0];
3286 if (c == NUL)
3287 {
3288 ScreenLines[off] = ' ';
3289 if (enc_utf8)
3290 ScreenLinesUC[off] = NUL;
3291 }
3292 else
3293 {
3294 if (enc_utf8)
3295 {
3296 int i;
3297
3298 /* composing chars */
3299 for (i = 0; i < Screen_mco
3300 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3301 {
3302 ScreenLinesC[i][off] = cell.chars[i + 1];
3303 if (cell.chars[i + 1] == 0)
3304 break;
3305 }
3306 if (c >= 0x80 || (Screen_mco > 0
3307 && ScreenLinesC[0][off] != 0))
3308 {
3309 ScreenLines[off] = ' ';
3310 ScreenLinesUC[off] = c;
3311 }
3312 else
3313 {
3314 ScreenLines[off] = c;
3315 ScreenLinesUC[off] = NUL;
3316 }
3317 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01003318#ifdef MSWIN
Bram Moolenaar13568252018-03-16 20:46:58 +01003319 else if (has_mbyte && c >= 0x80)
3320 {
3321 char_u mb[MB_MAXBYTES+1];
3322 WCHAR wc = c;
3323
3324 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3325 (char*)mb, 2, 0, 0) > 1)
3326 {
3327 ScreenLines[off] = mb[0];
3328 ScreenLines[off + 1] = mb[1];
3329 cell.width = mb_ptr2cells(mb);
3330 }
3331 else
3332 ScreenLines[off] = c;
3333 }
3334#endif
3335 else
3336 ScreenLines[off] = c;
3337 }
3338 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
3339
3340 ++pos->col;
3341 ++off;
3342 if (cell.width == 2)
3343 {
3344 if (enc_utf8)
3345 ScreenLinesUC[off] = NUL;
3346
3347 /* don't set the second byte to NUL for a DBCS encoding, it
3348 * has been set above */
3349 if (enc_utf8 || !has_mbyte)
3350 ScreenLines[off] = NUL;
3351
3352 ++pos->col;
3353 ++off;
3354 }
3355 }
3356}
3357
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003358#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003359 static void
3360update_system_term(term_T *term)
3361{
3362 VTermPos pos;
3363 VTermScreen *screen;
3364
3365 if (term->tl_vterm == NULL)
3366 return;
3367 screen = vterm_obtain_screen(term->tl_vterm);
3368
3369 /* Scroll up to make more room for terminal lines if needed. */
3370 while (term->tl_toprow > 0
3371 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3372 {
3373 int save_p_more = p_more;
3374
3375 p_more = FALSE;
3376 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003377 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003378 p_more = save_p_more;
3379 --term->tl_toprow;
3380 }
3381
3382 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3383 && pos.row < Rows; ++pos.row)
3384 {
3385 if (pos.row < term->tl_rows)
3386 {
3387 int max_col = MIN(Columns, term->tl_cols);
3388
3389 term_line2screenline(screen, &pos, max_col);
3390 }
3391 else
3392 pos.col = 0;
3393
Bram Moolenaar4d784b22019-05-25 19:51:39 +02003394 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, 0);
Bram Moolenaar13568252018-03-16 20:46:58 +01003395 }
3396
3397 term->tl_dirty_row_start = MAX_ROW;
3398 term->tl_dirty_row_end = 0;
Bram Moolenaar13568252018-03-16 20:46:58 +01003399}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003400#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003401
3402/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003403 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3404 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003405 * Terminal-Normal mode.
3406 */
3407 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003408term_do_update_window(win_T *wp)
3409{
3410 term_T *term = wp->w_buffer->b_term;
3411
3412 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3413}
3414
3415/*
3416 * Called to update a window that contains an active terminal.
3417 */
3418 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003419term_update_window(win_T *wp)
3420{
3421 term_T *term = wp->w_buffer->b_term;
3422 VTerm *vterm;
3423 VTermScreen *screen;
3424 VTermState *state;
3425 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003426 int rows, cols;
3427 int newrows, newcols;
3428 int minsize;
3429 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003430
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003431 vterm = term->tl_vterm;
3432 screen = vterm_obtain_screen(vterm);
3433 state = vterm_obtain_state(vterm);
3434
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003435 /* We use NOT_VALID on a resize or scroll, redraw everything then. With
3436 * SOME_VALID only redraw what was marked dirty. */
3437 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003438 {
3439 term->tl_dirty_row_start = 0;
3440 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003441
3442 if (term->tl_postponed_scroll > 0
3443 && term->tl_postponed_scroll < term->tl_rows / 3)
3444 /* Scrolling is usually faster than redrawing, when there are only
3445 * a few lines to scroll. */
3446 term_scroll_up(term, 0, term->tl_postponed_scroll);
3447 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003448 }
3449
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003450 /*
3451 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003452 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003453 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003454 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003455
Bram Moolenaar498c2562018-04-15 23:45:15 +02003456 newrows = 99999;
3457 newcols = 99999;
3458 FOR_ALL_WINDOWS(twp)
3459 {
3460 /* When more than one window shows the same terminal, use the
3461 * smallest size. */
3462 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003463 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02003464 newrows = MIN(newrows, twp->w_height);
3465 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003466 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02003467 }
Bram Moolenaare0d749a2019-09-25 22:14:48 +02003468 if (newrows == 99999 || newcols == 99999)
3469 return; // safety exit
Bram Moolenaar498c2562018-04-15 23:45:15 +02003470 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3471 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3472
3473 if (term->tl_rows != newrows || term->tl_cols != newcols)
3474 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003475 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003476 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003477 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003478 newrows);
3479 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003480
3481 // Updating the terminal size will cause the snapshot to be cleared.
3482 // When not in terminal_loop() we need to restore it.
3483 if (term != in_terminal_loop)
3484 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003485 }
3486
3487 /* The cursor may have been moved when resizing. */
3488 vterm_state_get_cursorpos(state, &pos);
3489 position_cursor(wp, &pos);
3490
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003491 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3492 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003493 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003494 if (pos.row < term->tl_rows)
3495 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003496 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003497
Bram Moolenaar13568252018-03-16 20:46:58 +01003498 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003499 }
3500 else
3501 pos.col = 0;
3502
Bram Moolenaarf118d482018-03-13 13:14:00 +01003503 screen_line(wp->w_winrow + pos.row
3504#ifdef FEAT_MENU
3505 + winbar_height(wp)
3506#endif
Bram Moolenaar4d784b22019-05-25 19:51:39 +02003507 , wp->w_wincol, pos.col, wp->w_width, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003508 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003509 term->tl_dirty_row_start = MAX_ROW;
3510 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003511}
3512
3513/*
3514 * Return TRUE if "wp" is a terminal window where the job has finished.
3515 */
3516 int
3517term_is_finished(buf_T *buf)
3518{
3519 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3520}
3521
3522/*
3523 * Return TRUE if "wp" is a terminal window where the job has finished or we
3524 * are in Terminal-Normal mode, thus we show the buffer contents.
3525 */
3526 int
3527term_show_buffer(buf_T *buf)
3528{
3529 term_T *term = buf->b_term;
3530
3531 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3532}
3533
3534/*
3535 * The current buffer is going to be changed. If there is terminal
3536 * highlighting remove it now.
3537 */
3538 void
3539term_change_in_curbuf(void)
3540{
3541 term_T *term = curbuf->b_term;
3542
3543 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3544 {
3545 free_scrollback(term);
3546 redraw_buf_later(term->tl_buffer, NOT_VALID);
3547
3548 /* The buffer is now like a normal buffer, it cannot be easily
3549 * abandoned when changed. */
3550 set_string_option_direct((char_u *)"buftype", -1,
3551 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3552 }
3553}
3554
3555/*
3556 * Get the screen attribute for a position in the buffer.
3557 * Use a negative "col" to get the filler background color.
3558 */
3559 int
3560term_get_attr(buf_T *buf, linenr_T lnum, int col)
3561{
3562 term_T *term = buf->b_term;
3563 sb_line_T *line;
3564 cellattr_T *cellattr;
3565
3566 if (lnum > term->tl_scrollback.ga_len)
3567 cellattr = &term->tl_default_color;
3568 else
3569 {
3570 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3571 if (col < 0 || col >= line->sb_cols)
3572 cellattr = &line->sb_fill_attr;
3573 else
3574 cellattr = line->sb_cells + col;
3575 }
3576 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3577}
3578
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003579/*
3580 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003581 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003582 */
3583 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003584cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003585{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003586 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003587}
3588
3589/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003590 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003591 */
3592 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003593init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003594{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003595 VTermColor *fg, *bg;
3596 int fgval, bgval;
3597 int id;
3598
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003599 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3600 term->tl_default_color.width = 1;
3601 fg = &term->tl_default_color.fg;
3602 bg = &term->tl_default_color.bg;
3603
3604 /* Vterm uses a default black background. Set it to white when
3605 * 'background' is "light". */
3606 if (*p_bg == 'l')
3607 {
3608 fgval = 0;
3609 bgval = 255;
3610 }
3611 else
3612 {
3613 fgval = 255;
3614 bgval = 0;
3615 }
3616 fg->red = fg->green = fg->blue = fgval;
3617 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003618 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003619
3620 /* The "Terminal" highlight group overrules the defaults. */
3621 id = syn_name2id((char_u *)"Terminal");
3622
Bram Moolenaar46359e12017-11-29 22:33:38 +01003623 /* Use the actual color for the GUI and when 'termguicolors' is set. */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003624#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3625 if (0
3626# ifdef FEAT_GUI
3627 || gui.in_use
3628# endif
3629# ifdef FEAT_TERMGUICOLORS
3630 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003631# ifdef FEAT_VTP
3632 /* Finally get INVALCOLOR on this execution path */
3633 || (!p_tgc && t_colors >= 256)
3634# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003635# endif
3636 )
3637 {
3638 guicolor_T fg_rgb = INVALCOLOR;
3639 guicolor_T bg_rgb = INVALCOLOR;
3640
3641 if (id != 0)
3642 syn_id2colors(id, &fg_rgb, &bg_rgb);
3643
3644# ifdef FEAT_GUI
3645 if (gui.in_use)
3646 {
3647 if (fg_rgb == INVALCOLOR)
3648 fg_rgb = gui.norm_pixel;
3649 if (bg_rgb == INVALCOLOR)
3650 bg_rgb = gui.back_pixel;
3651 }
3652# ifdef FEAT_TERMGUICOLORS
3653 else
3654# endif
3655# endif
3656# ifdef FEAT_TERMGUICOLORS
3657 {
3658 if (fg_rgb == INVALCOLOR)
3659 fg_rgb = cterm_normal_fg_gui_color;
3660 if (bg_rgb == INVALCOLOR)
3661 bg_rgb = cterm_normal_bg_gui_color;
3662 }
3663# endif
3664 if (fg_rgb != INVALCOLOR)
3665 {
3666 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3667
3668 fg->red = (unsigned)(rgb >> 16);
3669 fg->green = (unsigned)(rgb >> 8) & 255;
3670 fg->blue = (unsigned)rgb & 255;
3671 }
3672 if (bg_rgb != INVALCOLOR)
3673 {
3674 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3675
3676 bg->red = (unsigned)(rgb >> 16);
3677 bg->green = (unsigned)(rgb >> 8) & 255;
3678 bg->blue = (unsigned)rgb & 255;
3679 }
3680 }
3681 else
3682#endif
3683 if (id != 0 && t_colors >= 16)
3684 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003685 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003686 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003687 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003688 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003689 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003690 else
3691 {
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003692#if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003693 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003694#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003695
3696 /* In an MS-Windows console we know the normal colors. */
3697 if (cterm_normal_fg_color > 0)
3698 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003699 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003700# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3701# ifdef VIMDLL
3702 if (!gui.in_use)
3703# endif
3704 {
3705 tmp = fg->red;
3706 fg->red = fg->blue;
3707 fg->blue = tmp;
3708 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003709# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003710 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003711# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003712 else
3713 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003714# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003715
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003716 if (cterm_normal_bg_color > 0)
3717 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003718 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003719# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3720# ifdef VIMDLL
3721 if (!gui.in_use)
3722# endif
3723 {
3724 tmp = fg->red;
3725 fg->red = fg->blue;
3726 fg->blue = tmp;
3727 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003728# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003729 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003730# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003731 else
3732 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003733# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003734 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003735}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003736
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003737#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3738/*
3739 * Set the 16 ANSI colors from array of RGB values
3740 */
3741 static void
3742set_vterm_palette(VTerm *vterm, long_u *rgb)
3743{
3744 int index = 0;
3745 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003746
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003747 for (; index < 16; index++)
3748 {
3749 VTermColor color;
Bram Moolenaaref8c83c2019-04-11 11:40:13 +02003750
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003751 color.red = (unsigned)(rgb[index] >> 16);
3752 color.green = (unsigned)(rgb[index] >> 8) & 255;
3753 color.blue = (unsigned)rgb[index] & 255;
3754 vterm_state_set_palette_color(state, index, &color);
3755 }
3756}
3757
3758/*
3759 * Set the ANSI color palette from a list of colors
3760 */
3761 static int
3762set_ansi_colors_list(VTerm *vterm, list_T *list)
3763{
3764 int n = 0;
3765 long_u rgb[16];
3766 listitem_T *li = list->lv_first;
3767
3768 for (; li != NULL && n < 16; li = li->li_next, n++)
3769 {
3770 char_u *color_name;
3771 guicolor_T guicolor;
3772
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003773 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003774 if (color_name == NULL)
3775 return FAIL;
3776
3777 guicolor = GUI_GET_COLOR(color_name);
3778 if (guicolor == INVALCOLOR)
3779 return FAIL;
3780
3781 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3782 }
3783
3784 if (n != 16 || li != NULL)
3785 return FAIL;
3786
3787 set_vterm_palette(vterm, rgb);
3788
3789 return OK;
3790}
3791
3792/*
3793 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3794 */
3795 static void
3796init_vterm_ansi_colors(VTerm *vterm)
3797{
3798 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3799
3800 if (var != NULL
3801 && (var->di_tv.v_type != VAR_LIST
3802 || var->di_tv.vval.v_list == NULL
3803 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003804 semsg(_(e_invarg2), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003805}
3806#endif
3807
Bram Moolenaar52acb112018-03-18 19:20:22 +01003808/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003809 * Handles a "drop" command from the job in the terminal.
3810 * "item" is the file name, "item->li_next" may have options.
3811 */
3812 static void
3813handle_drop_command(listitem_T *item)
3814{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003815 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003816 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003817 int bufnr;
3818 win_T *wp;
3819 tabpage_T *tp;
3820 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003821 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003822
3823 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3824 FOR_ALL_TAB_WINDOWS(tp, wp)
3825 {
3826 if (wp->w_buffer->b_fnum == bufnr)
3827 {
3828 /* buffer is in a window already, go there */
3829 goto_tabpage_win(tp, wp);
3830 return;
3831 }
3832 }
3833
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003834 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003835
3836 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3837 && opt_item->li_tv.vval.v_dict != NULL)
3838 {
3839 dict_T *dict = opt_item->li_tv.vval.v_dict;
3840 char_u *p;
3841
Bram Moolenaar8f667172018-12-14 15:38:31 +01003842 p = dict_get_string(dict, (char_u *)"ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003843 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003844 p = dict_get_string(dict, (char_u *)"fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003845 if (p != NULL)
3846 {
3847 if (check_ff_value(p) == FAIL)
3848 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3849 else
3850 ea.force_ff = *p;
3851 }
Bram Moolenaar8f667172018-12-14 15:38:31 +01003852 p = dict_get_string(dict, (char_u *)"enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003853 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003854 p = dict_get_string(dict, (char_u *)"encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003855 if (p != NULL)
3856 {
Bram Moolenaar51e14382019-05-25 20:21:28 +02003857 ea.cmd = alloc(STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003858 if (ea.cmd != NULL)
3859 {
3860 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3861 ea.force_enc = 11;
3862 tofree = ea.cmd;
3863 }
3864 }
3865
Bram Moolenaar8f667172018-12-14 15:38:31 +01003866 p = dict_get_string(dict, (char_u *)"bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003867 if (p != NULL)
3868 get_bad_opt(p, &ea);
3869
3870 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3871 ea.force_bin = FORCE_BIN;
3872 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3873 ea.force_bin = FORCE_BIN;
3874 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3875 ea.force_bin = FORCE_NOBIN;
3876 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3877 ea.force_bin = FORCE_NOBIN;
3878 }
3879
3880 /* open in new window, like ":split fname" */
3881 if (ea.cmd == NULL)
3882 ea.cmd = (char_u *)"split";
3883 ea.arg = fname;
3884 ea.cmdidx = CMD_split;
3885 ex_splitview(&ea);
3886
3887 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003888}
3889
3890/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003891 * Return TRUE if "func" starts with "pat" and "pat" isn't empty.
3892 */
3893 static int
3894is_permitted_term_api(char_u *func, char_u *pat)
3895{
3896 return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
3897}
3898
3899/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003900 * Handles a function call from the job running in a terminal.
3901 * "item" is the function name, "item->li_next" has the arguments.
3902 */
3903 static void
3904handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3905{
3906 char_u *func;
3907 typval_T argvars[2];
3908 typval_T rettv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02003909 funcexe_T funcexe;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003910
3911 if (item->li_next == NULL)
3912 {
3913 ch_log(channel, "Missing function arguments for call");
3914 return;
3915 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003916 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003917
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003918 if (!is_permitted_term_api(func, term->tl_api))
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003919 {
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003920 ch_log(channel, "Unpermitted function: %s", func);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003921 return;
3922 }
3923
3924 argvars[0].v_type = VAR_NUMBER;
3925 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3926 argvars[1] = item->li_next->li_tv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02003927 vim_memset(&funcexe, 0, sizeof(funcexe));
3928 funcexe.firstline = 1L;
3929 funcexe.lastline = 1L;
3930 funcexe.evaluate = TRUE;
3931 if (call_func(func, -1, &rettv, 2, argvars, &funcexe) == OK)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003932 {
3933 clear_tv(&rettv);
3934 ch_log(channel, "Function %s called", func);
3935 }
3936 else
3937 ch_log(channel, "Calling function %s failed", func);
3938}
3939
3940/*
3941 * Called by libvterm when it cannot recognize an OSC sequence.
3942 * We recognize a terminal API command.
3943 */
3944 static int
3945parse_osc(const char *command, size_t cmdlen, void *user)
3946{
3947 term_T *term = (term_T *)user;
3948 js_read_T reader;
3949 typval_T tv;
3950 channel_T *channel = term->tl_job == NULL ? NULL
3951 : term->tl_job->jv_channel;
3952
3953 /* We recognize only OSC 5 1 ; {command} */
3954 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
3955 return 0; /* not handled */
3956
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003957 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003958 if (reader.js_buf == NULL)
3959 return 1;
3960 reader.js_fill = NULL;
3961 reader.js_used = 0;
3962 if (json_decode(&reader, &tv, 0) == OK
3963 && tv.v_type == VAR_LIST
3964 && tv.vval.v_list != NULL)
3965 {
3966 listitem_T *item = tv.vval.v_list->lv_first;
3967
3968 if (item == NULL)
3969 ch_log(channel, "Missing command");
3970 else
3971 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003972 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003973
Bram Moolenaara997b452018-04-17 23:24:06 +02003974 /* Make sure an invoked command doesn't delete the buffer (and the
3975 * terminal) under our fingers. */
3976 ++term->tl_buffer->b_locked;
3977
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003978 item = item->li_next;
3979 if (item == NULL)
3980 ch_log(channel, "Missing argument for %s", cmd);
3981 else if (STRCMP(cmd, "drop") == 0)
3982 handle_drop_command(item);
3983 else if (STRCMP(cmd, "call") == 0)
3984 handle_call_command(term, channel, item);
3985 else
3986 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003987 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003988 }
3989 }
3990 else
3991 ch_log(channel, "Invalid JSON received");
3992
3993 vim_free(reader.js_buf);
3994 clear_tv(&tv);
3995 return 1;
3996}
3997
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02003998/*
3999 * Called by libvterm when it cannot recognize a CSI sequence.
4000 * We recognize the window position report.
4001 */
4002 static int
4003parse_csi(
4004 const char *leader UNUSED,
4005 const long args[],
4006 int argcount,
4007 const char *intermed UNUSED,
4008 char command,
4009 void *user)
4010{
4011 term_T *term = (term_T *)user;
4012 char buf[100];
4013 int len;
4014 int x = 0;
4015 int y = 0;
4016 win_T *wp;
4017
4018 // We recognize only CSI 13 t
4019 if (command != 't' || argcount != 1 || args[0] != 13)
4020 return 0; // not handled
4021
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004022 // When getting the window position is not possible or it fails it results
4023 // in zero/zero.
Bram Moolenaar16c34c32019-04-06 22:01:24 +02004024#if defined(FEAT_GUI) \
4025 || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
4026 || defined(MSWIN)
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004027 (void)ui_get_winpos(&x, &y, (varnumber_T)100);
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004028#endif
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004029
4030 FOR_ALL_WINDOWS(wp)
4031 if (wp->w_buffer == term->tl_buffer)
4032 break;
4033 if (wp != NULL)
4034 {
4035#ifdef FEAT_GUI
4036 if (gui.in_use)
4037 {
4038 x += wp->w_wincol * gui.char_width;
4039 y += W_WINROW(wp) * gui.char_height;
4040 }
4041 else
4042#endif
4043 {
4044 // We roughly estimate the position of the terminal window inside
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004045 // the Vim window by assuming a 10 x 7 character cell.
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004046 x += wp->w_wincol * 7;
4047 y += W_WINROW(wp) * 10;
4048 }
4049 }
4050
4051 len = vim_snprintf(buf, 100, "\x1b[3;%d;%dt", x, y);
4052 channel_send(term->tl_job->jv_channel, get_tty_part(term),
4053 (char_u *)buf, len, NULL);
4054 return 1;
4055}
4056
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004057static VTermParserCallbacks parser_fallbacks = {
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004058 NULL, // text
4059 NULL, // control
4060 NULL, // escape
4061 parse_csi, // csi
4062 parse_osc, // osc
4063 NULL, // dcs
4064 NULL // resize
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004065};
4066
4067/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02004068 * Use Vim's allocation functions for vterm so profiling works.
4069 */
4070 static void *
4071vterm_malloc(size_t size, void *data UNUSED)
4072{
Bram Moolenaar18a4ba22019-05-24 19:39:03 +02004073 return alloc_clear(size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02004074}
4075
4076 static void
4077vterm_memfree(void *ptr, void *data UNUSED)
4078{
4079 vim_free(ptr);
4080}
4081
4082static VTermAllocatorFunctions vterm_allocator = {
4083 &vterm_malloc,
4084 &vterm_memfree
4085};
4086
4087/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004088 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004089 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004090 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004091 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01004092create_vterm(term_T *term, int rows, int cols)
4093{
4094 VTerm *vterm;
4095 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004096 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01004097 VTermValue value;
4098
Bram Moolenaar756ef112018-04-10 12:04:27 +02004099 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004100 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004101 if (vterm == NULL)
4102 return FAIL;
4103
4104 // Allocate screen and state here, so we can bail out if that fails.
4105 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004106 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004107 if (state == NULL || screen == NULL)
4108 {
4109 vterm_free(vterm);
4110 return FAIL;
4111 }
4112
Bram Moolenaar52acb112018-03-18 19:20:22 +01004113 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
4114 /* TODO: depends on 'encoding'. */
4115 vterm_set_utf8(vterm, 1);
4116
4117 init_default_colors(term);
4118
4119 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004120 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01004121 &term->tl_default_color.fg,
4122 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004123
Bram Moolenaar9e587872019-05-13 20:27:23 +02004124 if (t_colors < 16)
4125 // Less than 16 colors: assume that bold means using a bright color for
4126 // the foreground color.
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004127 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
4128
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004129 /* Required to initialize most things. */
4130 vterm_screen_reset(screen, 1 /* hard */);
4131
4132 /* Allow using alternate screen. */
4133 vterm_screen_enable_altscreen(screen, 1);
4134
4135 /* For unix do not use a blinking cursor. In an xterm this causes the
4136 * cursor to blink if it's blinking in the xterm.
4137 * For Windows we respect the system wide setting. */
Bram Moolenaar4f974752019-02-17 17:44:42 +01004138#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004139 if (GetCaretBlinkTime() == INFINITE)
4140 value.boolean = 0;
4141 else
4142 value.boolean = 1;
4143#else
4144 value.boolean = 0;
4145#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004146 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
4147 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004148
4149 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004150}
4151
4152/*
4153 * Return the text to show for the buffer name and status.
4154 */
4155 char_u *
4156term_get_status_text(term_T *term)
4157{
4158 if (term->tl_status_text == NULL)
4159 {
4160 char_u *txt;
4161 size_t len;
4162
4163 if (term->tl_normal_mode)
4164 {
4165 if (term_job_running(term))
4166 txt = (char_u *)_("Terminal");
4167 else
4168 txt = (char_u *)_("Terminal-finished");
4169 }
4170 else if (term->tl_title != NULL)
4171 txt = term->tl_title;
4172 else if (term_none_open(term))
4173 txt = (char_u *)_("active");
4174 else if (term_job_running(term))
4175 txt = (char_u *)_("running");
4176 else
4177 txt = (char_u *)_("finished");
4178 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
Bram Moolenaar51e14382019-05-25 20:21:28 +02004179 term->tl_status_text = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004180 if (term->tl_status_text != NULL)
4181 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
4182 term->tl_buffer->b_fname, txt);
4183 }
4184 return term->tl_status_text;
4185}
4186
4187/*
4188 * Mark references in jobs of terminals.
4189 */
4190 int
4191set_ref_in_term(int copyID)
4192{
4193 int abort = FALSE;
4194 term_T *term;
4195 typval_T tv;
4196
Bram Moolenaar75a1a942019-06-20 03:45:36 +02004197 for (term = first_term; !abort && term != NULL; term = term->tl_next)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004198 if (term->tl_job != NULL)
4199 {
4200 tv.v_type = VAR_JOB;
4201 tv.vval.v_job = term->tl_job;
4202 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4203 }
4204 return abort;
4205}
4206
4207/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01004208 * Cache "Terminal" highlight group colors.
4209 */
4210 void
4211set_terminal_default_colors(int cterm_fg, int cterm_bg)
4212{
4213 term_default_cterm_fg = cterm_fg - 1;
4214 term_default_cterm_bg = cterm_bg - 1;
4215}
4216
4217/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004218 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004219 * Returns NULL when the buffer is not for a terminal window and logs a message
4220 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004221 */
4222 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004223term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004224{
4225 buf_T *buf;
4226
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004227 (void)tv_get_number(&argvars[0]); /* issue errmsg if type error */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004228 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01004229 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004230 --emsg_off;
4231 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004232 {
4233 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004234 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004235 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004236 return buf;
4237}
4238
Bram Moolenaard96ff162018-02-18 22:13:29 +01004239 static int
4240same_color(VTermColor *a, VTermColor *b)
4241{
4242 return a->red == b->red
4243 && a->green == b->green
4244 && a->blue == b->blue
4245 && a->ansi_index == b->ansi_index;
4246}
4247
4248 static void
4249dump_term_color(FILE *fd, VTermColor *color)
4250{
4251 fprintf(fd, "%02x%02x%02x%d",
4252 (int)color->red, (int)color->green, (int)color->blue,
4253 (int)color->ansi_index);
4254}
4255
4256/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004257 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01004258 *
4259 * Each screen cell in full is:
4260 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
4261 * {characters} is a space for an empty cell
4262 * For a double-width character "+" is changed to "*" and the next cell is
4263 * skipped.
4264 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
4265 * when "&" use the same as the previous cell.
4266 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
4267 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
4268 * {color-idx} is a number from 0 to 255
4269 *
4270 * Screen cell with same width, attributes and color as the previous one:
4271 * |{characters}
4272 *
4273 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
4274 *
4275 * Repeating the previous screen cell:
4276 * @{count}
4277 */
4278 void
4279f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
4280{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004281 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01004282 term_T *term;
4283 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004284 int max_height = 0;
4285 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004286 stat_T st;
4287 FILE *fd;
4288 VTermPos pos;
4289 VTermScreen *screen;
4290 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004291 VTermState *state;
4292 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004293
4294 if (check_restricted() || check_secure())
4295 return;
4296 if (buf == NULL)
4297 return;
4298 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004299 if (term->tl_vterm == NULL)
4300 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004301 emsg(_("E958: Job already finished"));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004302 return;
4303 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004304
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004305 if (argvars[2].v_type != VAR_UNKNOWN)
4306 {
4307 dict_T *d;
4308
4309 if (argvars[2].v_type != VAR_DICT)
4310 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004311 emsg(_(e_dictreq));
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004312 return;
4313 }
4314 d = argvars[2].vval.v_dict;
4315 if (d != NULL)
4316 {
Bram Moolenaar8f667172018-12-14 15:38:31 +01004317 max_height = dict_get_number(d, (char_u *)"rows");
4318 max_width = dict_get_number(d, (char_u *)"columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004319 }
4320 }
4321
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004322 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004323 if (fname == NULL)
4324 return;
4325 if (mch_stat((char *)fname, &st) >= 0)
4326 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004327 semsg(_("E953: File exists: %s"), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004328 return;
4329 }
4330
Bram Moolenaard96ff162018-02-18 22:13:29 +01004331 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
4332 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004333 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004334 return;
4335 }
4336
4337 vim_memset(&prev_cell, 0, sizeof(prev_cell));
4338
4339 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004340 state = vterm_obtain_state(term->tl_vterm);
4341 vterm_state_get_cursorpos(state, &cursor_pos);
4342
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004343 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
4344 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004345 {
4346 int repeat = 0;
4347
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004348 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
4349 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004350 {
4351 VTermScreenCell cell;
4352 int same_attr;
4353 int same_chars = TRUE;
4354 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004355 int is_cursor_pos = (pos.col == cursor_pos.col
4356 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004357
4358 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4359 vim_memset(&cell, 0, sizeof(cell));
4360
4361 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4362 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01004363 int c = cell.chars[i];
4364 int pc = prev_cell.chars[i];
4365
4366 /* For the first character NUL is the same as space. */
4367 if (i == 0)
4368 {
4369 c = (c == NUL) ? ' ' : c;
4370 pc = (pc == NUL) ? ' ' : pc;
4371 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004372 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004373 same_chars = FALSE;
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004374 if (c == NUL || pc == NUL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004375 break;
4376 }
4377 same_attr = vtermAttr2hl(cell.attrs)
4378 == vtermAttr2hl(prev_cell.attrs)
4379 && same_color(&cell.fg, &prev_cell.fg)
4380 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004381 if (same_chars && cell.width == prev_cell.width && same_attr
4382 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004383 {
4384 ++repeat;
4385 }
4386 else
4387 {
4388 if (repeat > 0)
4389 {
4390 fprintf(fd, "@%d", repeat);
4391 repeat = 0;
4392 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004393 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004394
4395 if (cell.chars[0] == NUL)
4396 fputs(" ", fd);
4397 else
4398 {
4399 char_u charbuf[10];
4400 int len;
4401
4402 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
4403 && cell.chars[i] != NUL; ++i)
4404 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02004405 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004406 fwrite(charbuf, len, 1, fd);
4407 }
4408 }
4409
4410 /* When only the characters differ we don't write anything, the
4411 * following "|", "@" or NL will indicate using the same
4412 * attributes. */
4413 if (cell.width != prev_cell.width || !same_attr)
4414 {
4415 if (cell.width == 2)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004416 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004417 else
4418 fputs("+", fd);
4419
4420 if (same_attr)
4421 {
4422 fputs("&", fd);
4423 }
4424 else
4425 {
4426 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
4427 if (same_color(&cell.fg, &prev_cell.fg))
4428 fputs("&", fd);
4429 else
4430 {
4431 fputs("#", fd);
4432 dump_term_color(fd, &cell.fg);
4433 }
4434 if (same_color(&cell.bg, &prev_cell.bg))
4435 fputs("&", fd);
4436 else
4437 {
4438 fputs("#", fd);
4439 dump_term_color(fd, &cell.bg);
4440 }
4441 }
4442 }
4443
4444 prev_cell = cell;
4445 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004446
4447 if (cell.width == 2)
4448 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004449 }
4450 if (repeat > 0)
4451 fprintf(fd, "@%d", repeat);
4452 fputs("\n", fd);
4453 }
4454
4455 fclose(fd);
4456}
4457
4458/*
4459 * Called when a dump is corrupted. Put a breakpoint here when debugging.
4460 */
4461 static void
4462dump_is_corrupt(garray_T *gap)
4463{
4464 ga_concat(gap, (char_u *)"CORRUPT");
4465}
4466
4467 static void
4468append_cell(garray_T *gap, cellattr_T *cell)
4469{
4470 if (ga_grow(gap, 1) == OK)
4471 {
4472 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4473 ++gap->ga_len;
4474 }
4475}
4476
4477/*
4478 * Read the dump file from "fd" and append lines to the current buffer.
4479 * Return the cell width of the longest line.
4480 */
4481 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01004482read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004483{
4484 int c;
4485 garray_T ga_text;
4486 garray_T ga_cell;
4487 char_u *prev_char = NULL;
4488 int attr = 0;
4489 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004490 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004491 term_T *term = curbuf->b_term;
4492 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004493 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004494
4495 ga_init2(&ga_text, 1, 90);
4496 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4497 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004498 vim_memset(&empty_cell, 0, sizeof(empty_cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01004499 cursor_pos->row = -1;
4500 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004501
4502 c = fgetc(fd);
4503 for (;;)
4504 {
4505 if (c == EOF)
4506 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02004507 if (c == '\r')
4508 {
4509 // DOS line endings? Ignore.
4510 c = fgetc(fd);
4511 }
4512 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004513 {
4514 /* End of a line: append it to the buffer. */
4515 if (ga_text.ga_data == NULL)
4516 dump_is_corrupt(&ga_text);
4517 if (ga_grow(&term->tl_scrollback, 1) == OK)
4518 {
4519 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
4520 + term->tl_scrollback.ga_len;
4521
4522 if (max_cells < ga_cell.ga_len)
4523 max_cells = ga_cell.ga_len;
4524 line->sb_cols = ga_cell.ga_len;
4525 line->sb_cells = ga_cell.ga_data;
4526 line->sb_fill_attr = term->tl_default_color;
4527 ++term->tl_scrollback.ga_len;
4528 ga_init(&ga_cell);
4529
4530 ga_append(&ga_text, NUL);
4531 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4532 ga_text.ga_len, FALSE);
4533 }
4534 else
4535 ga_clear(&ga_cell);
4536 ga_text.ga_len = 0;
4537
4538 c = fgetc(fd);
4539 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004540 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004541 {
4542 int prev_len = ga_text.ga_len;
4543
Bram Moolenaar9271d052018-02-25 21:39:46 +01004544 if (c == '>')
4545 {
4546 if (cursor_pos->row != -1)
4547 dump_is_corrupt(&ga_text); /* duplicate cursor */
4548 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4549 cursor_pos->col = ga_cell.ga_len;
4550 }
4551
Bram Moolenaard96ff162018-02-18 22:13:29 +01004552 /* normal character(s) followed by "+", "*", "|", "@" or NL */
4553 c = fgetc(fd);
4554 if (c != EOF)
4555 ga_append(&ga_text, c);
4556 for (;;)
4557 {
4558 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004559 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004560 || c == EOF || c == '\n')
4561 break;
4562 ga_append(&ga_text, c);
4563 }
4564
4565 /* save the character for repeating it */
4566 vim_free(prev_char);
4567 if (ga_text.ga_data != NULL)
4568 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4569 ga_text.ga_len - prev_len);
4570
Bram Moolenaar9271d052018-02-25 21:39:46 +01004571 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004572 {
4573 /* use all attributes from previous cell */
4574 }
4575 else if (c == '+' || c == '*')
4576 {
4577 int is_bg;
4578
4579 cell.width = c == '+' ? 1 : 2;
4580
4581 c = fgetc(fd);
4582 if (c == '&')
4583 {
4584 /* use same attr as previous cell */
4585 c = fgetc(fd);
4586 }
4587 else if (isdigit(c))
4588 {
4589 /* get the decimal attribute */
4590 attr = 0;
4591 while (isdigit(c))
4592 {
4593 attr = attr * 10 + (c - '0');
4594 c = fgetc(fd);
4595 }
4596 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004597
4598 /* is_bg == 0: fg, is_bg == 1: bg */
4599 for (is_bg = 0; is_bg <= 1; ++is_bg)
4600 {
4601 if (c == '&')
4602 {
4603 /* use same color as previous cell */
4604 c = fgetc(fd);
4605 }
4606 else if (c == '#')
4607 {
4608 int red, green, blue, index = 0;
4609
4610 c = fgetc(fd);
4611 red = hex2nr(c);
4612 c = fgetc(fd);
4613 red = (red << 4) + hex2nr(c);
4614 c = fgetc(fd);
4615 green = hex2nr(c);
4616 c = fgetc(fd);
4617 green = (green << 4) + hex2nr(c);
4618 c = fgetc(fd);
4619 blue = hex2nr(c);
4620 c = fgetc(fd);
4621 blue = (blue << 4) + hex2nr(c);
4622 c = fgetc(fd);
4623 if (!isdigit(c))
4624 dump_is_corrupt(&ga_text);
4625 while (isdigit(c))
4626 {
4627 index = index * 10 + (c - '0');
4628 c = fgetc(fd);
4629 }
4630
4631 if (is_bg)
4632 {
4633 cell.bg.red = red;
4634 cell.bg.green = green;
4635 cell.bg.blue = blue;
4636 cell.bg.ansi_index = index;
4637 }
4638 else
4639 {
4640 cell.fg.red = red;
4641 cell.fg.green = green;
4642 cell.fg.blue = blue;
4643 cell.fg.ansi_index = index;
4644 }
4645 }
4646 else
4647 dump_is_corrupt(&ga_text);
4648 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004649 }
4650 else
4651 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004652 }
4653 else
4654 dump_is_corrupt(&ga_text);
4655
4656 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004657 if (cell.width == 2)
4658 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004659 }
4660 else if (c == '@')
4661 {
4662 if (prev_char == NULL)
4663 dump_is_corrupt(&ga_text);
4664 else
4665 {
4666 int count = 0;
4667
4668 /* repeat previous character, get the count */
4669 for (;;)
4670 {
4671 c = fgetc(fd);
4672 if (!isdigit(c))
4673 break;
4674 count = count * 10 + (c - '0');
4675 }
4676
4677 while (count-- > 0)
4678 {
4679 ga_concat(&ga_text, prev_char);
4680 append_cell(&ga_cell, &cell);
4681 }
4682 }
4683 }
4684 else
4685 {
4686 dump_is_corrupt(&ga_text);
4687 c = fgetc(fd);
4688 }
4689 }
4690
4691 if (ga_text.ga_len > 0)
4692 {
4693 /* trailing characters after last NL */
4694 dump_is_corrupt(&ga_text);
4695 ga_append(&ga_text, NUL);
4696 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4697 ga_text.ga_len, FALSE);
4698 }
4699
4700 ga_clear(&ga_text);
Bram Moolenaar86173482019-10-01 17:02:16 +02004701 ga_clear(&ga_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004702 vim_free(prev_char);
4703
4704 return max_cells;
4705}
4706
4707/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004708 * Return an allocated string with at least "text_width" "=" characters and
4709 * "fname" inserted in the middle.
4710 */
4711 static char_u *
4712get_separator(int text_width, char_u *fname)
4713{
4714 int width = MAX(text_width, curwin->w_width);
4715 char_u *textline;
4716 int fname_size;
4717 char_u *p = fname;
4718 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004719 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004720
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004721 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004722 if (textline == NULL)
4723 return NULL;
4724
4725 fname_size = vim_strsize(fname);
4726 if (fname_size < width - 8)
4727 {
4728 /* enough room, don't use the full window width */
4729 width = MAX(text_width, fname_size + 8);
4730 }
4731 else if (fname_size > width - 8)
4732 {
4733 /* full name doesn't fit, use only the tail */
4734 p = gettail(fname);
4735 fname_size = vim_strsize(p);
4736 }
4737 /* skip characters until the name fits */
4738 while (fname_size > width - 8)
4739 {
4740 p += (*mb_ptr2len)(p);
4741 fname_size = vim_strsize(p);
4742 }
4743
4744 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4745 textline[i] = '=';
4746 textline[i++] = ' ';
4747
4748 STRCPY(textline + i, p);
4749 off = STRLEN(textline);
4750 textline[off] = ' ';
4751 for (i = 1; i < (width - fname_size) / 2; ++i)
4752 textline[off + i] = '=';
4753 textline[off + i] = NUL;
4754
4755 return textline;
4756}
4757
4758/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004759 * Common for "term_dumpdiff()" and "term_dumpload()".
4760 */
4761 static void
4762term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4763{
4764 jobopt_T opt;
Bram Moolenaar87abab92019-06-03 21:14:59 +02004765 buf_T *buf = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004766 char_u buf1[NUMBUFLEN];
4767 char_u buf2[NUMBUFLEN];
4768 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004769 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004770 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004771 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004772 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004773 char_u *textline = NULL;
4774
4775 /* First open the files. If this fails bail out. */
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004776 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004777 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004778 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004779 if (fname1 == NULL || (do_diff && fname2 == NULL))
4780 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004781 emsg(_(e_invarg));
Bram Moolenaard96ff162018-02-18 22:13:29 +01004782 return;
4783 }
4784 fd1 = mch_fopen((char *)fname1, READBIN);
4785 if (fd1 == NULL)
4786 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004787 semsg(_(e_notread), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004788 return;
4789 }
4790 if (do_diff)
4791 {
4792 fd2 = mch_fopen((char *)fname2, READBIN);
4793 if (fd2 == NULL)
4794 {
4795 fclose(fd1);
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004796 semsg(_(e_notread), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004797 return;
4798 }
4799 }
4800
4801 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004802 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4803 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4804 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4805 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4806 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004807
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004808 if (opt.jo_term_name == NULL)
4809 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004810 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004811
Bram Moolenaar51e14382019-05-25 20:21:28 +02004812 fname_tofree = alloc(len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004813 if (fname_tofree != NULL)
4814 {
4815 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4816 opt.jo_term_name = fname_tofree;
4817 }
4818 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004819
Bram Moolenaar87abab92019-06-03 21:14:59 +02004820 if (opt.jo_bufnr_buf != NULL)
4821 {
4822 win_T *wp = buf_jump_open_win(opt.jo_bufnr_buf);
4823
4824 // With "bufnr" argument: enter the window with this buffer and make it
4825 // empty.
4826 if (wp == NULL)
4827 semsg(_(e_invarg2), "bufnr");
4828 else
4829 {
4830 buf = curbuf;
4831 while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
4832 ml_delete((linenr_T)1, FALSE);
Bram Moolenaar86173482019-10-01 17:02:16 +02004833 free_scrollback(curbuf->b_term);
Bram Moolenaar87abab92019-06-03 21:14:59 +02004834 redraw_later(NOT_VALID);
4835 }
4836 }
4837 else
4838 // Create a new terminal window.
4839 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
4840
Bram Moolenaard96ff162018-02-18 22:13:29 +01004841 if (buf != NULL && buf->b_term != NULL)
4842 {
4843 int i;
4844 linenr_T bot_lnum;
4845 linenr_T lnum;
4846 term_T *term = buf->b_term;
4847 int width;
4848 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004849 VTermPos cursor_pos1;
4850 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004851
Bram Moolenaar52acb112018-03-18 19:20:22 +01004852 init_default_colors(term);
4853
Bram Moolenaard96ff162018-02-18 22:13:29 +01004854 rettv->vval.v_number = buf->b_fnum;
4855
4856 /* read the files, fill the buffer with the diff */
Bram Moolenaar9271d052018-02-25 21:39:46 +01004857 width = read_dump_file(fd1, &cursor_pos1);
4858
4859 /* position the cursor */
4860 if (cursor_pos1.row >= 0)
4861 {
4862 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4863 coladvance(cursor_pos1.col);
4864 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004865
4866 /* Delete the empty line that was in the empty buffer. */
4867 ml_delete(1, FALSE);
4868
4869 /* For term_dumpload() we are done here. */
4870 if (!do_diff)
4871 goto theend;
4872
4873 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4874
Bram Moolenaar4a696342018-04-05 18:45:26 +02004875 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004876 if (textline == NULL)
4877 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004878 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4879 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4880 vim_free(textline);
4881
4882 textline = get_separator(width, fname2);
4883 if (textline == NULL)
4884 goto theend;
4885 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4886 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004887 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004888
4889 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004890 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004891 if (width2 > width)
4892 {
4893 vim_free(textline);
4894 textline = alloc(width2 + 1);
4895 if (textline == NULL)
4896 goto theend;
4897 width = width2;
4898 textline[width] = NUL;
4899 }
4900 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4901
4902 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4903 {
4904 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4905 {
4906 /* bottom part has fewer rows, fill with "-" */
4907 for (i = 0; i < width; ++i)
4908 textline[i] = '-';
4909 }
4910 else
4911 {
4912 char_u *line1;
4913 char_u *line2;
4914 char_u *p1;
4915 char_u *p2;
4916 int col;
4917 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4918 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4919 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4920 ->sb_cells;
4921
4922 /* Make a copy, getting the second line will invalidate it. */
4923 line1 = vim_strsave(ml_get(lnum));
4924 if (line1 == NULL)
4925 break;
4926 p1 = line1;
4927
4928 line2 = ml_get(lnum + bot_lnum);
4929 p2 = line2;
4930 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4931 {
4932 int len1 = utfc_ptr2len(p1);
4933 int len2 = utfc_ptr2len(p2);
4934
4935 textline[col] = ' ';
4936 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar9271d052018-02-25 21:39:46 +01004937 /* text differs */
Bram Moolenaard96ff162018-02-18 22:13:29 +01004938 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004939 else if (lnum == cursor_pos1.row + 1
4940 && col == cursor_pos1.col
4941 && (cursor_pos1.row != cursor_pos2.row
4942 || cursor_pos1.col != cursor_pos2.col))
4943 /* cursor in first but not in second */
4944 textline[col] = '>';
4945 else if (lnum == cursor_pos2.row + 1
4946 && col == cursor_pos2.col
4947 && (cursor_pos1.row != cursor_pos2.row
4948 || cursor_pos1.col != cursor_pos2.col))
4949 /* cursor in second but not in first */
4950 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004951 else if (cellattr1 != NULL && cellattr2 != NULL)
4952 {
4953 if ((cellattr1 + col)->width
4954 != (cellattr2 + col)->width)
4955 textline[col] = 'w';
4956 else if (!same_color(&(cellattr1 + col)->fg,
4957 &(cellattr2 + col)->fg))
4958 textline[col] = 'f';
4959 else if (!same_color(&(cellattr1 + col)->bg,
4960 &(cellattr2 + col)->bg))
4961 textline[col] = 'b';
4962 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4963 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4964 textline[col] = 'a';
4965 }
4966 p1 += len1;
4967 p2 += len2;
4968 /* TODO: handle different width */
4969 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004970
4971 while (col < width)
4972 {
4973 if (*p1 == NUL && *p2 == NUL)
4974 textline[col] = '?';
4975 else if (*p1 == NUL)
4976 {
4977 textline[col] = '+';
4978 p2 += utfc_ptr2len(p2);
4979 }
4980 else
4981 {
4982 textline[col] = '-';
4983 p1 += utfc_ptr2len(p1);
4984 }
4985 ++col;
4986 }
Bram Moolenaar81aa0f52019-02-14 23:23:19 +01004987
4988 vim_free(line1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004989 }
4990 if (add_empty_scrollback(term, &term->tl_default_color,
4991 term->tl_top_diff_rows) == OK)
4992 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4993 ++bot_lnum;
4994 }
4995
4996 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
4997 {
4998 /* bottom part has more rows, fill with "+" */
4999 for (i = 0; i < width; ++i)
5000 textline[i] = '+';
5001 if (add_empty_scrollback(term, &term->tl_default_color,
5002 term->tl_top_diff_rows) == OK)
5003 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5004 ++lnum;
5005 ++bot_lnum;
5006 }
5007
5008 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005009
5010 /* looks better without wrapping */
5011 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005012 }
5013
5014theend:
5015 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005016 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005017 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005018 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005019 fclose(fd2);
5020}
5021
5022/*
5023 * If the current buffer shows the output of term_dumpdiff(), swap the top and
5024 * bottom files.
5025 * Return FAIL when this is not possible.
5026 */
5027 int
5028term_swap_diff()
5029{
5030 term_T *term = curbuf->b_term;
5031 linenr_T line_count;
5032 linenr_T top_rows;
5033 linenr_T bot_rows;
5034 linenr_T bot_start;
5035 linenr_T lnum;
5036 char_u *p;
5037 sb_line_T *sb_line;
5038
5039 if (term == NULL
5040 || !term_is_finished(curbuf)
5041 || term->tl_top_diff_rows == 0
5042 || term->tl_scrollback.ga_len == 0)
5043 return FAIL;
5044
5045 line_count = curbuf->b_ml.ml_line_count;
5046 top_rows = term->tl_top_diff_rows;
5047 bot_rows = term->tl_bot_diff_rows;
5048 bot_start = line_count - bot_rows;
5049 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5050
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005051 // move lines from top to above the bottom part
Bram Moolenaard96ff162018-02-18 22:13:29 +01005052 for (lnum = 1; lnum <= top_rows; ++lnum)
5053 {
5054 p = vim_strsave(ml_get(1));
5055 if (p == NULL)
5056 return OK;
5057 ml_append(bot_start, p, 0, FALSE);
5058 ml_delete(1, FALSE);
5059 vim_free(p);
5060 }
5061
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005062 // move lines from bottom to the top
Bram Moolenaard96ff162018-02-18 22:13:29 +01005063 for (lnum = 1; lnum <= bot_rows; ++lnum)
5064 {
5065 p = vim_strsave(ml_get(bot_start + lnum));
5066 if (p == NULL)
5067 return OK;
5068 ml_delete(bot_start + lnum, FALSE);
5069 ml_append(lnum - 1, p, 0, FALSE);
5070 vim_free(p);
5071 }
5072
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005073 // move top title to bottom
5074 p = vim_strsave(ml_get(bot_rows + 1));
5075 if (p == NULL)
5076 return OK;
5077 ml_append(line_count - top_rows - 1, p, 0, FALSE);
5078 ml_delete(bot_rows + 1, FALSE);
5079 vim_free(p);
5080
5081 // move bottom title to top
5082 p = vim_strsave(ml_get(line_count - top_rows));
5083 if (p == NULL)
5084 return OK;
5085 ml_delete(line_count - top_rows, FALSE);
5086 ml_append(bot_rows, p, 0, FALSE);
5087 vim_free(p);
5088
Bram Moolenaard96ff162018-02-18 22:13:29 +01005089 if (top_rows == bot_rows)
5090 {
5091 /* rows counts are equal, can swap cell properties */
5092 for (lnum = 0; lnum < top_rows; ++lnum)
5093 {
5094 sb_line_T temp;
5095
5096 temp = *(sb_line + lnum);
5097 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
5098 *(sb_line + bot_start + lnum) = temp;
5099 }
5100 }
5101 else
5102 {
5103 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005104 sb_line_T *temp = alloc(size);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005105
5106 /* need to copy cell properties into temp memory */
5107 if (temp != NULL)
5108 {
5109 mch_memmove(temp, term->tl_scrollback.ga_data, size);
5110 mch_memmove(term->tl_scrollback.ga_data,
5111 temp + bot_start,
5112 sizeof(sb_line_T) * bot_rows);
5113 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
5114 temp + top_rows,
5115 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
5116 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
5117 + line_count - top_rows,
5118 temp,
5119 sizeof(sb_line_T) * top_rows);
5120 vim_free(temp);
5121 }
5122 }
5123
5124 term->tl_top_diff_rows = bot_rows;
5125 term->tl_bot_diff_rows = top_rows;
5126
5127 update_screen(NOT_VALID);
5128 return OK;
5129}
5130
5131/*
5132 * "term_dumpdiff(filename, filename, options)" function
5133 */
5134 void
5135f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
5136{
5137 term_load_dump(argvars, rettv, TRUE);
5138}
5139
5140/*
5141 * "term_dumpload(filename, options)" function
5142 */
5143 void
5144f_term_dumpload(typval_T *argvars, typval_T *rettv)
5145{
5146 term_load_dump(argvars, rettv, FALSE);
5147}
5148
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005149/*
5150 * "term_getaltscreen(buf)" function
5151 */
5152 void
5153f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
5154{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005155 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005156
5157 if (buf == NULL)
5158 return;
5159 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
5160}
5161
5162/*
5163 * "term_getattr(attr, name)" function
5164 */
5165 void
5166f_term_getattr(typval_T *argvars, typval_T *rettv)
5167{
5168 int attr;
5169 size_t i;
5170 char_u *name;
5171
5172 static struct {
5173 char *name;
5174 int attr;
5175 } attrs[] = {
5176 {"bold", HL_BOLD},
5177 {"italic", HL_ITALIC},
5178 {"underline", HL_UNDERLINE},
5179 {"strike", HL_STRIKETHROUGH},
5180 {"reverse", HL_INVERSE},
5181 };
5182
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005183 attr = tv_get_number(&argvars[0]);
5184 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005185 if (name == NULL)
5186 return;
5187
Bram Moolenaar7ee80f72019-09-08 20:55:06 +02005188 if (attr > HL_ALL)
5189 attr = syn_attr2attr(attr);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005190 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
5191 if (STRCMP(name, attrs[i].name) == 0)
5192 {
5193 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
5194 break;
5195 }
5196}
5197
5198/*
5199 * "term_getcursor(buf)" function
5200 */
5201 void
5202f_term_getcursor(typval_T *argvars, typval_T *rettv)
5203{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005204 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005205 term_T *term;
5206 list_T *l;
5207 dict_T *d;
5208
5209 if (rettv_list_alloc(rettv) == FAIL)
5210 return;
5211 if (buf == NULL)
5212 return;
5213 term = buf->b_term;
5214
5215 l = rettv->vval.v_list;
5216 list_append_number(l, term->tl_cursor_pos.row + 1);
5217 list_append_number(l, term->tl_cursor_pos.col + 1);
5218
5219 d = dict_alloc();
5220 if (d != NULL)
5221 {
Bram Moolenaare0be1672018-07-08 16:50:37 +02005222 dict_add_number(d, "visible", term->tl_cursor_visible);
5223 dict_add_number(d, "blink", blink_state_is_inverted()
5224 ? !term->tl_cursor_blink : term->tl_cursor_blink);
5225 dict_add_number(d, "shape", term->tl_cursor_shape);
5226 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005227 list_append_dict(l, d);
5228 }
5229}
5230
5231/*
5232 * "term_getjob(buf)" function
5233 */
5234 void
5235f_term_getjob(typval_T *argvars, typval_T *rettv)
5236{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005237 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005238
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005239 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005240 {
5241 rettv->v_type = VAR_SPECIAL;
5242 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005243 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005244 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005245
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005246 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005247 rettv->vval.v_job = buf->b_term->tl_job;
5248 if (rettv->vval.v_job != NULL)
5249 ++rettv->vval.v_job->jv_refcount;
5250}
5251
5252 static int
5253get_row_number(typval_T *tv, term_T *term)
5254{
5255 if (tv->v_type == VAR_STRING
5256 && tv->vval.v_string != NULL
5257 && STRCMP(tv->vval.v_string, ".") == 0)
5258 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005259 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005260}
5261
5262/*
5263 * "term_getline(buf, row)" function
5264 */
5265 void
5266f_term_getline(typval_T *argvars, typval_T *rettv)
5267{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005268 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005269 term_T *term;
5270 int row;
5271
5272 rettv->v_type = VAR_STRING;
5273 if (buf == NULL)
5274 return;
5275 term = buf->b_term;
5276 row = get_row_number(&argvars[1], term);
5277
5278 if (term->tl_vterm == NULL)
5279 {
5280 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
5281
5282 /* vterm is finished, get the text from the buffer */
5283 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
5284 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
5285 }
5286 else
5287 {
5288 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
5289 VTermRect rect;
5290 int len;
5291 char_u *p;
5292
5293 if (row < 0 || row >= term->tl_rows)
5294 return;
5295 len = term->tl_cols * MB_MAXBYTES + 1;
5296 p = alloc(len);
5297 if (p == NULL)
5298 return;
5299 rettv->vval.v_string = p;
5300
5301 rect.start_col = 0;
5302 rect.end_col = term->tl_cols;
5303 rect.start_row = row;
5304 rect.end_row = row + 1;
5305 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
5306 }
5307}
5308
5309/*
5310 * "term_getscrolled(buf)" function
5311 */
5312 void
5313f_term_getscrolled(typval_T *argvars, typval_T *rettv)
5314{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005315 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005316
5317 if (buf == NULL)
5318 return;
5319 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
5320}
5321
5322/*
5323 * "term_getsize(buf)" function
5324 */
5325 void
5326f_term_getsize(typval_T *argvars, typval_T *rettv)
5327{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005328 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005329 list_T *l;
5330
5331 if (rettv_list_alloc(rettv) == FAIL)
5332 return;
5333 if (buf == NULL)
5334 return;
5335
5336 l = rettv->vval.v_list;
5337 list_append_number(l, buf->b_term->tl_rows);
5338 list_append_number(l, buf->b_term->tl_cols);
5339}
5340
5341/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005342 * "term_setsize(buf, rows, cols)" function
5343 */
5344 void
5345f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5346{
5347 buf_T *buf = term_get_buf(argvars, "term_setsize()");
5348 term_T *term;
5349 varnumber_T rows, cols;
5350
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005351 if (buf == NULL)
5352 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005353 emsg(_("E955: Not a terminal buffer"));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005354 return;
5355 }
5356 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02005357 return;
5358 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005359 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005360 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005361 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005362 cols = cols <= 0 ? term->tl_cols : cols;
5363 vterm_set_size(term->tl_vterm, rows, cols);
5364 /* handle_resize() will resize the windows */
5365
5366 /* Get and remember the size we ended up with. Update the pty. */
5367 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
5368 term_report_winsize(term, term->tl_rows, term->tl_cols);
5369}
5370
5371/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005372 * "term_getstatus(buf)" function
5373 */
5374 void
5375f_term_getstatus(typval_T *argvars, typval_T *rettv)
5376{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005377 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005378 term_T *term;
5379 char_u val[100];
5380
5381 rettv->v_type = VAR_STRING;
5382 if (buf == NULL)
5383 return;
5384 term = buf->b_term;
5385
5386 if (term_job_running(term))
5387 STRCPY(val, "running");
5388 else
5389 STRCPY(val, "finished");
5390 if (term->tl_normal_mode)
5391 STRCAT(val, ",normal");
5392 rettv->vval.v_string = vim_strsave(val);
5393}
5394
5395/*
5396 * "term_gettitle(buf)" function
5397 */
5398 void
5399f_term_gettitle(typval_T *argvars, typval_T *rettv)
5400{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005401 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005402
5403 rettv->v_type = VAR_STRING;
5404 if (buf == NULL)
5405 return;
5406
5407 if (buf->b_term->tl_title != NULL)
5408 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
5409}
5410
5411/*
5412 * "term_gettty(buf)" function
5413 */
5414 void
5415f_term_gettty(typval_T *argvars, typval_T *rettv)
5416{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005417 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02005418 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005419 int num = 0;
5420
5421 rettv->v_type = VAR_STRING;
5422 if (buf == NULL)
5423 return;
5424 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005425 num = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005426
5427 switch (num)
5428 {
5429 case 0:
5430 if (buf->b_term->tl_job != NULL)
5431 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005432 break;
5433 case 1:
5434 if (buf->b_term->tl_job != NULL)
5435 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005436 break;
5437 default:
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005438 semsg(_(e_invarg2), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005439 return;
5440 }
5441 if (p != NULL)
5442 rettv->vval.v_string = vim_strsave(p);
5443}
5444
5445/*
5446 * "term_list()" function
5447 */
5448 void
5449f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
5450{
5451 term_T *tp;
5452 list_T *l;
5453
5454 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
5455 return;
5456
5457 l = rettv->vval.v_list;
5458 for (tp = first_term; tp != NULL; tp = tp->tl_next)
5459 if (tp != NULL && tp->tl_buffer != NULL)
5460 if (list_append_number(l,
5461 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
5462 return;
5463}
5464
5465/*
5466 * "term_scrape(buf, row)" function
5467 */
5468 void
5469f_term_scrape(typval_T *argvars, typval_T *rettv)
5470{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005471 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005472 VTermScreen *screen = NULL;
5473 VTermPos pos;
5474 list_T *l;
5475 term_T *term;
5476 char_u *p;
5477 sb_line_T *line;
5478
5479 if (rettv_list_alloc(rettv) == FAIL)
5480 return;
5481 if (buf == NULL)
5482 return;
5483 term = buf->b_term;
5484
5485 l = rettv->vval.v_list;
5486 pos.row = get_row_number(&argvars[1], term);
5487
5488 if (term->tl_vterm != NULL)
5489 {
5490 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01005491 if (screen == NULL) // can't really happen
5492 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005493 p = NULL;
5494 line = NULL;
5495 }
5496 else
5497 {
5498 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
5499
5500 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
5501 return;
5502 p = ml_get_buf(buf, lnum + 1, FALSE);
5503 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
5504 }
5505
5506 for (pos.col = 0; pos.col < term->tl_cols; )
5507 {
5508 dict_T *dcell;
5509 int width;
5510 VTermScreenCellAttrs attrs;
5511 VTermColor fg, bg;
5512 char_u rgb[8];
5513 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5514 int off = 0;
5515 int i;
5516
5517 if (screen == NULL)
5518 {
5519 cellattr_T *cellattr;
5520 int len;
5521
5522 /* vterm has finished, get the cell from scrollback */
5523 if (pos.col >= line->sb_cols)
5524 break;
5525 cellattr = line->sb_cells + pos.col;
5526 width = cellattr->width;
5527 attrs = cellattr->attrs;
5528 fg = cellattr->fg;
5529 bg = cellattr->bg;
Bram Moolenaar1614a142019-10-06 22:00:13 +02005530 len = mb_ptr2len(p);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005531 mch_memmove(mbs, p, len);
5532 mbs[len] = NUL;
5533 p += len;
5534 }
5535 else
5536 {
5537 VTermScreenCell cell;
5538 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5539 break;
5540 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5541 {
5542 if (cell.chars[i] == 0)
5543 break;
5544 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5545 }
5546 mbs[off] = NUL;
5547 width = cell.width;
5548 attrs = cell.attrs;
5549 fg = cell.fg;
5550 bg = cell.bg;
5551 }
5552 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01005553 if (dcell == NULL)
5554 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005555 list_append_dict(l, dcell);
5556
Bram Moolenaare0be1672018-07-08 16:50:37 +02005557 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005558
5559 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5560 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005561 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005562 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5563 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005564 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005565
Bram Moolenaare0be1672018-07-08 16:50:37 +02005566 dict_add_number(dcell, "attr", cell2attr(attrs, fg, bg));
5567 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005568
5569 ++pos.col;
5570 if (width == 2)
5571 ++pos.col;
5572 }
5573}
5574
5575/*
5576 * "term_sendkeys(buf, keys)" function
5577 */
5578 void
5579f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5580{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005581 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005582 char_u *msg;
5583 term_T *term;
5584
5585 rettv->v_type = VAR_UNKNOWN;
5586 if (buf == NULL)
5587 return;
5588
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005589 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005590 if (msg == NULL)
5591 return;
5592 term = buf->b_term;
5593 if (term->tl_vterm == NULL)
5594 return;
5595
5596 while (*msg != NUL)
5597 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02005598 int c;
5599
5600 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
5601 {
5602 c = TO_SPECIAL(msg[1], msg[2]);
5603 msg += 3;
5604 }
5605 else
5606 {
5607 c = PTR2CHAR(msg);
5608 msg += MB_CPTR2LEN(msg);
5609 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01005610 send_keys_to_term(term, c, 0, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005611 }
5612}
5613
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005614#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5615/*
5616 * "term_getansicolors(buf)" function
5617 */
5618 void
5619f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5620{
5621 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5622 term_T *term;
5623 VTermState *state;
5624 VTermColor color;
5625 char_u hexbuf[10];
5626 int index;
5627 list_T *list;
5628
5629 if (rettv_list_alloc(rettv) == FAIL)
5630 return;
5631
5632 if (buf == NULL)
5633 return;
5634 term = buf->b_term;
5635 if (term->tl_vterm == NULL)
5636 return;
5637
5638 list = rettv->vval.v_list;
5639 state = vterm_obtain_state(term->tl_vterm);
5640 for (index = 0; index < 16; index++)
5641 {
5642 vterm_state_get_palette_color(state, index, &color);
5643 sprintf((char *)hexbuf, "#%02x%02x%02x",
5644 color.red, color.green, color.blue);
5645 if (list_append_string(list, hexbuf, 7) == FAIL)
5646 return;
5647 }
5648}
5649
5650/*
5651 * "term_setansicolors(buf, list)" function
5652 */
5653 void
5654f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5655{
5656 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5657 term_T *term;
5658
5659 if (buf == NULL)
5660 return;
5661 term = buf->b_term;
5662 if (term->tl_vterm == NULL)
5663 return;
5664
5665 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5666 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005667 emsg(_(e_listreq));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005668 return;
5669 }
5670
5671 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005672 emsg(_(e_invarg));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005673}
5674#endif
5675
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005676/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005677 * "term_setapi(buf, api)" function
5678 */
5679 void
5680f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
5681{
5682 buf_T *buf = term_get_buf(argvars, "term_setapi()");
5683 term_T *term;
5684 char_u *api;
5685
5686 if (buf == NULL)
5687 return;
5688 term = buf->b_term;
5689 vim_free(term->tl_api);
5690 api = tv_get_string_chk(&argvars[1]);
5691 if (api != NULL)
5692 term->tl_api = vim_strsave(api);
5693 else
5694 term->tl_api = NULL;
5695}
5696
5697/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005698 * "term_setrestore(buf, command)" function
5699 */
5700 void
5701f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5702{
5703#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005704 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005705 term_T *term;
5706 char_u *cmd;
5707
5708 if (buf == NULL)
5709 return;
5710 term = buf->b_term;
5711 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005712 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005713 if (cmd != NULL)
5714 term->tl_command = vim_strsave(cmd);
5715 else
5716 term->tl_command = NULL;
5717#endif
5718}
5719
5720/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005721 * "term_setkill(buf, how)" function
5722 */
5723 void
5724f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5725{
5726 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5727 term_T *term;
5728 char_u *how;
5729
5730 if (buf == NULL)
5731 return;
5732 term = buf->b_term;
5733 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005734 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005735 if (how != NULL)
5736 term->tl_kill = vim_strsave(how);
5737 else
5738 term->tl_kill = NULL;
5739}
5740
5741/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005742 * "term_start(command, options)" function
5743 */
5744 void
5745f_term_start(typval_T *argvars, typval_T *rettv)
5746{
5747 jobopt_T opt;
5748 buf_T *buf;
5749
5750 init_job_options(&opt);
5751 if (argvars[1].v_type != VAR_UNKNOWN
5752 && get_job_options(&argvars[1], &opt,
5753 JO_TIMEOUT_ALL + JO_STOPONEXIT
5754 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5755 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5756 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5757 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005758 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005759 + JO2_NORESTORE + JO2_TERM_KILL
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005760 + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005761 return;
5762
Bram Moolenaar13568252018-03-16 20:46:58 +01005763 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005764
5765 if (buf != NULL && buf->b_term != NULL)
5766 rettv->vval.v_number = buf->b_fnum;
5767}
5768
5769/*
5770 * "term_wait" function
5771 */
5772 void
5773f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5774{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005775 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005776
5777 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005778 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005779 if (buf->b_term->tl_job == NULL)
5780 {
5781 ch_log(NULL, "term_wait(): no job to wait for");
5782 return;
5783 }
5784 if (buf->b_term->tl_job->jv_channel == NULL)
5785 /* channel is closed, nothing to do */
5786 return;
5787
5788 /* Get the job status, this will detect a job that finished. */
Bram Moolenaara15ef452018-02-09 16:46:00 +01005789 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005790 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5791 {
5792 /* The job is dead, keep reading channel I/O until the channel is
5793 * closed. buf->b_term may become NULL if the terminal was closed while
5794 * waiting. */
5795 ch_log(NULL, "term_wait(): waiting for channel to close");
5796 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5797 {
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005798 term_flush_messages();
5799
Bram Moolenaard45aa552018-05-21 22:50:29 +02005800 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01005801 if (!buf_valid(buf))
5802 /* If the terminal is closed when the channel is closed the
5803 * buffer disappears. */
5804 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005805 }
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005806
5807 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005808 }
5809 else
5810 {
5811 long wait = 10L;
5812
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005813 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005814
5815 /* Wait for some time for any channel I/O. */
5816 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005817 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005818 ui_delay(wait, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005819
5820 /* Flushing messages on channels is hopefully sufficient.
5821 * TODO: is there a better way? */
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005822 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005823 }
5824}
5825
5826/*
5827 * Called when a channel has sent all the lines to a terminal.
5828 * Send a CTRL-D to mark the end of the text.
5829 */
5830 void
5831term_send_eof(channel_T *ch)
5832{
5833 term_T *term;
5834
5835 for (term = first_term; term != NULL; term = term->tl_next)
5836 if (term->tl_job == ch->ch_job)
5837 {
5838 if (term->tl_eof_chars != NULL)
5839 {
5840 channel_send(ch, PART_IN, term->tl_eof_chars,
5841 (int)STRLEN(term->tl_eof_chars), NULL);
5842 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5843 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01005844# ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005845 else
5846 /* Default: CTRL-D */
5847 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5848# endif
5849 }
5850}
5851
Bram Moolenaar113e1072019-01-20 15:30:40 +01005852#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005853 job_T *
5854term_getjob(term_T *term)
5855{
5856 return term != NULL ? term->tl_job : NULL;
5857}
Bram Moolenaar113e1072019-01-20 15:30:40 +01005858#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005859
Bram Moolenaar4f974752019-02-17 17:44:42 +01005860# if defined(MSWIN) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005861
5862/**************************************
5863 * 2. MS-Windows implementation.
5864 */
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005865#ifdef PROTO
5866typedef int COORD;
5867typedef int DWORD;
5868typedef int HANDLE;
5869typedef int *DWORD_PTR;
5870typedef int HPCON;
5871typedef int HRESULT;
5872typedef int LPPROC_THREAD_ATTRIBUTE_LIST;
Bram Moolenaarad3ec762019-04-21 00:00:13 +02005873typedef int SIZE_T;
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005874typedef int PSIZE_T;
5875typedef int PVOID;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01005876typedef int BOOL;
5877# define WINAPI
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005878#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005879
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005880HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
5881HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
5882HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
Bram Moolenaar48773f12019-02-12 21:46:46 +01005883BOOL (WINAPI *pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
5884BOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
5885void (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005886
5887 static int
5888dyn_conpty_init(int verbose)
5889{
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005890 static HMODULE hKerneldll = NULL;
5891 int i;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005892 static struct
5893 {
5894 char *name;
5895 FARPROC *ptr;
5896 } conpty_entry[] =
5897 {
5898 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
5899 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
5900 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
5901 {"InitializeProcThreadAttributeList",
5902 (FARPROC*)&pInitializeProcThreadAttributeList},
5903 {"UpdateProcThreadAttribute",
5904 (FARPROC*)&pUpdateProcThreadAttribute},
5905 {"DeleteProcThreadAttributeList",
5906 (FARPROC*)&pDeleteProcThreadAttributeList},
5907 {NULL, NULL}
5908 };
5909
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01005910 if (!has_conpty_working())
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005911 {
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005912 if (verbose)
5913 emsg(_("E982: ConPTY is not available"));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005914 return FAIL;
5915 }
5916
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005917 // No need to initialize twice.
5918 if (hKerneldll)
5919 return OK;
5920
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005921 hKerneldll = vimLoadLib("kernel32.dll");
5922 for (i = 0; conpty_entry[i].name != NULL
5923 && conpty_entry[i].ptr != NULL; ++i)
5924 {
5925 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
5926 conpty_entry[i].name)) == NULL)
5927 {
5928 if (verbose)
5929 semsg(_(e_loadfunc), conpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005930 hKerneldll = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005931 return FAIL;
5932 }
5933 }
5934
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005935 return OK;
5936}
5937
5938 static int
5939conpty_term_and_job_init(
5940 term_T *term,
5941 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02005942 char **argv UNUSED,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005943 jobopt_T *opt,
5944 jobopt_T *orig_opt)
5945{
5946 WCHAR *cmd_wchar = NULL;
5947 WCHAR *cmd_wchar_copy = NULL;
5948 WCHAR *cwd_wchar = NULL;
5949 WCHAR *env_wchar = NULL;
5950 channel_T *channel = NULL;
5951 job_T *job = NULL;
5952 HANDLE jo = NULL;
5953 garray_T ga_cmd, ga_env;
5954 char_u *cmd = NULL;
5955 HRESULT hr;
5956 COORD consize;
5957 SIZE_T breq;
5958 PROCESS_INFORMATION proc_info;
5959 HANDLE i_theirs = NULL;
5960 HANDLE o_theirs = NULL;
5961 HANDLE i_ours = NULL;
5962 HANDLE o_ours = NULL;
5963
5964 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5965 ga_init2(&ga_env, (int)sizeof(char*), 20);
5966
5967 if (argvar->v_type == VAR_STRING)
5968 {
5969 cmd = argvar->vval.v_string;
5970 }
5971 else if (argvar->v_type == VAR_LIST)
5972 {
5973 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
5974 goto failed;
5975 cmd = ga_cmd.ga_data;
5976 }
5977 if (cmd == NULL || *cmd == NUL)
5978 {
5979 emsg(_(e_invarg));
5980 goto failed;
5981 }
5982
5983 term->tl_arg0_cmd = vim_strsave(cmd);
5984
5985 cmd_wchar = enc_to_utf16(cmd, NULL);
5986
5987 if (cmd_wchar != NULL)
5988 {
5989 /* Request by CreateProcessW */
5990 breq = wcslen(cmd_wchar) + 1 + 1; /* Addition of NUL by API */
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005991 cmd_wchar_copy = ALLOC_MULT(WCHAR, breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005992 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
5993 }
5994
5995 ga_clear(&ga_cmd);
5996 if (cmd_wchar == NULL)
5997 goto failed;
5998 if (opt->jo_cwd != NULL)
5999 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
6000
6001 win32_build_env(opt->jo_env, &ga_env, TRUE);
6002 env_wchar = ga_env.ga_data;
6003
6004 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
6005 goto failed;
6006 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
6007 goto failed;
6008
6009 consize.X = term->tl_cols;
6010 consize.Y = term->tl_rows;
6011 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
6012 &term->tl_conpty);
6013 if (FAILED(hr))
6014 goto failed;
6015
6016 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
6017
6018 /* Set up pipe inheritance safely: Vista or later. */
6019 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006020 term->tl_siex.lpAttributeList = alloc(breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006021 if (!term->tl_siex.lpAttributeList)
6022 goto failed;
6023 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
6024 0, &breq))
6025 goto failed;
6026 if (!pUpdateProcThreadAttribute(
6027 term->tl_siex.lpAttributeList, 0,
6028 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
6029 sizeof(HPCON), NULL, NULL))
6030 goto failed;
6031
6032 channel = add_channel();
6033 if (channel == NULL)
6034 goto failed;
6035
6036 job = job_alloc();
6037 if (job == NULL)
6038 goto failed;
6039 if (argvar->v_type == VAR_STRING)
6040 {
6041 int argc;
6042
6043 build_argv_from_string(cmd, &job->jv_argv, &argc);
6044 }
6045 else
6046 {
6047 int argc;
6048
6049 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6050 }
6051
6052 if (opt->jo_set & JO_IN_BUF)
6053 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6054
6055 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
6056 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
6057 | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP
6058 | CREATE_DEFAULT_ERROR_MODE,
6059 env_wchar, cwd_wchar,
6060 &term->tl_siex.StartupInfo, &proc_info))
6061 goto failed;
6062
6063 CloseHandle(i_theirs);
6064 CloseHandle(o_theirs);
6065
6066 channel_set_pipes(channel,
6067 (sock_T)i_ours,
6068 (sock_T)o_ours,
6069 (sock_T)o_ours);
6070
6071 /* Write lines with CR instead of NL. */
6072 channel->ch_write_text_mode = TRUE;
6073
6074 /* Use to explicitly delete anonymous pipe handle. */
6075 channel->ch_anonymous_pipe = TRUE;
6076
6077 jo = CreateJobObject(NULL, NULL);
6078 if (jo == NULL)
6079 goto failed;
6080
6081 if (!AssignProcessToJobObject(jo, proc_info.hProcess))
6082 {
6083 /* Failed, switch the way to terminate process with TerminateProcess. */
6084 CloseHandle(jo);
6085 jo = NULL;
6086 }
6087
6088 ResumeThread(proc_info.hThread);
6089 CloseHandle(proc_info.hThread);
6090
6091 vim_free(cmd_wchar);
6092 vim_free(cmd_wchar_copy);
6093 vim_free(cwd_wchar);
6094 vim_free(env_wchar);
6095
6096 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6097 goto failed;
6098
6099#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6100 if (opt->jo_set2 & JO2_ANSI_COLORS)
6101 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6102 else
6103 init_vterm_ansi_colors(term->tl_vterm);
6104#endif
6105
6106 channel_set_job(channel, job, opt);
6107 job_set_options(job, opt);
6108
6109 job->jv_channel = channel;
6110 job->jv_proc_info = proc_info;
6111 job->jv_job_object = jo;
6112 job->jv_status = JOB_STARTED;
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006113 job->jv_tty_type = vim_strsave((char_u *)"conpty");
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006114 ++job->jv_refcount;
6115 term->tl_job = job;
6116
6117 /* Redirecting stdout and stderr doesn't work at the job level. Instead
6118 * open the file here and handle it in. opt->jo_io was changed in
6119 * setup_job_options(), use the original flags here. */
6120 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6121 {
6122 char_u *fname = opt->jo_io_name[PART_OUT];
6123
6124 ch_log(channel, "Opening output file %s", fname);
6125 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6126 if (term->tl_out_fd == NULL)
6127 semsg(_(e_notopen), fname);
6128 }
6129
6130 return OK;
6131
6132failed:
6133 ga_clear(&ga_cmd);
6134 ga_clear(&ga_env);
6135 vim_free(cmd_wchar);
6136 vim_free(cmd_wchar_copy);
6137 vim_free(cwd_wchar);
6138 if (channel != NULL)
6139 channel_clear(channel);
6140 if (job != NULL)
6141 {
6142 job->jv_channel = NULL;
6143 job_cleanup(job);
6144 }
6145 term->tl_job = NULL;
6146 if (jo != NULL)
6147 CloseHandle(jo);
6148
6149 if (term->tl_siex.lpAttributeList != NULL)
6150 {
6151 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6152 vim_free(term->tl_siex.lpAttributeList);
6153 }
6154 term->tl_siex.lpAttributeList = NULL;
6155 if (o_theirs != NULL)
6156 CloseHandle(o_theirs);
6157 if (o_ours != NULL)
6158 CloseHandle(o_ours);
6159 if (i_ours != NULL)
6160 CloseHandle(i_ours);
6161 if (i_theirs != NULL)
6162 CloseHandle(i_theirs);
6163 if (term->tl_conpty != NULL)
6164 pClosePseudoConsole(term->tl_conpty);
6165 term->tl_conpty = NULL;
6166 return FAIL;
6167}
6168
6169 static void
6170conpty_term_report_winsize(term_T *term, int rows, int cols)
6171{
6172 COORD consize;
6173
6174 consize.X = cols;
6175 consize.Y = rows;
6176 pResizePseudoConsole(term->tl_conpty, consize);
6177}
6178
Bram Moolenaar840d16f2019-09-10 21:27:18 +02006179 static void
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006180term_free_conpty(term_T *term)
6181{
6182 if (term->tl_siex.lpAttributeList != NULL)
6183 {
6184 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6185 vim_free(term->tl_siex.lpAttributeList);
6186 }
6187 term->tl_siex.lpAttributeList = NULL;
6188 if (term->tl_conpty != NULL)
6189 pClosePseudoConsole(term->tl_conpty);
6190 term->tl_conpty = NULL;
6191}
6192
6193 int
6194use_conpty(void)
6195{
6196 return has_conpty;
6197}
6198
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006199# ifndef PROTO
6200
6201#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
6202#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01006203#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006204
6205void* (*winpty_config_new)(UINT64, void*);
6206void* (*winpty_open)(void*, void*);
6207void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
6208BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
6209void (*winpty_config_set_mouse_mode)(void*, int);
6210void (*winpty_config_set_initial_size)(void*, int, int);
6211LPCWSTR (*winpty_conin_name)(void*);
6212LPCWSTR (*winpty_conout_name)(void*);
6213LPCWSTR (*winpty_conerr_name)(void*);
6214void (*winpty_free)(void*);
6215void (*winpty_config_free)(void*);
6216void (*winpty_spawn_config_free)(void*);
6217void (*winpty_error_free)(void*);
6218LPCWSTR (*winpty_error_msg)(void*);
6219BOOL (*winpty_set_size)(void*, int, int, void*);
6220HANDLE (*winpty_agent_process)(void*);
6221
6222#define WINPTY_DLL "winpty.dll"
6223
6224static HINSTANCE hWinPtyDLL = NULL;
6225# endif
6226
6227 static int
6228dyn_winpty_init(int verbose)
6229{
6230 int i;
6231 static struct
6232 {
6233 char *name;
6234 FARPROC *ptr;
6235 } winpty_entry[] =
6236 {
6237 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
6238 {"winpty_config_free", (FARPROC*)&winpty_config_free},
6239 {"winpty_config_new", (FARPROC*)&winpty_config_new},
6240 {"winpty_config_set_mouse_mode",
6241 (FARPROC*)&winpty_config_set_mouse_mode},
6242 {"winpty_config_set_initial_size",
6243 (FARPROC*)&winpty_config_set_initial_size},
6244 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
6245 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
6246 {"winpty_error_free", (FARPROC*)&winpty_error_free},
6247 {"winpty_free", (FARPROC*)&winpty_free},
6248 {"winpty_open", (FARPROC*)&winpty_open},
6249 {"winpty_spawn", (FARPROC*)&winpty_spawn},
6250 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
6251 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
6252 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
6253 {"winpty_set_size", (FARPROC*)&winpty_set_size},
6254 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
6255 {NULL, NULL}
6256 };
6257
6258 /* No need to initialize twice. */
6259 if (hWinPtyDLL)
6260 return OK;
6261 /* Load winpty.dll, prefer using the 'winptydll' option, fall back to just
6262 * winpty.dll. */
6263 if (*p_winptydll != NUL)
6264 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
6265 if (!hWinPtyDLL)
6266 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
6267 if (!hWinPtyDLL)
6268 {
6269 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006270 semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006271 : (char_u *)WINPTY_DLL);
6272 return FAIL;
6273 }
6274 for (i = 0; winpty_entry[i].name != NULL
6275 && winpty_entry[i].ptr != NULL; ++i)
6276 {
6277 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
6278 winpty_entry[i].name)) == NULL)
6279 {
6280 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006281 semsg(_(e_loadfunc), winpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006282 hWinPtyDLL = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006283 return FAIL;
6284 }
6285 }
6286
6287 return OK;
6288}
6289
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006290 static int
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006291winpty_term_and_job_init(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006292 term_T *term,
6293 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02006294 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006295 jobopt_T *opt,
6296 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006297{
6298 WCHAR *cmd_wchar = NULL;
6299 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006300 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006301 channel_T *channel = NULL;
6302 job_T *job = NULL;
6303 DWORD error;
6304 HANDLE jo = NULL;
6305 HANDLE child_process_handle;
6306 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01006307 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006308 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006309 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006310 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006311
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006312 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
6313 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006314
6315 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006316 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006317 cmd = argvar->vval.v_string;
6318 }
6319 else if (argvar->v_type == VAR_LIST)
6320 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006321 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006322 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006323 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006324 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006325 if (cmd == NULL || *cmd == NUL)
6326 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006327 emsg(_(e_invarg));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006328 goto failed;
6329 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006330
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006331 term->tl_arg0_cmd = vim_strsave(cmd);
6332
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006333 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006334 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006335 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006336 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006337 if (opt->jo_cwd != NULL)
6338 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006339
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006340 win32_build_env(opt->jo_env, &ga_env, TRUE);
6341 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006342
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006343 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
6344 if (term->tl_winpty_config == NULL)
6345 goto failed;
6346
6347 winpty_config_set_mouse_mode(term->tl_winpty_config,
6348 WINPTY_MOUSE_MODE_FORCE);
6349 winpty_config_set_initial_size(term->tl_winpty_config,
6350 term->tl_cols, term->tl_rows);
6351 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
6352 if (term->tl_winpty == NULL)
6353 goto failed;
6354
6355 spawn_config = winpty_spawn_config_new(
6356 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
6357 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
6358 NULL,
6359 cmd_wchar,
6360 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006361 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006362 &winpty_err);
6363 if (spawn_config == NULL)
6364 goto failed;
6365
6366 channel = add_channel();
6367 if (channel == NULL)
6368 goto failed;
6369
6370 job = job_alloc();
6371 if (job == NULL)
6372 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02006373 if (argvar->v_type == VAR_STRING)
6374 {
6375 int argc;
6376
6377 build_argv_from_string(cmd, &job->jv_argv, &argc);
6378 }
6379 else
6380 {
6381 int argc;
6382
6383 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6384 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006385
6386 if (opt->jo_set & JO_IN_BUF)
6387 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6388
6389 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
6390 &child_thread_handle, &error, &winpty_err))
6391 goto failed;
6392
6393 channel_set_pipes(channel,
6394 (sock_T)CreateFileW(
6395 winpty_conin_name(term->tl_winpty),
6396 GENERIC_WRITE, 0, NULL,
6397 OPEN_EXISTING, 0, NULL),
6398 (sock_T)CreateFileW(
6399 winpty_conout_name(term->tl_winpty),
6400 GENERIC_READ, 0, NULL,
6401 OPEN_EXISTING, 0, NULL),
6402 (sock_T)CreateFileW(
6403 winpty_conerr_name(term->tl_winpty),
6404 GENERIC_READ, 0, NULL,
6405 OPEN_EXISTING, 0, NULL));
6406
6407 /* Write lines with CR instead of NL. */
6408 channel->ch_write_text_mode = TRUE;
6409
6410 jo = CreateJobObject(NULL, NULL);
6411 if (jo == NULL)
6412 goto failed;
6413
6414 if (!AssignProcessToJobObject(jo, child_process_handle))
6415 {
6416 /* Failed, switch the way to terminate process with TerminateProcess. */
6417 CloseHandle(jo);
6418 jo = NULL;
6419 }
6420
6421 winpty_spawn_config_free(spawn_config);
6422 vim_free(cmd_wchar);
6423 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006424 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006425
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006426 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6427 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006428
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006429#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6430 if (opt->jo_set2 & JO2_ANSI_COLORS)
6431 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6432 else
6433 init_vterm_ansi_colors(term->tl_vterm);
6434#endif
6435
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006436 channel_set_job(channel, job, opt);
6437 job_set_options(job, opt);
6438
6439 job->jv_channel = channel;
6440 job->jv_proc_info.hProcess = child_process_handle;
6441 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
6442 job->jv_job_object = jo;
6443 job->jv_status = JOB_STARTED;
6444 job->jv_tty_in = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006445 (short_u *)winpty_conin_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006446 job->jv_tty_out = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006447 (short_u *)winpty_conout_name(term->tl_winpty), NULL);
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006448 job->jv_tty_type = vim_strsave((char_u *)"winpty");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006449 ++job->jv_refcount;
6450 term->tl_job = job;
6451
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006452 /* Redirecting stdout and stderr doesn't work at the job level. Instead
6453 * open the file here and handle it in. opt->jo_io was changed in
6454 * setup_job_options(), use the original flags here. */
6455 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6456 {
6457 char_u *fname = opt->jo_io_name[PART_OUT];
6458
6459 ch_log(channel, "Opening output file %s", fname);
6460 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6461 if (term->tl_out_fd == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006462 semsg(_(e_notopen), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006463 }
6464
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006465 return OK;
6466
6467failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006468 ga_clear(&ga_cmd);
6469 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006470 vim_free(cmd_wchar);
6471 vim_free(cwd_wchar);
6472 if (spawn_config != NULL)
6473 winpty_spawn_config_free(spawn_config);
6474 if (channel != NULL)
6475 channel_clear(channel);
6476 if (job != NULL)
6477 {
6478 job->jv_channel = NULL;
6479 job_cleanup(job);
6480 }
6481 term->tl_job = NULL;
6482 if (jo != NULL)
6483 CloseHandle(jo);
6484 if (term->tl_winpty != NULL)
6485 winpty_free(term->tl_winpty);
6486 term->tl_winpty = NULL;
6487 if (term->tl_winpty_config != NULL)
6488 winpty_config_free(term->tl_winpty_config);
6489 term->tl_winpty_config = NULL;
6490 if (winpty_err != NULL)
6491 {
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006492 char *msg = (char *)utf16_to_enc(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006493 (short_u *)winpty_error_msg(winpty_err), NULL);
6494
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006495 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006496 winpty_error_free(winpty_err);
6497 }
6498 return FAIL;
6499}
6500
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006501/*
6502 * Create a new terminal of "rows" by "cols" cells.
6503 * Store a reference in "term".
6504 * Return OK or FAIL.
6505 */
6506 static int
6507term_and_job_init(
6508 term_T *term,
6509 typval_T *argvar,
Bram Moolenaar197c6b72019-11-03 23:37:12 +01006510 char **argv,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006511 jobopt_T *opt,
6512 jobopt_T *orig_opt)
6513{
6514 int use_winpty = FALSE;
6515 int use_conpty = FALSE;
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006516 int tty_type = *p_twt;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006517
6518 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
6519 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
6520
6521 if (!has_winpty && !has_conpty)
6522 // If neither is available give the errors for winpty, since when
6523 // conpty is not available it can't be installed either.
6524 return dyn_winpty_init(TRUE);
6525
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006526 if (opt->jo_tty_type != NUL)
6527 tty_type = opt->jo_tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006528
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006529 if (tty_type == NUL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006530 {
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01006531 if (has_conpty && (is_conpty_stable() || !has_winpty))
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006532 use_conpty = TRUE;
6533 else if (has_winpty)
6534 use_winpty = TRUE;
6535 // else: error
6536 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006537 else if (tty_type == 'w') // winpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006538 {
6539 if (has_winpty)
6540 use_winpty = TRUE;
6541 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006542 else if (tty_type == 'c') // conpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006543 {
6544 if (has_conpty)
6545 use_conpty = TRUE;
6546 else
6547 return dyn_conpty_init(TRUE);
6548 }
6549
6550 if (use_conpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006551 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006552
6553 if (use_winpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006554 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006555
6556 // error
6557 return dyn_winpty_init(TRUE);
6558}
6559
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006560 static int
6561create_pty_only(term_T *term, jobopt_T *options)
6562{
6563 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
6564 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
6565 char in_name[80], out_name[80];
6566 channel_T *channel = NULL;
6567
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006568 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6569 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006570
6571 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
6572 GetCurrentProcessId(),
6573 curbuf->b_fnum);
6574 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
6575 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6576 PIPE_UNLIMITED_INSTANCES,
6577 0, 0, NMPWAIT_NOWAIT, NULL);
6578 if (hPipeIn == INVALID_HANDLE_VALUE)
6579 goto failed;
6580
6581 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
6582 GetCurrentProcessId(),
6583 curbuf->b_fnum);
6584 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
6585 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6586 PIPE_UNLIMITED_INSTANCES,
6587 0, 0, 0, NULL);
6588 if (hPipeOut == INVALID_HANDLE_VALUE)
6589 goto failed;
6590
6591 ConnectNamedPipe(hPipeIn, NULL);
6592 ConnectNamedPipe(hPipeOut, NULL);
6593
6594 term->tl_job = job_alloc();
6595 if (term->tl_job == NULL)
6596 goto failed;
6597 ++term->tl_job->jv_refcount;
6598
6599 /* behave like the job is already finished */
6600 term->tl_job->jv_status = JOB_FINISHED;
6601
6602 channel = add_channel();
6603 if (channel == NULL)
6604 goto failed;
6605 term->tl_job->jv_channel = channel;
6606 channel->ch_keep_open = TRUE;
6607 channel->ch_named_pipe = TRUE;
6608
6609 channel_set_pipes(channel,
6610 (sock_T)hPipeIn,
6611 (sock_T)hPipeOut,
6612 (sock_T)hPipeOut);
6613 channel_set_job(channel, term->tl_job, options);
6614 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
6615 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
6616
6617 return OK;
6618
6619failed:
6620 if (hPipeIn != NULL)
6621 CloseHandle(hPipeIn);
6622 if (hPipeOut != NULL)
6623 CloseHandle(hPipeOut);
6624 return FAIL;
6625}
6626
6627/*
6628 * Free the terminal emulator part of "term".
6629 */
6630 static void
6631term_free_vterm(term_T *term)
6632{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006633 term_free_conpty(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006634 if (term->tl_winpty != NULL)
6635 winpty_free(term->tl_winpty);
6636 term->tl_winpty = NULL;
6637 if (term->tl_winpty_config != NULL)
6638 winpty_config_free(term->tl_winpty_config);
6639 term->tl_winpty_config = NULL;
6640 if (term->tl_vterm != NULL)
6641 vterm_free(term->tl_vterm);
6642 term->tl_vterm = NULL;
6643}
6644
6645/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006646 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006647 */
6648 static void
6649term_report_winsize(term_T *term, int rows, int cols)
6650{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006651 if (term->tl_conpty)
6652 conpty_term_report_winsize(term, rows, cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006653 if (term->tl_winpty)
6654 winpty_set_size(term->tl_winpty, cols, rows, NULL);
6655}
6656
6657 int
6658terminal_enabled(void)
6659{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006660 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006661}
6662
6663# else
6664
6665/**************************************
6666 * 3. Unix-like implementation.
6667 */
6668
6669/*
6670 * Create a new terminal of "rows" by "cols" cells.
6671 * Start job for "cmd".
6672 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01006673 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006674 * Return OK or FAIL.
6675 */
6676 static int
6677term_and_job_init(
6678 term_T *term,
6679 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01006680 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006681 jobopt_T *opt,
6682 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006683{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006684 term->tl_arg0_cmd = NULL;
6685
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006686 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6687 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006688
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006689#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6690 if (opt->jo_set2 & JO2_ANSI_COLORS)
6691 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6692 else
6693 init_vterm_ansi_colors(term->tl_vterm);
6694#endif
6695
Bram Moolenaar13568252018-03-16 20:46:58 +01006696 /* This may change a string in "argvar". */
Bram Moolenaar493359e2018-06-12 20:25:52 +02006697 term->tl_job = job_start(argvar, argv, opt, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006698 if (term->tl_job != NULL)
6699 ++term->tl_job->jv_refcount;
6700
6701 return term->tl_job != NULL
6702 && term->tl_job->jv_channel != NULL
6703 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
6704}
6705
6706 static int
6707create_pty_only(term_T *term, jobopt_T *opt)
6708{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006709 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6710 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006711
6712 term->tl_job = job_alloc();
6713 if (term->tl_job == NULL)
6714 return FAIL;
6715 ++term->tl_job->jv_refcount;
6716
6717 /* behave like the job is already finished */
6718 term->tl_job->jv_status = JOB_FINISHED;
6719
6720 return mch_create_pty_channel(term->tl_job, opt);
6721}
6722
6723/*
6724 * Free the terminal emulator part of "term".
6725 */
6726 static void
6727term_free_vterm(term_T *term)
6728{
6729 if (term->tl_vterm != NULL)
6730 vterm_free(term->tl_vterm);
6731 term->tl_vterm = NULL;
6732}
6733
6734/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006735 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006736 */
6737 static void
6738term_report_winsize(term_T *term, int rows, int cols)
6739{
6740 /* Use an ioctl() to report the new window size to the job. */
6741 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
6742 {
6743 int fd = -1;
6744 int part;
6745
6746 for (part = PART_OUT; part < PART_COUNT; ++part)
6747 {
6748 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01006749 if (mch_isatty(fd))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006750 break;
6751 }
6752 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
6753 mch_signal_job(term->tl_job, (char_u *)"winch");
6754 }
6755}
6756
6757# endif
6758
6759#endif /* FEAT_TERMINAL */