blob: 3bf914ae9a9703cf6f4746d735ea754c0bae38fa [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
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +010054// This is VTermScreenCell without the characters, thus much smaller.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020055typedef 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 Moolenaar0d6f5d92019-12-05 21:33:15 +010086// typedef term_T in structs.h
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020087struct 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)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +010094 int tl_system; // when non-zero used for :!cmd output
95 int tl_toprow; // row with first line of system terminal
Bram Moolenaar13568252018-03-16 20:46:58 +010096#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020097
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +010098 // Set when setting the size of a vterm, reset after redrawing.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +020099 int tl_vterm_size_changed;
100
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100101 int tl_normal_mode; // TRUE: Terminal-Normal mode
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200102 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
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100107#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
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100130 // last known vterm size
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200131 int tl_rows;
132 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200133
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100134 char_u *tl_title; // NULL or allocated
135 char_u *tl_status_text; // NULL or allocated
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200136
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100137 // Range of screen rows to update. Zero based.
138 int tl_dirty_row_start; // MAX_ROW if nothing dirty
139 int tl_dirty_row_end; // row below last one to update
140 int tl_dirty_snapshot; // text updated after making snapshot
Bram Moolenaar56bc8e22018-05-10 18:05:56 +0200141#ifdef FEAT_TIMERS
142 int tl_timer_set;
143 proftime_T tl_timer_due;
144#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100145 int tl_postponed_scroll; // to be scrolled up
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200146
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 Moolenaar0d6f5d92019-12-05 21:33:15 +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
Bram Moolenaard96ff162018-02-18 22:13:29 +0100155
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200156 VTermPos tl_cursor_pos;
157 int tl_cursor_visible;
158 int tl_cursor_blink;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100159 int tl_cursor_shape; // 1: block, 2: underline, 3: bar
160 char_u *tl_cursor_color; // NULL or allocated
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200161
162 int tl_using_altscreen;
163};
164
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100165#define TMODE_ONCE 1 // CTRL-\ CTRL-N used
166#define TMODE_LOOP 2 // CTRL-W N used
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200167
168/*
169 * List of all active terminals.
170 */
171static term_T *first_term = NULL;
172
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100173// Terminal active in terminal_loop().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200174static 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 Moolenaar0d6f5d92019-12-05 21:33:15 +0100181#define MAX_ROW 999999 // used for tl_dirty_row_end to update all rows
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200182#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 Moolenaar0d6f5d92019-12-05 21:33:15 +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 Moolenaar0d6f5d92019-12-05 21:33:15 +0100201// "Terminal" highlight group colors.
Bram Moolenaara7c54cf2017-12-01 21:07:20 +0100202static int term_default_cterm_fg = -1;
203static int term_default_cterm_bg = -1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200204
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +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 Moolenaar0d6f5d92019-12-05 21:33:15 +0100215///////////////////////////////////////
216// 1. Generic code for all systems.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200217
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200218 static int
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200219cursor_color_equal(char_u *lhs_color, char_u *rhs_color)
220{
221 if (lhs_color != NULL && rhs_color != NULL)
222 return STRCMP(lhs_color, rhs_color) == 0;
223 return lhs_color == NULL && rhs_color == NULL;
224}
225
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200226 static void
227cursor_color_copy(char_u **to_color, char_u *from_color)
228{
229 // Avoid a free & alloc if the value is already right.
230 if (cursor_color_equal(*to_color, from_color))
231 return;
232 vim_free(*to_color);
233 *to_color = (from_color == NULL) ? NULL : vim_strsave(from_color);
234}
235
236 static char_u *
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200237cursor_color_get(char_u *color)
238{
239 return (color == NULL) ? (char_u *)"" : color;
240}
241
242
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200243/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200244 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200245 * current window.
246 * Sets "rows" and/or "cols" to zero when it should follow the window size.
247 * Return TRUE if the size is the minimum size: "24*80".
248 */
249 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200250parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200251{
252 int minsize = FALSE;
253
254 *rows = 0;
255 *cols = 0;
256
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200257 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200258 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200259 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200260
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100261 // Syntax of value was already checked when it's set.
Bram Moolenaar498c2562018-04-15 23:45:15 +0200262 if (p == NULL)
263 {
264 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200265 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200266 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200267 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200268 *cols = atoi((char *)p + 1);
269 }
270 return minsize;
271}
272
273/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200274 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200275 */
276 static void
277set_term_and_win_size(term_T *term)
278{
Bram Moolenaar13568252018-03-16 20:46:58 +0100279#ifdef FEAT_GUI
280 if (term->tl_system)
281 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100282 // Use the whole screen for the system command. However, it will start
283 // at the command line and scroll up as needed, using tl_toprow.
Bram Moolenaar13568252018-03-16 20:46:58 +0100284 term->tl_rows = Rows;
285 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200286 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100287 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100288#endif
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200289 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200290 {
Bram Moolenaar498c2562018-04-15 23:45:15 +0200291 if (term->tl_rows != 0)
292 term->tl_rows = MAX(term->tl_rows, curwin->w_height);
293 if (term->tl_cols != 0)
294 term->tl_cols = MAX(term->tl_cols, curwin->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200295 }
296 if (term->tl_rows == 0)
297 term->tl_rows = curwin->w_height;
298 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200299 win_setheight_win(term->tl_rows, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200300 if (term->tl_cols == 0)
301 term->tl_cols = curwin->w_width;
302 else
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200303 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200304}
305
306/*
307 * Initialize job options for a terminal job.
308 * Caller may overrule some of them.
309 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100310 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200311init_job_options(jobopt_T *opt)
312{
313 clear_job_options(opt);
314
315 opt->jo_mode = MODE_RAW;
316 opt->jo_out_mode = MODE_RAW;
317 opt->jo_err_mode = MODE_RAW;
318 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
319}
320
321/*
322 * Set job options mandatory for a terminal job.
323 */
324 static void
325setup_job_options(jobopt_T *opt, int rows, int cols)
326{
Bram Moolenaar4f974752019-02-17 17:44:42 +0100327#ifndef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100328 // Win32: Redirecting the job output won't work, thus always connect stdout
329 // here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200330 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200331#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200332 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100333 // Connect stdout to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200334 opt->jo_io[PART_OUT] = JIO_BUFFER;
335 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
336 opt->jo_modifiable[PART_OUT] = 0;
337 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
338 }
339
Bram Moolenaar4f974752019-02-17 17:44:42 +0100340#ifndef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100341 // Win32: Redirecting the job output won't work, thus always connect stderr
342 // here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200343 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200344#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200345 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100346 // Connect stderr to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200347 opt->jo_io[PART_ERR] = JIO_BUFFER;
348 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
349 opt->jo_modifiable[PART_ERR] = 0;
350 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
351 }
352
353 opt->jo_pty = TRUE;
354 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
355 opt->jo_term_rows = rows;
356 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
357 opt->jo_term_cols = cols;
358}
359
360/*
Bram Moolenaar5c381eb2019-06-25 06:50:31 +0200361 * Flush messages on channels.
362 */
363 static void
364term_flush_messages()
365{
366 mch_check_messages();
367 parse_queued_messages();
368}
369
370/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100371 * Close a terminal buffer (and its window). Used when creating the terminal
372 * fails.
373 */
374 static void
375term_close_buffer(buf_T *buf, buf_T *old_curbuf)
376{
377 free_terminal(buf);
378 if (old_curbuf != NULL)
379 {
380 --curbuf->b_nwindows;
381 curbuf = old_curbuf;
382 curwin->w_buffer = curbuf;
383 ++curbuf->b_nwindows;
384 }
385
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100386 // Wiping out the buffer will also close the window and call
387 // free_terminal().
Bram Moolenaard96ff162018-02-18 22:13:29 +0100388 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
389}
390
391/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200392 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100393 * Use either "argvar" or "argv", the other must be NULL.
394 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
395 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200396 * Returns NULL when failed.
397 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100398 buf_T *
399term_start(
400 typval_T *argvar,
401 char **argv,
402 jobopt_T *opt,
403 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200404{
405 exarg_T split_ea;
406 win_T *old_curwin = curwin;
407 term_T *term;
408 buf_T *old_curbuf = NULL;
409 int res;
410 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100411 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200412 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200413
414 if (check_restricted() || check_secure())
415 return NULL;
416
417 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
418 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
419 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
Bram Moolenaarb0992022020-01-30 14:55:42 +0100420 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF))
421 || (argvar != NULL
422 && argvar->v_type == VAR_LIST
423 && argvar->vval.v_list != NULL
424 && argvar->vval.v_list->lv_first == &range_list_item))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200425 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100426 emsg(_(e_invarg));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200427 return NULL;
428 }
429
Bram Moolenaarc799fe22019-05-28 23:08:19 +0200430 term = ALLOC_CLEAR_ONE(term_T);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200431 if (term == NULL)
432 return NULL;
433 term->tl_dirty_row_end = MAX_ROW;
434 term->tl_cursor_visible = TRUE;
435 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
436 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100437#ifdef FEAT_GUI
438 term->tl_system = (flags & TERM_START_SYSTEM);
439#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200440 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100441 ga_init2(&term->tl_scrollback_postponed, sizeof(sb_line_T), 300);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200442
443 vim_memset(&split_ea, 0, sizeof(split_ea));
444 if (opt->jo_curwin)
445 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100446 // Create a new buffer in the current window.
Bram Moolenaar13568252018-03-16 20:46:58 +0100447 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200448 {
449 no_write_message();
450 vim_free(term);
451 return NULL;
452 }
453 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100454 ECMD_HIDE
455 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
456 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200457 {
458 vim_free(term);
459 return NULL;
460 }
461 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100462 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200463 {
464 buf_T *buf;
465
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100466 // Create a new buffer without a window. Make it the current buffer for
467 // a moment to be able to do the initialisations.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200468 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
469 BLN_NEW | BLN_LISTED);
470 if (buf == NULL || ml_open(buf) == FAIL)
471 {
472 vim_free(term);
473 return NULL;
474 }
475 old_curbuf = curbuf;
476 --curbuf->b_nwindows;
477 curbuf = buf;
478 curwin->w_buffer = buf;
479 ++curbuf->b_nwindows;
480 }
481 else
482 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100483 // Open a new window or tab.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200484 split_ea.cmdidx = CMD_new;
485 split_ea.cmd = (char_u *)"new";
486 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100487 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200488 {
489 split_ea.line2 = opt->jo_term_rows;
490 split_ea.addr_count = 1;
491 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100492 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200493 {
494 split_ea.line2 = opt->jo_term_cols;
495 split_ea.addr_count = 1;
496 }
497
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100498 if (vertical)
499 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200500 ex_splitview(&split_ea);
501 if (curwin == old_curwin)
502 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100503 // split failed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200504 vim_free(term);
505 return NULL;
506 }
507 }
508 term->tl_buffer = curbuf;
509 curbuf->b_term = term;
510
511 if (!opt->jo_hidden)
512 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100513 // Only one size was taken care of with :new, do the other one. With
514 // "curwin" both need to be done.
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100515 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200516 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100517 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200518 win_setwidth(opt->jo_term_cols);
519 }
520
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100521 // Link the new terminal in the list of active terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200522 term->tl_next = first_term;
523 first_term = term;
524
525 if (opt->jo_term_name != NULL)
526 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100527 else if (argv != NULL)
528 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200529 else
530 {
531 int i;
532 size_t len;
533 char_u *cmd, *p;
534
535 if (argvar->v_type == VAR_STRING)
536 {
537 cmd = argvar->vval.v_string;
538 if (cmd == NULL)
539 cmd = (char_u *)"";
540 else if (STRCMP(cmd, "NONE") == 0)
541 cmd = (char_u *)"pty";
542 }
543 else if (argvar->v_type != VAR_LIST
544 || argvar->vval.v_list == NULL
Bram Moolenaarb0992022020-01-30 14:55:42 +0100545 || argvar->vval.v_list->lv_len == 0
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100546 || (cmd = tv_get_string_chk(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200547 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
548 cmd = (char_u*)"";
549
550 len = STRLEN(cmd) + 10;
Bram Moolenaar51e14382019-05-25 20:21:28 +0200551 p = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200552
553 for (i = 0; p != NULL; ++i)
554 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100555 // Prepend a ! to the command name to avoid the buffer name equals
556 // the executable, otherwise ":w!" would overwrite it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200557 if (i == 0)
558 vim_snprintf((char *)p, len, "!%s", cmd);
559 else
560 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
561 if (buflist_findname(p) == NULL)
562 {
563 vim_free(curbuf->b_ffname);
564 curbuf->b_ffname = p;
565 break;
566 }
567 }
568 }
569 curbuf->b_fname = curbuf->b_ffname;
570
571 if (opt->jo_term_opencmd != NULL)
572 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
573
574 if (opt->jo_eof_chars != NULL)
575 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
576
577 set_string_option_direct((char_u *)"buftype", -1,
578 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar7da1fb52018-08-04 16:54:11 +0200579 // Avoid that 'buftype' is reset when this buffer is entered.
580 curbuf->b_p_initialized = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200581
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100582 // Mark the buffer as not modifiable. It can only be made modifiable after
583 // the job finished.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200584 curbuf->b_p_ma = FALSE;
585
586 set_term_and_win_size(term);
Bram Moolenaar4f974752019-02-17 17:44:42 +0100587#ifdef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200588 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
589#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200590 setup_job_options(opt, term->tl_rows, term->tl_cols);
591
Bram Moolenaar13568252018-03-16 20:46:58 +0100592 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100593 return curbuf;
594
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100595#if defined(FEAT_SESSION)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100596 // Remember the command for the session file.
Bram Moolenaar13568252018-03-16 20:46:58 +0100597 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100598 {
599 term->tl_command = vim_strsave((char_u *)"NONE");
600 }
601 else if (argvar->v_type == VAR_STRING)
602 {
603 char_u *cmd = argvar->vval.v_string;
604
605 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
606 term->tl_command = vim_strsave(cmd);
607 }
608 else if (argvar->v_type == VAR_LIST
609 && argvar->vval.v_list != NULL
610 && argvar->vval.v_list->lv_len > 0)
611 {
612 garray_T ga;
613 listitem_T *item;
614
615 ga_init2(&ga, 1, 100);
616 for (item = argvar->vval.v_list->lv_first;
617 item != NULL; item = item->li_next)
618 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100619 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100620 char_u *p;
621
622 if (s == NULL)
623 break;
624 p = vim_strsave_fnameescape(s, FALSE);
625 if (p == NULL)
626 break;
627 ga_concat(&ga, p);
628 vim_free(p);
629 ga_append(&ga, ' ');
630 }
631 if (item == NULL)
632 {
633 ga_append(&ga, NUL);
634 term->tl_command = ga.ga_data;
635 }
636 else
637 ga_clear(&ga);
638 }
639#endif
640
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100641 if (opt->jo_term_kill != NULL)
642 {
643 char_u *p = skiptowhite(opt->jo_term_kill);
644
645 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
646 }
647
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200648 if (opt->jo_term_api != NULL)
649 term->tl_api = vim_strsave(opt->jo_term_api);
650 else
651 term->tl_api = vim_strsave((char_u *)"Tapi_");
652
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100653 // System dependent: setup the vterm and maybe start the job in it.
Bram Moolenaar13568252018-03-16 20:46:58 +0100654 if (argv == NULL
655 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200656 && argvar->vval.v_string != NULL
657 && STRCMP(argvar->vval.v_string, "NONE") == 0)
658 res = create_pty_only(term, opt);
659 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200660 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200661
662 newbuf = curbuf;
663 if (res == OK)
664 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100665 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200666 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
667 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100668#ifdef FEAT_GUI
669 if (term->tl_system)
670 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100671 // display first line below typed command
Bram Moolenaar13568252018-03-16 20:46:58 +0100672 term->tl_toprow = msg_row + 1;
673 term->tl_dirty_row_end = 0;
674 }
675#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200676
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100677 // Make sure we don't get stuck on sending keys to the job, it leads to
678 // a deadlock if the job is waiting for Vim to read.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200679 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
680
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200681 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200682 {
683 --curbuf->b_nwindows;
684 curbuf = old_curbuf;
685 curwin->w_buffer = curbuf;
686 ++curbuf->b_nwindows;
687 }
688 }
689 else
690 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100691 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200692 return NULL;
693 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100694
Bram Moolenaar13568252018-03-16 20:46:58 +0100695 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar28ed4df2019-10-26 16:21:40 +0200696 if (!opt->jo_hidden && !(flags & TERM_START_SYSTEM))
697 apply_autocmds(EVENT_TERMINALWINOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200698 return newbuf;
699}
700
701/*
702 * ":terminal": open a terminal window and execute a job in it.
703 */
704 void
705ex_terminal(exarg_T *eap)
706{
707 typval_T argvar[2];
708 jobopt_T opt;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100709 int opt_shell = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200710 char_u *cmd;
711 char_u *tofree = NULL;
712
713 init_job_options(&opt);
714
715 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100716 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200717 {
718 char_u *p, *ep;
719
720 cmd += 2;
721 p = skiptowhite(cmd);
722 ep = vim_strchr(cmd, '=');
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200723 if (ep != NULL)
724 {
725 if (ep < p)
726 p = ep;
727 else
728 ep = NULL;
729 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200730
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200731# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
732 && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
733 if (OPTARG_HAS("close"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200734 opt.jo_term_finish = 'c';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200735 else if (OPTARG_HAS("noclose"))
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100736 opt.jo_term_finish = 'n';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200737 else if (OPTARG_HAS("open"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200738 opt.jo_term_finish = 'o';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200739 else if (OPTARG_HAS("curwin"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200740 opt.jo_curwin = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200741 else if (OPTARG_HAS("hidden"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200742 opt.jo_hidden = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200743 else if (OPTARG_HAS("norestore"))
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100744 opt.jo_term_norestore = 1;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100745 else if (OPTARG_HAS("shell"))
746 opt_shell = TRUE;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200747 else if (OPTARG_HAS("kill") && ep != NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100748 {
749 opt.jo_set2 |= JO2_TERM_KILL;
750 opt.jo_term_kill = ep + 1;
751 p = skiptowhite(cmd);
752 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200753 else if (OPTARG_HAS("api"))
754 {
755 opt.jo_set2 |= JO2_TERM_API;
756 if (ep != NULL)
757 {
758 opt.jo_term_api = ep + 1;
759 p = skiptowhite(cmd);
760 }
761 else
762 opt.jo_term_api = NULL;
763 }
764 else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200765 {
766 opt.jo_set2 |= JO2_TERM_ROWS;
767 opt.jo_term_rows = atoi((char *)ep + 1);
768 p = skiptowhite(cmd);
769 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200770 else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200771 {
772 opt.jo_set2 |= JO2_TERM_COLS;
773 opt.jo_term_cols = atoi((char *)ep + 1);
774 p = skiptowhite(cmd);
775 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200776 else if (OPTARG_HAS("eof") && ep != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200777 {
778 char_u *buf = NULL;
779 char_u *keys;
780
781 p = skiptowhite(cmd);
782 *p = NUL;
Bram Moolenaar459fd782019-10-13 16:43:39 +0200783 keys = replace_termcodes(ep + 1, &buf,
784 REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200785 opt.jo_set2 |= JO2_EOF_CHARS;
786 opt.jo_eof_chars = vim_strsave(keys);
787 vim_free(buf);
788 *p = ' ';
789 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100790#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100791 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "type", 4) == 0
792 && ep != NULL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100793 {
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100794 int tty_type = NUL;
795
796 p = skiptowhite(cmd);
797 if (STRNICMP(ep + 1, "winpty", p - (ep + 1)) == 0)
798 tty_type = 'w';
799 else if (STRNICMP(ep + 1, "conpty", p - (ep + 1)) == 0)
800 tty_type = 'c';
801 else
802 {
803 semsg(e_invargval, "type");
804 goto theend;
805 }
806 opt.jo_set2 |= JO2_TTY_TYPE;
807 opt.jo_tty_type = tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100808 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100809#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200810 else
811 {
812 if (*p)
813 *p = NUL;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100814 semsg(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100815 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200816 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200817# undef OPTARG_HAS
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200818 cmd = skipwhite(p);
819 }
820 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100821 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100822 // Make a copy of 'shell', an autocommand may change the option.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200823 tofree = cmd = vim_strsave(p_sh);
824
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100825 // default to close when the shell exits
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100826 if (opt.jo_term_finish == NUL)
827 opt.jo_term_finish = 'c';
828 }
829
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200830 if (eap->addr_count > 0)
831 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100832 // Write lines from current buffer to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200833 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
834 opt.jo_io[PART_IN] = JIO_BUFFER;
835 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
836 opt.jo_in_top = eap->line1;
837 opt.jo_in_bot = eap->line2;
838 }
839
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100840 if (opt_shell && tofree == NULL)
841 {
842#ifdef UNIX
843 char **argv = NULL;
844 char_u *tofree1 = NULL;
845 char_u *tofree2 = NULL;
846
847 // :term ++shell command
848 if (unix_build_argv(cmd, &argv, &tofree1, &tofree2) == OK)
849 term_start(NULL, argv, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaaradf4aa22019-11-10 22:36:44 +0100850 vim_free(argv);
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100851 vim_free(tofree1);
852 vim_free(tofree2);
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100853 goto theend;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100854#else
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100855# ifdef MSWIN
856 long_u cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
857 char_u *newcmd;
858
859 newcmd = alloc(cmdlen);
860 if (newcmd == NULL)
861 goto theend;
862 tofree = newcmd;
863 vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd);
864 cmd = newcmd;
865# else
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100866 emsg(_("E279: Sorry, ++shell is not supported on this system"));
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100867 goto theend;
868# endif
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100869#endif
870 }
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100871 argvar[0].v_type = VAR_STRING;
872 argvar[0].vval.v_string = cmd;
873 argvar[1].v_type = VAR_UNKNOWN;
874 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100875
876theend:
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100877 vim_free(tofree);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200878 vim_free(opt.jo_eof_chars);
879}
880
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100881#if defined(FEAT_SESSION) || defined(PROTO)
882/*
883 * Write a :terminal command to the session file to restore the terminal in
884 * window "wp".
885 * Return FAIL if writing fails.
886 */
887 int
888term_write_session(FILE *fd, win_T *wp)
889{
890 term_T *term = wp->w_buffer->b_term;
891
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100892 // Create the terminal and run the command. This is not without
893 // risk, but let's assume the user only creates a session when this
894 // will be OK.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100895 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
896 term->tl_cols, term->tl_rows) < 0)
897 return FAIL;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100898#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100899 if (fprintf(fd, "++type=%s ", term->tl_job->jv_tty_type) < 0)
900 return FAIL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100901#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100902 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
903 return FAIL;
904
905 return put_eol(fd);
906}
907
908/*
909 * Return TRUE if "buf" has a terminal that should be restored.
910 */
911 int
912term_should_restore(buf_T *buf)
913{
914 term_T *term = buf->b_term;
915
916 return term != NULL && (term->tl_command == NULL
917 || STRCMP(term->tl_command, "NONE") != 0);
918}
919#endif
920
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200921/*
922 * Free the scrollback buffer for "term".
923 */
924 static void
925free_scrollback(term_T *term)
926{
927 int i;
928
929 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
930 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
931 ga_clear(&term->tl_scrollback);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100932 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
933 vim_free(((sb_line_T *)term->tl_scrollback_postponed.ga_data + i)->sb_cells);
934 ga_clear(&term->tl_scrollback_postponed);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200935}
936
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100937
938// Terminals that need to be freed soon.
Bram Moolenaar840d16f2019-09-10 21:27:18 +0200939static term_T *terminals_to_free = NULL;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100940
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200941/*
942 * Free a terminal and everything it refers to.
943 * Kills the job if there is one.
944 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100945 * The actual terminal structure is freed later in free_unused_terminals(),
946 * because callbacks may wipe out a buffer while the terminal is still
947 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200948 */
949 void
950free_terminal(buf_T *buf)
951{
952 term_T *term = buf->b_term;
953 term_T *tp;
954
955 if (term == NULL)
956 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100957
958 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200959 if (first_term == term)
960 first_term = term->tl_next;
961 else
962 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
963 if (tp->tl_next == term)
964 {
965 tp->tl_next = term->tl_next;
966 break;
967 }
968
969 if (term->tl_job != NULL)
970 {
971 if (term->tl_job->jv_status != JOB_ENDED
972 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100973 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200974 job_stop(term->tl_job, NULL, "kill");
975 job_unref(term->tl_job);
976 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100977 term->tl_next = terminals_to_free;
978 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200979
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200980 buf->b_term = NULL;
981 if (in_terminal_loop == term)
982 in_terminal_loop = NULL;
983}
984
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100985 void
986free_unused_terminals()
987{
988 while (terminals_to_free != NULL)
989 {
990 term_T *term = terminals_to_free;
991
992 terminals_to_free = term->tl_next;
993
994 free_scrollback(term);
995
996 term_free_vterm(term);
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200997 vim_free(term->tl_api);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100998 vim_free(term->tl_title);
999#ifdef FEAT_SESSION
1000 vim_free(term->tl_command);
1001#endif
1002 vim_free(term->tl_kill);
1003 vim_free(term->tl_status_text);
1004 vim_free(term->tl_opencmd);
1005 vim_free(term->tl_eof_chars);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001006 vim_free(term->tl_arg0_cmd);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001007#ifdef MSWIN
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001008 if (term->tl_out_fd != NULL)
1009 fclose(term->tl_out_fd);
1010#endif
1011 vim_free(term->tl_cursor_color);
1012 vim_free(term);
1013 }
1014}
1015
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001016/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001017 * Get the part that is connected to the tty. Normally this is PART_IN, but
1018 * when writing buffer lines to the job it can be another. This makes it
1019 * possible to do "1,5term vim -".
1020 */
1021 static ch_part_T
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02001022get_tty_part(term_T *term UNUSED)
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001023{
1024#ifdef UNIX
1025 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
1026 int i;
1027
1028 for (i = 0; i < 3; ++i)
1029 {
1030 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
1031
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01001032 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001033 return parts[i];
1034 }
1035#endif
1036 return PART_IN;
1037}
1038
1039/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001040 * Write job output "msg[len]" to the vterm.
1041 */
1042 static void
1043term_write_job_output(term_T *term, char_u *msg, size_t len)
1044{
1045 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001046 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001047
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001048 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001049
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001050 // flush vterm buffer when vterm responded to control sequence
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001051 if (prevlen != vterm_output_get_buffer_current(vterm))
1052 {
1053 char buf[KEY_BUF_LEN];
1054 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
1055
1056 if (curlen > 0)
1057 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1058 (char_u *)buf, (int)curlen, NULL);
1059 }
1060
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001061 // this invokes the damage callbacks
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001062 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
1063}
1064
1065 static void
1066update_cursor(term_T *term, int redraw)
1067{
1068 if (term->tl_normal_mode)
1069 return;
Bram Moolenaar13568252018-03-16 20:46:58 +01001070#ifdef FEAT_GUI
1071 if (term->tl_system)
1072 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
1073 term->tl_cursor_pos.col);
1074 else
1075#endif
1076 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001077 if (redraw)
1078 {
1079 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
1080 cursor_on();
1081 out_flush();
1082#ifdef FEAT_GUI
1083 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001084 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001085 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001086 gui_mch_flush();
1087 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001088#endif
1089 }
1090}
1091
1092/*
1093 * Invoked when "msg" output from a job was received. Write it to the terminal
1094 * of "buffer".
1095 */
1096 void
1097write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1098{
1099 size_t len = STRLEN(msg);
1100 term_T *term = buffer->b_term;
1101
Bram Moolenaar4f974752019-02-17 17:44:42 +01001102#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001103 // Win32: Cannot redirect output of the job, intercept it here and write to
1104 // the file.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02001105 if (term->tl_out_fd != NULL)
1106 {
1107 ch_log(channel, "Writing %d bytes to output file", (int)len);
1108 fwrite(msg, len, 1, term->tl_out_fd);
1109 return;
1110 }
1111#endif
1112
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001113 if (term->tl_vterm == NULL)
1114 {
1115 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1116 return;
1117 }
1118 ch_log(channel, "writing %d bytes to terminal", (int)len);
1119 term_write_job_output(term, msg, len);
1120
Bram Moolenaar13568252018-03-16 20:46:58 +01001121#ifdef FEAT_GUI
1122 if (term->tl_system)
1123 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001124 // show system output, scrolling up the screen as needed
Bram Moolenaar13568252018-03-16 20:46:58 +01001125 update_system_term(term);
1126 update_cursor(term, TRUE);
1127 }
1128 else
1129#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001130 // In Terminal-Normal mode we are displaying the buffer, not the terminal
1131 // contents, thus no screen update is needed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001132 if (!term->tl_normal_mode)
1133 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001134 // Don't use update_screen() when editing the command line, it gets
1135 // cleared.
1136 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001137 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001138 if (buffer == curbuf && (State & CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001139 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001140 update_screen(VALID_NO_UPDATE);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001141 // update_screen() can be slow, check the terminal wasn't closed
1142 // already
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001143 if (buffer == curbuf && curbuf->b_term != NULL)
1144 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001145 }
1146 else
1147 redraw_after_callback(TRUE);
1148 }
1149}
1150
1151/*
1152 * Send a mouse position and click to the vterm
1153 */
1154 static int
1155term_send_mouse(VTerm *vterm, int button, int pressed)
1156{
1157 VTermModifier mod = VTERM_MOD_NONE;
1158
1159 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +02001160 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001161 if (button != 0)
1162 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001163 return TRUE;
1164}
1165
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001166static int enter_mouse_col = -1;
1167static int enter_mouse_row = -1;
1168
1169/*
1170 * Handle a mouse click, drag or release.
1171 * Return TRUE when a mouse event is sent to the terminal.
1172 */
1173 static int
1174term_mouse_click(VTerm *vterm, int key)
1175{
1176#if defined(FEAT_CLIPBOARD)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001177 // For modeless selection mouse drag and release events are ignored, unless
1178 // they are preceded with a mouse down event
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001179 static int ignore_drag_release = TRUE;
1180 VTermMouseState mouse_state;
1181
1182 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1183 if (mouse_state.flags == 0)
1184 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001185 // Terminal is not using the mouse, use modeless selection.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001186 switch (key)
1187 {
1188 case K_LEFTDRAG:
1189 case K_LEFTRELEASE:
1190 case K_RIGHTDRAG:
1191 case K_RIGHTRELEASE:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001192 // Ignore drag and release events when the button-down wasn't
1193 // seen before.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001194 if (ignore_drag_release)
1195 {
1196 int save_mouse_col, save_mouse_row;
1197
1198 if (enter_mouse_col < 0)
1199 break;
1200
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001201 // mouse click in the window gave us focus, handle that
1202 // click now
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001203 save_mouse_col = mouse_col;
1204 save_mouse_row = mouse_row;
1205 mouse_col = enter_mouse_col;
1206 mouse_row = enter_mouse_row;
1207 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1208 mouse_col = save_mouse_col;
1209 mouse_row = save_mouse_row;
1210 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001211 // FALLTHROUGH
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001212 case K_LEFTMOUSE:
1213 case K_RIGHTMOUSE:
1214 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1215 ignore_drag_release = TRUE;
1216 else
1217 ignore_drag_release = FALSE;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001218 // Should we call mouse_has() here?
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001219 if (clip_star.available)
1220 {
1221 int button, is_click, is_drag;
1222
1223 button = get_mouse_button(KEY2TERMCAP1(key),
1224 &is_click, &is_drag);
1225 if (mouse_model_popup() && button == MOUSE_LEFT
1226 && (mod_mask & MOD_MASK_SHIFT))
1227 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001228 // Translate shift-left to right button.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001229 button = MOUSE_RIGHT;
1230 mod_mask &= ~MOD_MASK_SHIFT;
1231 }
1232 clip_modeless(button, is_click, is_drag);
1233 }
1234 break;
1235
1236 case K_MIDDLEMOUSE:
1237 if (clip_star.available)
1238 insert_reg('*', TRUE);
1239 break;
1240 }
1241 enter_mouse_col = -1;
1242 return FALSE;
1243 }
1244#endif
1245 enter_mouse_col = -1;
1246
1247 switch (key)
1248 {
1249 case K_LEFTMOUSE:
1250 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1251 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1252 case K_LEFTRELEASE:
1253 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1254 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1255 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1256 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1257 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1258 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1259 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1260 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1261 }
1262 return TRUE;
1263}
1264
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001265/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001266 * Convert typed key "c" with modifiers "modmask" into bytes to send to the
1267 * job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001268 * Return the number of bytes in "buf".
1269 */
1270 static int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001271term_convert_key(term_T *term, int c, int modmask, char *buf)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001272{
1273 VTerm *vterm = term->tl_vterm;
1274 VTermKey key = VTERM_KEY_NONE;
1275 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001276 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001277
1278 switch (c)
1279 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001280 // don't use VTERM_KEY_ENTER, it may do an unwanted conversion
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001281
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001282 // don't use VTERM_KEY_BACKSPACE, it always
1283 // becomes 0x7f DEL
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001284 case K_BS: c = term_backspace_char; break;
1285
1286 case ESC: key = VTERM_KEY_ESCAPE; break;
1287 case K_DEL: key = VTERM_KEY_DEL; break;
1288 case K_DOWN: key = VTERM_KEY_DOWN; break;
1289 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1290 key = VTERM_KEY_DOWN; break;
1291 case K_END: key = VTERM_KEY_END; break;
1292 case K_S_END: mod = VTERM_MOD_SHIFT;
1293 key = VTERM_KEY_END; break;
1294 case K_C_END: mod = VTERM_MOD_CTRL;
1295 key = VTERM_KEY_END; break;
1296 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1297 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1298 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1299 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1300 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1301 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1302 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1303 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1304 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1305 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1306 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1307 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1308 case K_HOME: key = VTERM_KEY_HOME; break;
1309 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1310 key = VTERM_KEY_HOME; break;
1311 case K_C_HOME: mod = VTERM_MOD_CTRL;
1312 key = VTERM_KEY_HOME; break;
1313 case K_INS: key = VTERM_KEY_INS; break;
1314 case K_K0: key = VTERM_KEY_KP_0; break;
1315 case K_K1: key = VTERM_KEY_KP_1; break;
1316 case K_K2: key = VTERM_KEY_KP_2; break;
1317 case K_K3: key = VTERM_KEY_KP_3; break;
1318 case K_K4: key = VTERM_KEY_KP_4; break;
1319 case K_K5: key = VTERM_KEY_KP_5; break;
1320 case K_K6: key = VTERM_KEY_KP_6; break;
1321 case K_K7: key = VTERM_KEY_KP_7; break;
1322 case K_K8: key = VTERM_KEY_KP_8; break;
1323 case K_K9: key = VTERM_KEY_KP_9; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001324 case K_KDEL: key = VTERM_KEY_DEL; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001325 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001326 case K_KEND: key = VTERM_KEY_KP_1; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001327 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001328 case K_KHOME: key = VTERM_KEY_KP_7; break; // TODO
1329 case K_KINS: key = VTERM_KEY_KP_0; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001330 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1331 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001332 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; // TODO
1333 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001334 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1335 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1336 case K_LEFT: key = VTERM_KEY_LEFT; break;
1337 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1338 key = VTERM_KEY_LEFT; break;
1339 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1340 key = VTERM_KEY_LEFT; break;
1341 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1342 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1343 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1344 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1345 key = VTERM_KEY_RIGHT; break;
1346 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1347 key = VTERM_KEY_RIGHT; break;
1348 case K_UP: key = VTERM_KEY_UP; break;
1349 case K_S_UP: mod = VTERM_MOD_SHIFT;
1350 key = VTERM_KEY_UP; break;
1351 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001352 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1353 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001354
Bram Moolenaara42ad572017-11-16 13:08:04 +01001355 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1356 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001357 case K_MOUSELEFT: /* TODO */ return 0;
1358 case K_MOUSERIGHT: /* TODO */ return 0;
1359
1360 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001361 case K_LEFTMOUSE_NM:
1362 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001363 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001364 case K_LEFTRELEASE_NM:
1365 case K_MOUSEMOVE:
1366 case K_MIDDLEMOUSE:
1367 case K_MIDDLEDRAG:
1368 case K_MIDDLERELEASE:
1369 case K_RIGHTMOUSE:
1370 case K_RIGHTDRAG:
1371 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1372 return 0;
1373 other = TRUE;
1374 break;
1375
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001376 case K_X1MOUSE: /* TODO */ return 0;
1377 case K_X1DRAG: /* TODO */ return 0;
1378 case K_X1RELEASE: /* TODO */ return 0;
1379 case K_X2MOUSE: /* TODO */ return 0;
1380 case K_X2DRAG: /* TODO */ return 0;
1381 case K_X2RELEASE: /* TODO */ return 0;
1382
1383 case K_IGNORE: return 0;
1384 case K_NOP: return 0;
1385 case K_UNDO: return 0;
1386 case K_HELP: return 0;
1387 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1388 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1389 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1390 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1391 case K_SELECT: return 0;
1392#ifdef FEAT_GUI
1393 case K_VER_SCROLLBAR: return 0;
1394 case K_HOR_SCROLLBAR: return 0;
1395#endif
1396#ifdef FEAT_GUI_TABLINE
1397 case K_TABLINE: return 0;
1398 case K_TABMENU: return 0;
1399#endif
1400#ifdef FEAT_NETBEANS_INTG
1401 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1402#endif
1403#ifdef FEAT_DND
1404 case K_DROP: return 0;
1405#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001406 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001407 case K_PS: vterm_keyboard_start_paste(vterm);
1408 other = TRUE;
1409 break;
1410 case K_PE: vterm_keyboard_end_paste(vterm);
1411 other = TRUE;
1412 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001413 }
1414
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001415 // add modifiers for the typed key
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001416 if (modmask & MOD_MASK_SHIFT)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001417 mod |= VTERM_MOD_SHIFT;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001418 if (modmask & MOD_MASK_CTRL)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001419 mod |= VTERM_MOD_CTRL;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001420 if (modmask & (MOD_MASK_ALT | MOD_MASK_META))
Bram Moolenaar459fd782019-10-13 16:43:39 +02001421 mod |= VTERM_MOD_ALT;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001422
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001423 /*
1424 * Convert special keys to vterm keys:
1425 * - Write keys to vterm: vterm_keyboard_key()
1426 * - Write output to channel.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001427 */
1428 if (key != VTERM_KEY_NONE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001429 // Special key, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001430 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001431 else if (!other)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001432 // Normal character, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001433 vterm_keyboard_unichar(vterm, c, mod);
1434
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001435 // Read back the converted escape sequence.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001436 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1437}
1438
1439/*
1440 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001441 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001442 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001443 */
1444 static int
1445term_job_running_check(term_T *term, int check_job_status)
1446{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001447 // Also consider the job finished when the channel is closed, to avoid a
1448 // race condition when updating the title.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001449 if (term != NULL
1450 && term->tl_job != NULL
1451 && channel_is_open(term->tl_job->jv_channel))
1452 {
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001453 job_T *job = term->tl_job;
1454
1455 // Careful: Checking the job status may invoked callbacks, which close
1456 // the buffer and terminate "term". However, "job" will not be freed
1457 // yet.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001458 if (check_job_status)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001459 job_status(job);
1460 return (job->jv_status == JOB_STARTED
1461 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001462 }
1463 return FALSE;
1464}
1465
1466/*
1467 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001468 */
1469 int
1470term_job_running(term_T *term)
1471{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001472 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001473}
1474
1475/*
1476 * Return TRUE if "term" has an active channel and used ":term NONE".
1477 */
1478 int
1479term_none_open(term_T *term)
1480{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001481 // Also consider the job finished when the channel is closed, to avoid a
1482 // race condition when updating the title.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001483 return term != NULL
1484 && term->tl_job != NULL
1485 && channel_is_open(term->tl_job->jv_channel)
1486 && term->tl_job->jv_channel->ch_keep_open;
1487}
1488
1489/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001490 * Used when exiting: kill the job in "buf" if so desired.
1491 * Return OK when the job finished.
1492 * Return FAIL when the job is still running.
1493 */
1494 int
1495term_try_stop_job(buf_T *buf)
1496{
1497 int count;
1498 char *how = (char *)buf->b_term->tl_kill;
1499
1500#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1501 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1502 {
1503 char_u buff[DIALOG_MSG_SIZE];
1504 int ret;
1505
1506 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1507 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1508 if (ret == VIM_YES)
1509 how = "kill";
1510 else if (ret == VIM_CANCEL)
1511 return FAIL;
1512 }
1513#endif
1514 if (how == NULL || *how == NUL)
1515 return FAIL;
1516
1517 job_stop(buf->b_term->tl_job, NULL, how);
1518
Bram Moolenaar9172d232019-01-29 23:06:54 +01001519 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001520 for (count = 0; count < 100; ++count)
1521 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001522 job_T *job;
1523
1524 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001525 if (!buf_valid(buf)
1526 || buf->b_term == NULL
1527 || buf->b_term->tl_job == NULL)
1528 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001529 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001530
Bram Moolenaar9172d232019-01-29 23:06:54 +01001531 // Call job_status() to update jv_status. It may cause the job to be
1532 // cleaned up but it won't be freed.
1533 job_status(job);
1534 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001535 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001536
Bram Moolenaar8f7ab4b2019-10-23 23:16:45 +02001537 ui_delay(10L, TRUE);
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02001538 term_flush_messages();
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001539 }
1540 return FAIL;
1541}
1542
1543/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001544 * Add the last line of the scrollback buffer to the buffer in the window.
1545 */
1546 static void
1547add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1548{
1549 buf_T *buf = term->tl_buffer;
1550 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1551 linenr_T lnum = buf->b_ml.ml_line_count;
1552
Bram Moolenaar4f974752019-02-17 17:44:42 +01001553#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001554 if (!enc_utf8 && enc_codepage > 0)
1555 {
1556 WCHAR *ret = NULL;
1557 int length = 0;
1558
1559 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1560 &ret, &length);
1561 if (ret != NULL)
1562 {
1563 WideCharToMultiByte_alloc(enc_codepage, 0,
1564 ret, length, (char **)&text, &len, 0, 0);
1565 vim_free(ret);
1566 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1567 vim_free(text);
1568 }
1569 }
1570 else
1571#endif
1572 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1573 if (empty)
1574 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001575 // Delete the empty line that was in the empty buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001576 curbuf = buf;
1577 ml_delete(1, FALSE);
1578 curbuf = curwin->w_buffer;
1579 }
1580}
1581
1582 static void
1583cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1584{
1585 attr->width = cell->width;
1586 attr->attrs = cell->attrs;
1587 attr->fg = cell->fg;
1588 attr->bg = cell->bg;
1589}
1590
1591 static int
1592equal_celattr(cellattr_T *a, cellattr_T *b)
1593{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001594 // Comparing the colors should be sufficient.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001595 return a->fg.red == b->fg.red
1596 && a->fg.green == b->fg.green
1597 && a->fg.blue == b->fg.blue
1598 && a->bg.red == b->bg.red
1599 && a->bg.green == b->bg.green
1600 && a->bg.blue == b->bg.blue;
1601}
1602
Bram Moolenaard96ff162018-02-18 22:13:29 +01001603/*
1604 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1605 * line at this position. Otherwise at the end.
1606 */
1607 static int
1608add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1609{
1610 if (ga_grow(&term->tl_scrollback, 1) == OK)
1611 {
1612 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1613 + term->tl_scrollback.ga_len;
1614
1615 if (lnum > 0)
1616 {
1617 int i;
1618
1619 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1620 {
1621 *line = *(line - 1);
1622 --line;
1623 }
1624 }
1625 line->sb_cols = 0;
1626 line->sb_cells = NULL;
1627 line->sb_fill_attr = *fill_attr;
1628 ++term->tl_scrollback.ga_len;
1629 return OK;
1630 }
1631 return FALSE;
1632}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001633
1634/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001635 * Remove the terminal contents from the scrollback and the buffer.
1636 * Used before adding a new scrollback line or updating the buffer for lines
1637 * displayed in the terminal.
1638 */
1639 static void
1640cleanup_scrollback(term_T *term)
1641{
1642 sb_line_T *line;
1643 garray_T *gap;
1644
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001645 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001646 gap = &term->tl_scrollback;
1647 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1648 && gap->ga_len > 0)
1649 {
1650 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1651 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1652 vim_free(line->sb_cells);
1653 --gap->ga_len;
1654 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001655 curbuf = curwin->w_buffer;
1656 if (curbuf == term->tl_buffer)
1657 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001658}
1659
1660/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001661 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001662 */
1663 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001664update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001665{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001666 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001667 int len;
1668 int lines_skipped = 0;
1669 VTermPos pos;
1670 VTermScreenCell cell;
1671 cellattr_T fill_attr, new_fill_attr;
1672 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001673
1674 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1675 "Adding terminal window snapshot to buffer");
1676
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001677 // First remove the lines that were appended before, they might be
1678 // outdated.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001679 cleanup_scrollback(term);
1680
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001681 screen = vterm_obtain_screen(term->tl_vterm);
1682 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001683 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1684 {
1685 len = 0;
1686 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1687 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1688 && cell.chars[0] != NUL)
1689 {
1690 len = pos.col + 1;
1691 new_fill_attr = term->tl_default_color;
1692 }
1693 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001694 // Assume the last attr is the filler attr.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001695 cell2cellattr(&cell, &new_fill_attr);
1696
1697 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1698 ++lines_skipped;
1699 else
1700 {
1701 while (lines_skipped > 0)
1702 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001703 // Line was skipped, add an empty line.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001704 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001705 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001706 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001707 }
1708
1709 if (len == 0)
1710 p = NULL;
1711 else
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001712 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001713 if ((p != NULL || len == 0)
1714 && ga_grow(&term->tl_scrollback, 1) == OK)
1715 {
1716 garray_T ga;
1717 int width;
1718 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1719 + term->tl_scrollback.ga_len;
1720
1721 ga_init2(&ga, 1, 100);
1722 for (pos.col = 0; pos.col < len; pos.col += width)
1723 {
1724 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1725 {
1726 width = 1;
1727 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1728 if (ga_grow(&ga, 1) == OK)
1729 ga.ga_len += utf_char2bytes(' ',
1730 (char_u *)ga.ga_data + ga.ga_len);
1731 }
1732 else
1733 {
1734 width = cell.width;
1735
1736 cell2cellattr(&cell, &p[pos.col]);
1737
Bram Moolenaara79fd562018-12-20 20:47:32 +01001738 // Each character can be up to 6 bytes.
1739 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001740 {
1741 int i;
1742 int c;
1743
1744 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1745 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1746 (char_u *)ga.ga_data + ga.ga_len);
1747 }
1748 }
1749 }
1750 line->sb_cols = len;
1751 line->sb_cells = p;
1752 line->sb_fill_attr = new_fill_attr;
1753 fill_attr = new_fill_attr;
1754 ++term->tl_scrollback.ga_len;
1755
1756 if (ga_grow(&ga, 1) == FAIL)
1757 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1758 else
1759 {
1760 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1761 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1762 }
1763 ga_clear(&ga);
1764 }
1765 else
1766 vim_free(p);
1767 }
1768 }
1769
Bram Moolenaarf3aea592018-11-11 22:18:21 +01001770 // Add trailing empty lines.
1771 for (pos.row = term->tl_scrollback.ga_len;
1772 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1773 ++pos.row)
1774 {
1775 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1776 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1777 }
1778
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001779 term->tl_dirty_snapshot = FALSE;
1780#ifdef FEAT_TIMERS
1781 term->tl_timer_set = FALSE;
1782#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001783}
1784
1785/*
1786 * If needed, add the current lines of the terminal to scrollback and to the
1787 * buffer. Called after the job has ended and when switching to
1788 * Terminal-Normal mode.
1789 * When "redraw" is TRUE redraw the windows that show the terminal.
1790 */
1791 static void
1792may_move_terminal_to_buffer(term_T *term, int redraw)
1793{
1794 win_T *wp;
1795
1796 if (term->tl_vterm == NULL)
1797 return;
1798
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001799 // Update the snapshot only if something changes or the buffer does not
1800 // have all the lines.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001801 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
1802 <= term->tl_scrollback_scrolled)
1803 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001804
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001805 // Obtain the current background color.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001806 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1807 &term->tl_default_color.fg, &term->tl_default_color.bg);
1808
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001809 if (redraw)
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001810 FOR_ALL_WINDOWS(wp)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001811 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001812 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001813 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001814 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1815 wp->w_cursor.col = 0;
1816 wp->w_valid = 0;
1817 if (wp->w_cursor.lnum >= wp->w_height)
1818 {
1819 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001820
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001821 if (wp->w_topline < min_topline)
1822 wp->w_topline = min_topline;
1823 }
1824 redraw_win_later(wp, NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001825 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001826 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001827}
1828
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001829#if defined(FEAT_TIMERS) || defined(PROTO)
1830/*
1831 * Check if any terminal timer expired. If so, copy text from the terminal to
1832 * the buffer.
1833 * Return the time until the next timer will expire.
1834 */
1835 int
1836term_check_timers(int next_due_arg, proftime_T *now)
1837{
1838 term_T *term;
1839 int next_due = next_due_arg;
1840
1841 for (term = first_term; term != NULL; term = term->tl_next)
1842 {
1843 if (term->tl_timer_set && !term->tl_normal_mode)
1844 {
1845 long this_due = proftime_time_left(&term->tl_timer_due, now);
1846
1847 if (this_due <= 1)
1848 {
1849 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001850 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001851 }
1852 else if (next_due == -1 || next_due > this_due)
1853 next_due = this_due;
1854 }
1855 }
1856
1857 return next_due;
1858}
1859#endif
1860
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001861/*
1862 * When "normal_mode" is TRUE set the terminal to Terminal-Normal mode,
1863 * otherwise end it.
1864 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001865 static void
1866set_terminal_mode(term_T *term, int normal_mode)
1867{
1868 term->tl_normal_mode = normal_mode;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001869 if (!normal_mode)
1870 handle_postponed_scrollback(term);
Bram Moolenaard23a8232018-02-10 18:45:26 +01001871 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001872 if (term->tl_buffer == curbuf)
1873 maketitle();
1874}
1875
1876/*
1877 * Called after the job if finished and Terminal mode is not active:
1878 * Move the vterm contents into the scrollback buffer and free the vterm.
1879 */
1880 static void
1881cleanup_vterm(term_T *term)
1882{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001883 set_terminal_mode(term, FALSE);
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001884 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001885 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001886 term_free_vterm(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001887}
1888
1889/*
1890 * Switch from Terminal-Job mode to Terminal-Normal mode.
1891 * Suspends updating the terminal window.
1892 */
1893 static void
1894term_enter_normal_mode(void)
1895{
1896 term_T *term = curbuf->b_term;
1897
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001898 set_terminal_mode(term, TRUE);
1899
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001900 // Append the current terminal contents to the buffer.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001901 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001902
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001903 // Move the window cursor to the position of the cursor in the
1904 // terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001905 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1906 + term->tl_cursor_pos.row + 1;
1907 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02001908 if (coladvance(term->tl_cursor_pos.col) == FAIL)
1909 coladvance(MAXCOL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001910
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001911 // Display the same lines as in the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001912 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1913}
1914
1915/*
1916 * Returns TRUE if the current window contains a terminal and we are in
1917 * Terminal-Normal mode.
1918 */
1919 int
1920term_in_normal_mode(void)
1921{
1922 term_T *term = curbuf->b_term;
1923
1924 return term != NULL && term->tl_normal_mode;
1925}
1926
1927/*
1928 * Switch from Terminal-Normal mode to Terminal-Job mode.
1929 * Restores updating the terminal window.
1930 */
1931 void
1932term_enter_job_mode()
1933{
1934 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001935
1936 set_terminal_mode(term, FALSE);
1937
1938 if (term->tl_channel_closed)
1939 cleanup_vterm(term);
1940 redraw_buf_and_status_later(curbuf, NOT_VALID);
1941}
1942
1943/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001944 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001945 * Note: while waiting a terminal may be closed and freed if the channel is
1946 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001947 */
1948 static int
1949term_vgetc()
1950{
1951 int c;
1952 int save_State = State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001953 int modify_other_keys =
1954 vterm_is_modify_other_keys(curbuf->b_term->tl_vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001955
1956 State = TERMINAL;
1957 got_int = FALSE;
Bram Moolenaar4f974752019-02-17 17:44:42 +01001958#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001959 ctrl_break_was_pressed = FALSE;
1960#endif
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001961 if (modify_other_keys)
1962 ++no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001963 c = vgetc();
1964 got_int = FALSE;
1965 State = save_State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001966 if (modify_other_keys)
1967 --no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001968 return c;
1969}
1970
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001971static int mouse_was_outside = FALSE;
1972
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001973/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001974 * Send key "c" with modifiers "modmask" to terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001975 * Return FAIL when the key needs to be handled in Normal mode.
1976 * Return OK when the key was dropped or sent to the terminal.
1977 */
1978 int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001979send_keys_to_term(term_T *term, int c, int modmask, int typed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001980{
1981 char msg[KEY_BUF_LEN];
1982 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001983 int dragging_outside = FALSE;
1984
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001985 // Catch keys that need to be handled as in Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001986 switch (c)
1987 {
1988 case NUL:
1989 case K_ZERO:
1990 if (typed)
1991 stuffcharReadbuff(c);
1992 return FAIL;
1993
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001994 case K_TABLINE:
1995 stuffcharReadbuff(c);
1996 return FAIL;
1997
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001998 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02001999 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002000 return FAIL;
2001
2002 case K_LEFTDRAG:
2003 case K_MIDDLEDRAG:
2004 case K_RIGHTDRAG:
2005 case K_X1DRAG:
2006 case K_X2DRAG:
2007 dragging_outside = mouse_was_outside;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002008 // FALLTHROUGH
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002009 case K_LEFTMOUSE:
2010 case K_LEFTMOUSE_NM:
2011 case K_LEFTRELEASE:
2012 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01002013 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002014 case K_MIDDLEMOUSE:
2015 case K_MIDDLERELEASE:
2016 case K_RIGHTMOUSE:
2017 case K_RIGHTRELEASE:
2018 case K_X1MOUSE:
2019 case K_X1RELEASE:
2020 case K_X2MOUSE:
2021 case K_X2RELEASE:
2022
2023 case K_MOUSEUP:
2024 case K_MOUSEDOWN:
2025 case K_MOUSELEFT:
2026 case K_MOUSERIGHT:
2027 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01002028 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02002029 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01002030 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002031 || dragging_outside)
2032 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002033 // click or scroll outside the current window or on status line
2034 // or vertical separator
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002035 if (typed)
2036 {
2037 stuffcharReadbuff(c);
2038 mouse_was_outside = TRUE;
2039 }
2040 return FAIL;
2041 }
2042 }
2043 if (typed)
2044 mouse_was_outside = FALSE;
2045
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002046 // Convert the typed key to a sequence of bytes for the job.
2047 len = term_convert_key(term, c, modmask, msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002048 if (len > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002049 // TODO: if FAIL is returned, stop?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002050 channel_send(term->tl_job->jv_channel, get_tty_part(term),
2051 (char_u *)msg, (int)len, NULL);
2052
2053 return OK;
2054}
2055
2056 static void
2057position_cursor(win_T *wp, VTermPos *pos)
2058{
2059 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
2060 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
2061 wp->w_valid |= (VALID_WCOL|VALID_WROW);
2062}
2063
2064/*
2065 * Handle CTRL-W "": send register contents to the job.
2066 */
2067 static void
2068term_paste_register(int prev_c UNUSED)
2069{
2070 int c;
2071 list_T *l;
2072 listitem_T *item;
2073 long reglen = 0;
2074 int type;
2075
2076#ifdef FEAT_CMDL_INFO
2077 if (add_to_showcmd(prev_c))
2078 if (add_to_showcmd('"'))
2079 out_flush();
2080#endif
2081 c = term_vgetc();
2082#ifdef FEAT_CMDL_INFO
2083 clear_showcmd();
2084#endif
2085 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002086 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002087 return;
2088
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002089 // CTRL-W "= prompt for expression to evaluate.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002090 if (c == '=' && get_expr_register() != '=')
2091 return;
2092 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002093 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002094 return;
2095
2096 l = (list_T *)get_reg_contents(c, GREG_LIST);
2097 if (l != NULL)
2098 {
2099 type = get_reg_type(c, &reglen);
2100 for (item = l->lv_first; item != NULL; item = item->li_next)
2101 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01002102 char_u *s = tv_get_string(&item->li_tv);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002103#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002104 char_u *tmp = s;
2105
2106 if (!enc_utf8 && enc_codepage > 0)
2107 {
2108 WCHAR *ret = NULL;
2109 int length = 0;
2110
2111 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2112 (int)STRLEN(s), &ret, &length);
2113 if (ret != NULL)
2114 {
2115 WideCharToMultiByte_alloc(CP_UTF8, 0,
2116 ret, length, (char **)&s, &length, 0, 0);
2117 vim_free(ret);
2118 }
2119 }
2120#endif
2121 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2122 s, (int)STRLEN(s), NULL);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002123#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002124 if (tmp != s)
2125 vim_free(s);
2126#endif
2127
2128 if (item->li_next != NULL || type == MLINE)
2129 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2130 (char_u *)"\r", 1, NULL);
2131 }
2132 list_free(l);
2133 }
2134}
2135
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002136/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002137 * Return TRUE when waiting for a character in the terminal, the cursor of the
2138 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002139 */
2140 int
2141terminal_is_active()
2142{
2143 return in_terminal_loop != NULL;
2144}
2145
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002146#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002147 cursorentry_T *
2148term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2149{
2150 term_T *term = in_terminal_loop;
2151 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002152 int id;
2153 guicolor_T term_fg, term_bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002154
2155 vim_memset(&entry, 0, sizeof(entry));
2156 entry.shape = entry.mshape =
2157 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2158 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2159 SHAPE_BLOCK;
2160 entry.percentage = 20;
2161 if (term->tl_cursor_blink)
2162 {
2163 entry.blinkwait = 700;
2164 entry.blinkon = 400;
2165 entry.blinkoff = 250;
2166 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002167
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002168 // The "Terminal" highlight group overrules the defaults.
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002169 id = syn_name2id((char_u *)"Terminal");
2170 if (id != 0)
2171 {
2172 syn_id2colors(id, &term_fg, &term_bg);
2173 *fg = term_bg;
2174 }
2175 else
2176 *fg = gui.back_pixel;
2177
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002178 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002179 {
2180 if (id != 0)
2181 *bg = term_fg;
2182 else
2183 *bg = gui.norm_pixel;
2184 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002185 else
2186 *bg = color_name2handle(term->tl_cursor_color);
2187 entry.name = "n";
2188 entry.used_for = SHAPE_CURSOR;
2189
2190 return &entry;
2191}
2192#endif
2193
Bram Moolenaard317b382018-02-08 22:33:31 +01002194 static void
2195may_output_cursor_props(void)
2196{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002197 if (!cursor_color_equal(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)
2200 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002201 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002202 last_set_cursor_shape = desired_cursor_shape;
2203 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002204 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002205 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002206 // this will restore the initial cursor style, if possible
Bram Moolenaard317b382018-02-08 22:33:31 +01002207 ui_cursor_shape_forced(TRUE);
2208 else
2209 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2210 }
2211}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002212
Bram Moolenaard317b382018-02-08 22:33:31 +01002213/*
2214 * Set the cursor color and shape, if not last set to these.
2215 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002216 static void
2217may_set_cursor_props(term_T *term)
2218{
2219#ifdef FEAT_GUI
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002220 // For the GUI the cursor properties are obtained with
2221 // term_get_cursor_shape().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002222 if (gui.in_use)
2223 return;
2224#endif
2225 if (in_terminal_loop == term)
2226 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002227 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002228 desired_cursor_shape = term->tl_cursor_shape;
2229 desired_cursor_blink = term->tl_cursor_blink;
2230 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002231 }
2232}
2233
Bram Moolenaard317b382018-02-08 22:33:31 +01002234/*
2235 * Reset the desired cursor properties and restore them when needed.
2236 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002237 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002238prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002239{
2240#ifdef FEAT_GUI
2241 if (gui.in_use)
2242 return;
2243#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002244 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002245 desired_cursor_shape = -1;
2246 desired_cursor_blink = -1;
2247 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002248}
2249
2250/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002251 * Returns TRUE if the current window contains a terminal and we are sending
2252 * keys to the job.
2253 * If "check_job_status" is TRUE update the job status.
2254 */
2255 static int
2256term_use_loop_check(int check_job_status)
2257{
2258 term_T *term = curbuf->b_term;
2259
2260 return term != NULL
2261 && !term->tl_normal_mode
2262 && term->tl_vterm != NULL
2263 && term_job_running_check(term, check_job_status);
2264}
2265
2266/*
2267 * Returns TRUE if the current window contains a terminal and we are sending
2268 * keys to the job.
2269 */
2270 int
2271term_use_loop(void)
2272{
2273 return term_use_loop_check(FALSE);
2274}
2275
2276/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002277 * Called when entering a window with the mouse. If this is a terminal window
2278 * we may want to change state.
2279 */
2280 void
2281term_win_entered()
2282{
2283 term_T *term = curbuf->b_term;
2284
2285 if (term != NULL)
2286 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002287 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002288 {
2289 reset_VIsual_and_resel();
2290 if (State & INSERT)
2291 stop_insert_mode = TRUE;
2292 }
2293 mouse_was_outside = FALSE;
2294 enter_mouse_col = mouse_col;
2295 enter_mouse_row = mouse_row;
2296 }
2297}
2298
2299/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002300 * vgetc() may not include CTRL in the key when modify_other_keys is set.
2301 * Return the Ctrl-key value in that case.
2302 */
2303 static int
2304raw_c_to_ctrl(int c)
2305{
2306 if ((mod_mask & MOD_MASK_CTRL)
2307 && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')))
2308 return c & 0x1f;
2309 return c;
2310}
2311
2312/*
2313 * When modify_other_keys is set then do the reverse of raw_c_to_ctrl().
2314 * May set "mod_mask".
2315 */
2316 static int
2317ctrl_to_raw_c(int c)
2318{
2319 if (c < 0x20 && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
2320 {
2321 mod_mask |= MOD_MASK_CTRL;
2322 return c + '@';
2323 }
2324 return c;
2325}
2326
2327/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002328 * Wait for input and send it to the job.
2329 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2330 * when there is no more typahead.
2331 * Return when the start of a CTRL-W command is typed or anything else that
2332 * should be handled as a Normal mode command.
2333 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2334 * the terminal was closed.
2335 */
2336 int
2337terminal_loop(int blocking)
2338{
2339 int c;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002340 int raw_c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002341 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002342 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002343#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002344 int tty_fd = curbuf->b_term->tl_job->jv_channel
2345 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002346#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002347 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002348
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002349 // Remember the terminal we are sending keys to. However, the terminal
2350 // might be closed while waiting for a character, e.g. typing "exit" in a
2351 // shell and ++close was used. Therefore use curbuf->b_term instead of a
2352 // stored reference.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002353 in_terminal_loop = curbuf->b_term;
2354
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002355 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002356 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002357 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002358 if (termwinkey == Ctrl_W)
2359 termwinkey = 0;
2360 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002361 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2362 may_set_cursor_props(curbuf->b_term);
2363
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002364 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002365 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002366#ifdef FEAT_GUI
2367 if (!curbuf->b_term->tl_system)
2368#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002369 // TODO: skip screen update when handling a sequence of keys.
2370 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002371 while (must_redraw != 0)
2372 if (update_screen(0) == FAIL)
2373 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002374 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002375 // job finished while redrawing
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002376 break;
2377
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002378 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002379 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002380
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002381 raw_c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002382 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002383 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002384 // Job finished while waiting for a character. Push back the
2385 // received character.
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002386 if (raw_c != K_IGNORE)
2387 vungetc(raw_c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002388 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002389 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002390 if (raw_c == K_IGNORE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002391 continue;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002392 c = raw_c_to_ctrl(raw_c);
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002393
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002394#ifdef UNIX
2395 /*
2396 * The shell or another program may change the tty settings. Getting
2397 * them for every typed character is a bit of overhead, but it's needed
2398 * for the first character typed, e.g. when Vim starts in a shell.
2399 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002400 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002401 {
2402 ttyinfo_T info;
2403
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002404 // Get the current backspace character of the pty.
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002405 if (get_tty_info(tty_fd, &info) == OK)
2406 term_backspace_char = info.backspace;
2407 }
2408#endif
2409
Bram Moolenaar4f974752019-02-17 17:44:42 +01002410#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002411 // On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2412 // Use CTRL-BREAK to kill the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002413 if (ctrl_break_was_pressed)
2414 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2415#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002416 // Was either CTRL-W (termwinkey) or CTRL-\ pressed?
2417 // Not in a system terminal.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002418 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002419#ifdef FEAT_GUI
2420 && !curbuf->b_term->tl_system
2421#endif
2422 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002423 {
2424 int prev_c = c;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002425 int prev_raw_c = raw_c;
2426 int prev_mod_mask = mod_mask;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002427
2428#ifdef FEAT_CMDL_INFO
2429 if (add_to_showcmd(c))
2430 out_flush();
2431#endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002432 raw_c = term_vgetc();
2433 c = raw_c_to_ctrl(raw_c);
2434
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002435#ifdef FEAT_CMDL_INFO
2436 clear_showcmd();
2437#endif
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002438 if (!term_use_loop_check(TRUE)
2439 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002440 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002441 break;
2442
2443 if (prev_c == Ctrl_BSL)
2444 {
2445 if (c == Ctrl_N)
2446 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002447 // CTRL-\ CTRL-N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002448 term_enter_normal_mode();
2449 ret = FAIL;
2450 goto theend;
2451 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002452 // Send both keys to the terminal, first one here, second one
2453 // below.
2454 send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask,
2455 TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002456 }
2457 else if (c == Ctrl_C)
2458 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002459 // "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002460 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2461 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002462 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002463 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002464 // "CTRL-W .": send CTRL-W to the job
2465 // "'termwinkey' .": send 'termwinkey' to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002466 raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002467 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002468 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002469 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002470 // "CTRL-W CTRL-\": send CTRL-\ to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002471 raw_c = ctrl_to_raw_c(Ctrl_BSL);
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002472 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002473 else if (c == 'N')
2474 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002475 // CTRL-W N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002476 term_enter_normal_mode();
2477 ret = FAIL;
2478 goto theend;
2479 }
2480 else if (c == '"')
2481 {
2482 term_paste_register(prev_c);
2483 continue;
2484 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002485 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002486 {
Bram Moolenaara4b26992019-08-15 20:58:54 +02002487 char_u buf[MB_MAXBYTES + 2];
2488
2489 // Put the command into the typeahead buffer, when using the
2490 // stuff buffer KeyStuffed is set and 'langmap' won't be used.
2491 buf[0] = Ctrl_W;
2492 buf[(*mb_char2bytes)(c, buf + 1) + 1] = NUL;
2493 ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002494 ret = OK;
2495 goto theend;
2496 }
2497 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01002498# ifdef MSWIN
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002499 if (!enc_utf8 && has_mbyte && raw_c >= 0x80)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002500 {
2501 WCHAR wc;
2502 char_u mb[3];
2503
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002504 mb[0] = (unsigned)raw_c >> 8;
2505 mb[1] = raw_c;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002506 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002507 raw_c = wc;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002508 }
2509# endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002510 if (send_keys_to_term(curbuf->b_term, raw_c, mod_mask, TRUE) != OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002511 {
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002512 if (raw_c == K_MOUSEMOVE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002513 // We are sure to come back here, don't reset the cursor color
2514 // and shape to avoid flickering.
Bram Moolenaard317b382018-02-08 22:33:31 +01002515 restore_cursor = FALSE;
2516
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002517 ret = OK;
2518 goto theend;
2519 }
2520 }
2521 ret = FAIL;
2522
2523theend:
2524 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002525 if (restore_cursor)
2526 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002527
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002528 // Move a snapshot of the screen contents to the buffer, so that completion
2529 // works in other buffers.
Bram Moolenaar620020e2018-05-13 19:06:12 +02002530 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2531 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002532
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002533 return ret;
2534}
2535
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002536 static void
2537may_toggle_cursor(term_T *term)
2538{
2539 if (in_terminal_loop == term)
2540 {
2541 if (term->tl_cursor_visible)
2542 cursor_on();
2543 else
2544 cursor_off();
2545 }
2546}
2547
2548/*
2549 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002550 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002551 */
2552 static int
2553color2index(VTermColor *color, int fg, int *boldp)
2554{
2555 int red = color->red;
2556 int blue = color->blue;
2557 int green = color->green;
2558
Bram Moolenaar46359e12017-11-29 22:33:38 +01002559 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002560 {
Bram Moolenaar1d79ce82019-04-12 22:27:39 +02002561 // The first 16 colors and default: use the ANSI index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002562 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002563 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002564 case 0: return 0;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002565 case 1: return lookup_color( 0, fg, boldp) + 1; // black
2566 case 2: return lookup_color( 4, fg, boldp) + 1; // dark red
2567 case 3: return lookup_color( 2, fg, boldp) + 1; // dark green
2568 case 4: return lookup_color( 6, fg, boldp) + 1; // brown
2569 case 5: return lookup_color( 1, fg, boldp) + 1; // dark blue
2570 case 6: return lookup_color( 5, fg, boldp) + 1; // dark magenta
2571 case 7: return lookup_color( 3, fg, boldp) + 1; // dark cyan
2572 case 8: return lookup_color( 8, fg, boldp) + 1; // light grey
2573 case 9: return lookup_color(12, fg, boldp) + 1; // dark grey
2574 case 10: return lookup_color(20, fg, boldp) + 1; // red
2575 case 11: return lookup_color(16, fg, boldp) + 1; // green
2576 case 12: return lookup_color(24, fg, boldp) + 1; // yellow
2577 case 13: return lookup_color(14, fg, boldp) + 1; // blue
2578 case 14: return lookup_color(22, fg, boldp) + 1; // magenta
2579 case 15: return lookup_color(18, fg, boldp) + 1; // cyan
2580 case 16: return lookup_color(26, fg, boldp) + 1; // white
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002581 }
2582 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002583
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002584 if (t_colors >= 256)
2585 {
2586 if (red == blue && red == green)
2587 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002588 // 24-color greyscale plus white and black
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002589 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002590 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2591 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2592 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002593 int i;
2594
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002595 if (red < 5)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002596 return 17; // 00/00/00
2597 if (red > 245) // ff/ff/ff
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002598 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002599 for (i = 0; i < 23; ++i)
2600 if (red < cutoff[i])
2601 return i + 233;
2602 return 256;
2603 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002604 {
2605 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2606 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002607
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002608 // 216-color cube
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002609 for (ri = 0; ri < 5; ++ri)
2610 if (red < cutoff[ri])
2611 break;
2612 for (gi = 0; gi < 5; ++gi)
2613 if (green < cutoff[gi])
2614 break;
2615 for (bi = 0; bi < 5; ++bi)
2616 if (blue < cutoff[bi])
2617 break;
2618 return 17 + ri * 36 + gi * 6 + bi;
2619 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002620 }
2621 return 0;
2622}
2623
2624/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002625 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002626 */
2627 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002628vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002629{
2630 int attr = 0;
2631
2632 if (cellattrs.bold)
2633 attr |= HL_BOLD;
2634 if (cellattrs.underline)
2635 attr |= HL_UNDERLINE;
2636 if (cellattrs.italic)
2637 attr |= HL_ITALIC;
2638 if (cellattrs.strike)
2639 attr |= HL_STRIKETHROUGH;
2640 if (cellattrs.reverse)
2641 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002642 return attr;
2643}
2644
2645/*
2646 * Store Vterm attributes in "cell" from highlight flags.
2647 */
2648 static void
2649hl2vtermAttr(int attr, cellattr_T *cell)
2650{
2651 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2652 if (attr & HL_BOLD)
2653 cell->attrs.bold = 1;
2654 if (attr & HL_UNDERLINE)
2655 cell->attrs.underline = 1;
2656 if (attr & HL_ITALIC)
2657 cell->attrs.italic = 1;
2658 if (attr & HL_STRIKETHROUGH)
2659 cell->attrs.strike = 1;
2660 if (attr & HL_INVERSE)
2661 cell->attrs.reverse = 1;
2662}
2663
2664/*
2665 * Convert the attributes of a vterm cell into an attribute index.
2666 */
2667 static int
2668cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2669{
2670 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002671
2672#ifdef FEAT_GUI
2673 if (gui.in_use)
2674 {
2675 guicolor_T fg, bg;
2676
2677 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2678 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2679 return get_gui_attr_idx(attr, fg, bg);
2680 }
2681 else
2682#endif
2683#ifdef FEAT_TERMGUICOLORS
2684 if (p_tgc)
2685 {
2686 guicolor_T fg, bg;
2687
2688 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2689 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2690
2691 return get_tgc_attr_idx(attr, fg, bg);
2692 }
2693 else
2694#endif
2695 {
2696 int bold = MAYBE;
2697 int fg = color2index(&cellfg, TRUE, &bold);
2698 int bg = color2index(&cellbg, FALSE, &bold);
2699
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002700 // Use the "Terminal" highlighting for the default colors.
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002701 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002702 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002703 if (fg == 0 && term_default_cterm_fg >= 0)
2704 fg = term_default_cterm_fg + 1;
2705 if (bg == 0 && term_default_cterm_bg >= 0)
2706 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002707 }
2708
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002709 // with 8 colors set the bold attribute to get a bright foreground
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002710 if (bold == TRUE)
2711 attr |= HL_BOLD;
2712 return get_cterm_attr_idx(attr, fg, bg);
2713 }
2714 return 0;
2715}
2716
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002717 static void
2718set_dirty_snapshot(term_T *term)
2719{
2720 term->tl_dirty_snapshot = TRUE;
2721#ifdef FEAT_TIMERS
2722 if (!term->tl_normal_mode)
2723 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002724 // Update the snapshot after 100 msec of not getting updates.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002725 profile_setlimit(100L, &term->tl_timer_due);
2726 term->tl_timer_set = TRUE;
2727 }
2728#endif
2729}
2730
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002731 static int
2732handle_damage(VTermRect rect, void *user)
2733{
2734 term_T *term = (term_T *)user;
2735
2736 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2737 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002738 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002739 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002740 return 1;
2741}
2742
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002743 static void
2744term_scroll_up(term_T *term, int start_row, int count)
2745{
2746 win_T *wp;
2747 VTermColor fg, bg;
2748 VTermScreenCellAttrs attr;
2749 int clear_attr;
2750
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002751 // Set the color to clear lines with.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002752 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2753 &fg, &bg);
2754 vim_memset(&attr, 0, sizeof(attr));
2755 clear_attr = cell2attr(attr, fg, bg);
2756
2757 FOR_ALL_WINDOWS(wp)
2758 {
2759 if (wp->w_buffer == term->tl_buffer)
2760 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2761 }
2762}
2763
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002764 static int
2765handle_moverect(VTermRect dest, VTermRect src, void *user)
2766{
2767 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002768 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002769
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002770 // Scrolling up is done much more efficiently by deleting lines instead of
2771 // redrawing the text. But avoid doing this multiple times, postpone until
2772 // the redraw happens.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002773 if (dest.start_col == src.start_col
2774 && dest.end_col == src.end_col
2775 && dest.start_row < src.start_row)
2776 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002777 if (dest.start_row == 0)
2778 term->tl_postponed_scroll += count;
2779 else
2780 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002781 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002782
2783 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2784 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002785 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002786
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002787 // Note sure if the scrolling will work correctly, let's do a complete
2788 // redraw later.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002789 redraw_buf_later(term->tl_buffer, NOT_VALID);
2790 return 1;
2791}
2792
2793 static int
2794handle_movecursor(
2795 VTermPos pos,
2796 VTermPos oldpos UNUSED,
2797 int visible,
2798 void *user)
2799{
2800 term_T *term = (term_T *)user;
2801 win_T *wp;
2802
2803 term->tl_cursor_pos = pos;
2804 term->tl_cursor_visible = visible;
2805
2806 FOR_ALL_WINDOWS(wp)
2807 {
2808 if (wp->w_buffer == term->tl_buffer)
2809 position_cursor(wp, &pos);
2810 }
2811 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2812 {
2813 may_toggle_cursor(term);
2814 update_cursor(term, term->tl_cursor_visible);
2815 }
2816
2817 return 1;
2818}
2819
2820 static int
2821handle_settermprop(
2822 VTermProp prop,
2823 VTermValue *value,
2824 void *user)
2825{
2826 term_T *term = (term_T *)user;
2827
2828 switch (prop)
2829 {
2830 case VTERM_PROP_TITLE:
2831 vim_free(term->tl_title);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002832 // a blank title isn't useful, make it empty, so that "running" is
2833 // displayed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002834 if (*skipwhite((char_u *)value->string) == NUL)
2835 term->tl_title = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002836 // Same as blank
2837 else if (term->tl_arg0_cmd != NULL
2838 && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string,
2839 (int)STRLEN(term->tl_arg0_cmd)) == 0)
2840 term->tl_title = NULL;
2841 // Empty corrupted data of winpty
2842 else if (STRNCMP(" - ", (char_u *)value->string, 4) == 0)
2843 term->tl_title = NULL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01002844#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002845 else if (!enc_utf8 && enc_codepage > 0)
2846 {
2847 WCHAR *ret = NULL;
2848 int length = 0;
2849
2850 MultiByteToWideChar_alloc(CP_UTF8, 0,
2851 (char*)value->string, (int)STRLEN(value->string),
2852 &ret, &length);
2853 if (ret != NULL)
2854 {
2855 WideCharToMultiByte_alloc(enc_codepage, 0,
2856 ret, length, (char**)&term->tl_title,
2857 &length, 0, 0);
2858 vim_free(ret);
2859 }
2860 }
2861#endif
2862 else
2863 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002864 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002865 if (term == curbuf->b_term)
2866 maketitle();
2867 break;
2868
2869 case VTERM_PROP_CURSORVISIBLE:
2870 term->tl_cursor_visible = value->boolean;
2871 may_toggle_cursor(term);
2872 out_flush();
2873 break;
2874
2875 case VTERM_PROP_CURSORBLINK:
2876 term->tl_cursor_blink = value->boolean;
2877 may_set_cursor_props(term);
2878 break;
2879
2880 case VTERM_PROP_CURSORSHAPE:
2881 term->tl_cursor_shape = value->number;
2882 may_set_cursor_props(term);
2883 break;
2884
2885 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002886 cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002887 may_set_cursor_props(term);
2888 break;
2889
2890 case VTERM_PROP_ALTSCREEN:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002891 // TODO: do anything else?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002892 term->tl_using_altscreen = value->boolean;
2893 break;
2894
2895 default:
2896 break;
2897 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002898 // Always return 1, otherwise vterm doesn't store the value internally.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002899 return 1;
2900}
2901
2902/*
2903 * The job running in the terminal resized the terminal.
2904 */
2905 static int
2906handle_resize(int rows, int cols, void *user)
2907{
2908 term_T *term = (term_T *)user;
2909 win_T *wp;
2910
2911 term->tl_rows = rows;
2912 term->tl_cols = cols;
2913 if (term->tl_vterm_size_changed)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002914 // Size was set by vterm_set_size(), don't set the window size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002915 term->tl_vterm_size_changed = FALSE;
2916 else
2917 {
2918 FOR_ALL_WINDOWS(wp)
2919 {
2920 if (wp->w_buffer == term->tl_buffer)
2921 {
2922 win_setheight_win(rows, wp);
2923 win_setwidth_win(cols, wp);
2924 }
2925 }
2926 redraw_buf_later(term->tl_buffer, NOT_VALID);
2927 }
2928 return 1;
2929}
2930
2931/*
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002932 * If the number of lines that are stored goes over 'termscrollback' then
2933 * delete the first 10%.
2934 * "gap" points to tl_scrollback or tl_scrollback_postponed.
2935 * "update_buffer" is TRUE when the buffer should be updated.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002936 */
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002937 static void
2938limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002939{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002940 if (gap->ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002941 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002942 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002943 int i;
2944
2945 curbuf = term->tl_buffer;
2946 for (i = 0; i < todo; ++i)
2947 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002948 vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells);
2949 if (update_buffer)
2950 ml_delete(1, FALSE);
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002951 }
2952 curbuf = curwin->w_buffer;
2953
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002954 gap->ga_len -= todo;
2955 mch_memmove(gap->ga_data,
2956 (sb_line_T *)gap->ga_data + todo,
2957 sizeof(sb_line_T) * gap->ga_len);
2958 if (update_buffer)
2959 term->tl_scrollback_scrolled -= todo;
2960 }
2961}
2962
2963/*
2964 * Handle a line that is pushed off the top of the screen.
2965 */
2966 static int
2967handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2968{
2969 term_T *term = (term_T *)user;
2970 garray_T *gap;
2971 int update_buffer;
2972
2973 if (term->tl_normal_mode)
2974 {
2975 // In Terminal-Normal mode the user interacts with the buffer, thus we
2976 // must not change it. Postpone adding the scrollback lines.
2977 gap = &term->tl_scrollback_postponed;
2978 update_buffer = FALSE;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002979 }
2980 else
2981 {
2982 // First remove the lines that were appended before, the pushed line
2983 // goes above it.
2984 cleanup_scrollback(term);
2985 gap = &term->tl_scrollback;
2986 update_buffer = TRUE;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002987 }
2988
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002989 limit_scrollback(term, gap, update_buffer);
2990
2991 if (ga_grow(gap, 1) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002992 {
2993 cellattr_T *p = NULL;
2994 int len = 0;
2995 int i;
2996 int c;
2997 int col;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002998 int text_len;
2999 char_u *text;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003000 sb_line_T *line;
3001 garray_T ga;
3002 cellattr_T fill_attr = term->tl_default_color;
3003
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003004 // do not store empty cells at the end
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003005 for (i = 0; i < cols; ++i)
3006 if (cells[i].chars[0] != 0)
3007 len = i + 1;
3008 else
3009 cell2cellattr(&cells[i], &fill_attr);
3010
3011 ga_init2(&ga, 1, 100);
3012 if (len > 0)
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003013 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003014 if (p != NULL)
3015 {
3016 for (col = 0; col < len; col += cells[col].width)
3017 {
3018 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
3019 {
3020 ga.ga_len = 0;
3021 break;
3022 }
3023 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
3024 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
3025 (char_u *)ga.ga_data + ga.ga_len);
3026 cell2cellattr(&cells[col], &p[col]);
3027 }
3028 }
3029 if (ga_grow(&ga, 1) == FAIL)
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003030 {
3031 if (update_buffer)
3032 text = (char_u *)"";
3033 else
3034 text = vim_strsave((char_u *)"");
3035 text_len = 0;
3036 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003037 else
3038 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003039 text = ga.ga_data;
3040 text_len = ga.ga_len;
3041 *(text + text_len) = NUL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003042 }
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003043 if (update_buffer)
3044 add_scrollback_line_to_buffer(term, text, text_len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003045
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003046 line = (sb_line_T *)gap->ga_data + gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003047 line->sb_cols = len;
3048 line->sb_cells = p;
3049 line->sb_fill_attr = fill_attr;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003050 if (update_buffer)
3051 {
3052 line->sb_text = NULL;
3053 ++term->tl_scrollback_scrolled;
3054 ga_clear(&ga); // free the text
3055 }
3056 else
3057 {
3058 line->sb_text = text;
3059 ga_init(&ga); // text is kept in tl_scrollback_postponed
3060 }
3061 ++gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003062 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003063 return 0; // ignored
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003064}
3065
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003066/*
3067 * Called when leaving Terminal-Normal mode: deal with any scrollback that was
3068 * received and stored in tl_scrollback_postponed.
3069 */
3070 static void
3071handle_postponed_scrollback(term_T *term)
3072{
3073 int i;
3074
Bram Moolenaar8376c3d2019-03-19 20:50:43 +01003075 if (term->tl_scrollback_postponed.ga_len == 0)
3076 return;
3077 ch_log(NULL, "Moving postponed scrollback to scrollback");
3078
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003079 // First remove the lines that were appended before, the pushed lines go
3080 // above it.
3081 cleanup_scrollback(term);
3082
3083 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
3084 {
3085 char_u *text;
3086 sb_line_T *pp_line;
3087 sb_line_T *line;
3088
3089 if (ga_grow(&term->tl_scrollback, 1) == FAIL)
3090 break;
3091 pp_line = (sb_line_T *)term->tl_scrollback_postponed.ga_data + i;
3092
3093 text = pp_line->sb_text;
3094 if (text == NULL)
3095 text = (char_u *)"";
3096 add_scrollback_line_to_buffer(term, text, (int)STRLEN(text));
3097 vim_free(pp_line->sb_text);
3098
3099 line = (sb_line_T *)term->tl_scrollback.ga_data
3100 + term->tl_scrollback.ga_len;
3101 line->sb_cols = pp_line->sb_cols;
3102 line->sb_cells = pp_line->sb_cells;
3103 line->sb_fill_attr = pp_line->sb_fill_attr;
3104 line->sb_text = NULL;
3105 ++term->tl_scrollback_scrolled;
3106 ++term->tl_scrollback.ga_len;
3107 }
3108
3109 ga_clear(&term->tl_scrollback_postponed);
3110 limit_scrollback(term, &term->tl_scrollback, TRUE);
3111}
3112
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003113static VTermScreenCallbacks screen_callbacks = {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003114 handle_damage, // damage
3115 handle_moverect, // moverect
3116 handle_movecursor, // movecursor
3117 handle_settermprop, // settermprop
3118 NULL, // bell
3119 handle_resize, // resize
3120 handle_pushline, // sb_pushline
3121 NULL // sb_popline
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003122};
3123
3124/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003125 * Do the work after the channel of a terminal was closed.
3126 * Must be called only when updating_screen is FALSE.
3127 * Returns TRUE when a buffer was closed (list of terminals may have changed).
3128 */
3129 static int
3130term_after_channel_closed(term_T *term)
3131{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003132 // Unless in Terminal-Normal mode: clear the vterm.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003133 if (!term->tl_normal_mode)
3134 {
3135 int fnum = term->tl_buffer->b_fnum;
3136
3137 cleanup_vterm(term);
3138
3139 if (term->tl_finish == TL_FINISH_CLOSE)
3140 {
3141 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003142 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003143
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003144 // If this is the last normal window: exit Vim.
3145 if (term->tl_buffer->b_nwindows > 0 && only_one_window())
3146 {
3147 exarg_T ea;
3148
3149 vim_memset(&ea, 0, sizeof(ea));
3150 ex_quit(&ea);
3151 return TRUE;
3152 }
3153
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003154 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003155 ch_log(NULL, "terminal job finished, closing window");
3156 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003157 // Avoid closing the window if we temporarily use it.
Bram Moolenaar517f71a2019-06-17 22:40:41 +02003158 if (curwin == aucmd_win)
3159 do_set_w_closing = TRUE;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003160 if (do_set_w_closing)
3161 curwin->w_closing = TRUE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003162 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003163 if (do_set_w_closing)
3164 curwin->w_closing = FALSE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003165 aucmd_restbuf(&aco);
3166 return TRUE;
3167 }
3168 if (term->tl_finish == TL_FINISH_OPEN
3169 && term->tl_buffer->b_nwindows == 0)
3170 {
3171 char buf[50];
3172
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003173 // TODO: use term_opencmd
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003174 ch_log(NULL, "terminal job finished, opening window");
3175 vim_snprintf(buf, sizeof(buf),
3176 term->tl_opencmd == NULL
3177 ? "botright sbuf %d"
3178 : (char *)term->tl_opencmd, fnum);
3179 do_cmdline_cmd((char_u *)buf);
3180 }
3181 else
3182 ch_log(NULL, "terminal job finished");
3183 }
3184
3185 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
3186 return FALSE;
3187}
3188
3189/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003190 * Called when a channel has been closed.
3191 * If this was a channel for a terminal window then finish it up.
3192 */
3193 void
3194term_channel_closed(channel_T *ch)
3195{
3196 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003197 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003198 int did_one = FALSE;
3199
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003200 for (term = first_term; term != NULL; term = next_term)
3201 {
3202 next_term = term->tl_next;
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02003203 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003204 {
3205 term->tl_channel_closed = TRUE;
3206 did_one = TRUE;
3207
Bram Moolenaard23a8232018-02-10 18:45:26 +01003208 VIM_CLEAR(term->tl_title);
3209 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar4f974752019-02-17 17:44:42 +01003210#ifdef MSWIN
Bram Moolenaar402c8392018-05-06 22:01:42 +02003211 if (term->tl_out_fd != NULL)
3212 {
3213 fclose(term->tl_out_fd);
3214 term->tl_out_fd = NULL;
3215 }
3216#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003217
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003218 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003219 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003220 // Cannot open or close windows now. Can happen when
3221 // 'lazyredraw' is set.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003222 term->tl_channel_recently_closed = TRUE;
3223 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003224 }
3225
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003226 if (term_after_channel_closed(term))
3227 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003228 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003229 }
3230
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003231 if (did_one)
3232 {
3233 redraw_statuslines();
3234
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003235 // Need to break out of vgetc().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003236 ins_char_typebuf(K_IGNORE);
3237 typebuf_was_filled = TRUE;
3238
3239 term = curbuf->b_term;
3240 if (term != NULL)
3241 {
3242 if (term->tl_job == ch->ch_job)
3243 maketitle();
3244 update_cursor(term, term->tl_cursor_visible);
3245 }
3246 }
3247}
3248
3249/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003250 * To be called after resetting updating_screen: handle any terminal where the
3251 * channel was closed.
3252 */
3253 void
3254term_check_channel_closed_recently()
3255{
3256 term_T *term;
3257 term_T *next_term;
3258
3259 for (term = first_term; term != NULL; term = next_term)
3260 {
3261 next_term = term->tl_next;
3262 if (term->tl_channel_recently_closed)
3263 {
3264 term->tl_channel_recently_closed = FALSE;
3265 if (term_after_channel_closed(term))
3266 // start over, the list may have changed
3267 next_term = first_term;
3268 }
3269 }
3270}
3271
3272/*
Bram Moolenaar13568252018-03-16 20:46:58 +01003273 * Fill one screen line from a line of the terminal.
3274 * Advances "pos" to past the last column.
3275 */
3276 static void
3277term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
3278{
3279 int off = screen_get_current_line_off();
3280
3281 for (pos->col = 0; pos->col < max_col; )
3282 {
3283 VTermScreenCell cell;
3284 int c;
3285
3286 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
3287 vim_memset(&cell, 0, sizeof(cell));
3288
3289 c = cell.chars[0];
3290 if (c == NUL)
3291 {
3292 ScreenLines[off] = ' ';
3293 if (enc_utf8)
3294 ScreenLinesUC[off] = NUL;
3295 }
3296 else
3297 {
3298 if (enc_utf8)
3299 {
3300 int i;
3301
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003302 // composing chars
Bram Moolenaar13568252018-03-16 20:46:58 +01003303 for (i = 0; i < Screen_mco
3304 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3305 {
3306 ScreenLinesC[i][off] = cell.chars[i + 1];
3307 if (cell.chars[i + 1] == 0)
3308 break;
3309 }
3310 if (c >= 0x80 || (Screen_mco > 0
3311 && ScreenLinesC[0][off] != 0))
3312 {
3313 ScreenLines[off] = ' ';
3314 ScreenLinesUC[off] = c;
3315 }
3316 else
3317 {
3318 ScreenLines[off] = c;
3319 ScreenLinesUC[off] = NUL;
3320 }
3321 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01003322#ifdef MSWIN
Bram Moolenaar13568252018-03-16 20:46:58 +01003323 else if (has_mbyte && c >= 0x80)
3324 {
3325 char_u mb[MB_MAXBYTES+1];
3326 WCHAR wc = c;
3327
3328 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3329 (char*)mb, 2, 0, 0) > 1)
3330 {
3331 ScreenLines[off] = mb[0];
3332 ScreenLines[off + 1] = mb[1];
3333 cell.width = mb_ptr2cells(mb);
3334 }
3335 else
3336 ScreenLines[off] = c;
3337 }
3338#endif
3339 else
3340 ScreenLines[off] = c;
3341 }
3342 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
3343
3344 ++pos->col;
3345 ++off;
3346 if (cell.width == 2)
3347 {
3348 if (enc_utf8)
3349 ScreenLinesUC[off] = NUL;
3350
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003351 // don't set the second byte to NUL for a DBCS encoding, it
3352 // has been set above
Bram Moolenaar13568252018-03-16 20:46:58 +01003353 if (enc_utf8 || !has_mbyte)
3354 ScreenLines[off] = NUL;
3355
3356 ++pos->col;
3357 ++off;
3358 }
3359 }
3360}
3361
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003362#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003363 static void
3364update_system_term(term_T *term)
3365{
3366 VTermPos pos;
3367 VTermScreen *screen;
3368
3369 if (term->tl_vterm == NULL)
3370 return;
3371 screen = vterm_obtain_screen(term->tl_vterm);
3372
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003373 // Scroll up to make more room for terminal lines if needed.
Bram Moolenaar13568252018-03-16 20:46:58 +01003374 while (term->tl_toprow > 0
3375 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3376 {
3377 int save_p_more = p_more;
3378
3379 p_more = FALSE;
3380 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003381 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003382 p_more = save_p_more;
3383 --term->tl_toprow;
3384 }
3385
3386 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3387 && pos.row < Rows; ++pos.row)
3388 {
3389 if (pos.row < term->tl_rows)
3390 {
3391 int max_col = MIN(Columns, term->tl_cols);
3392
3393 term_line2screenline(screen, &pos, max_col);
3394 }
3395 else
3396 pos.col = 0;
3397
Bram Moolenaar4d784b22019-05-25 19:51:39 +02003398 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, 0);
Bram Moolenaar13568252018-03-16 20:46:58 +01003399 }
3400
3401 term->tl_dirty_row_start = MAX_ROW;
3402 term->tl_dirty_row_end = 0;
Bram Moolenaar13568252018-03-16 20:46:58 +01003403}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003404#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003405
3406/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003407 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3408 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003409 * Terminal-Normal mode.
3410 */
3411 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003412term_do_update_window(win_T *wp)
3413{
3414 term_T *term = wp->w_buffer->b_term;
3415
3416 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3417}
3418
3419/*
3420 * Called to update a window that contains an active terminal.
3421 */
3422 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003423term_update_window(win_T *wp)
3424{
3425 term_T *term = wp->w_buffer->b_term;
3426 VTerm *vterm;
3427 VTermScreen *screen;
3428 VTermState *state;
3429 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003430 int rows, cols;
3431 int newrows, newcols;
3432 int minsize;
3433 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003434
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003435 vterm = term->tl_vterm;
3436 screen = vterm_obtain_screen(vterm);
3437 state = vterm_obtain_state(vterm);
3438
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003439 // We use NOT_VALID on a resize or scroll, redraw everything then. With
3440 // SOME_VALID only redraw what was marked dirty.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003441 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003442 {
3443 term->tl_dirty_row_start = 0;
3444 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003445
3446 if (term->tl_postponed_scroll > 0
3447 && term->tl_postponed_scroll < term->tl_rows / 3)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003448 // Scrolling is usually faster than redrawing, when there are only
3449 // a few lines to scroll.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003450 term_scroll_up(term, 0, term->tl_postponed_scroll);
3451 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003452 }
3453
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003454 /*
3455 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003456 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003457 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003458 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003459
Bram Moolenaar498c2562018-04-15 23:45:15 +02003460 newrows = 99999;
3461 newcols = 99999;
3462 FOR_ALL_WINDOWS(twp)
3463 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003464 // When more than one window shows the same terminal, use the
3465 // smallest size.
Bram Moolenaar498c2562018-04-15 23:45:15 +02003466 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003467 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02003468 newrows = MIN(newrows, twp->w_height);
3469 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003470 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02003471 }
Bram Moolenaare0d749a2019-09-25 22:14:48 +02003472 if (newrows == 99999 || newcols == 99999)
3473 return; // safety exit
Bram Moolenaar498c2562018-04-15 23:45:15 +02003474 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3475 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3476
3477 if (term->tl_rows != newrows || term->tl_cols != newcols)
3478 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003479 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003480 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003481 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003482 newrows);
3483 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003484
3485 // Updating the terminal size will cause the snapshot to be cleared.
3486 // When not in terminal_loop() we need to restore it.
3487 if (term != in_terminal_loop)
3488 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003489 }
3490
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003491 // The cursor may have been moved when resizing.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003492 vterm_state_get_cursorpos(state, &pos);
3493 position_cursor(wp, &pos);
3494
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003495 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3496 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003497 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003498 if (pos.row < term->tl_rows)
3499 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003500 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003501
Bram Moolenaar13568252018-03-16 20:46:58 +01003502 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003503 }
3504 else
3505 pos.col = 0;
3506
Bram Moolenaarf118d482018-03-13 13:14:00 +01003507 screen_line(wp->w_winrow + pos.row
3508#ifdef FEAT_MENU
3509 + winbar_height(wp)
3510#endif
Bram Moolenaar4d784b22019-05-25 19:51:39 +02003511 , wp->w_wincol, pos.col, wp->w_width, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003512 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003513 term->tl_dirty_row_start = MAX_ROW;
3514 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003515}
3516
3517/*
3518 * Return TRUE if "wp" is a terminal window where the job has finished.
3519 */
3520 int
3521term_is_finished(buf_T *buf)
3522{
3523 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3524}
3525
3526/*
3527 * Return TRUE if "wp" is a terminal window where the job has finished or we
3528 * are in Terminal-Normal mode, thus we show the buffer contents.
3529 */
3530 int
3531term_show_buffer(buf_T *buf)
3532{
3533 term_T *term = buf->b_term;
3534
3535 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3536}
3537
3538/*
3539 * The current buffer is going to be changed. If there is terminal
3540 * highlighting remove it now.
3541 */
3542 void
3543term_change_in_curbuf(void)
3544{
3545 term_T *term = curbuf->b_term;
3546
3547 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3548 {
3549 free_scrollback(term);
3550 redraw_buf_later(term->tl_buffer, NOT_VALID);
3551
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003552 // The buffer is now like a normal buffer, it cannot be easily
3553 // abandoned when changed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003554 set_string_option_direct((char_u *)"buftype", -1,
3555 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3556 }
3557}
3558
3559/*
3560 * Get the screen attribute for a position in the buffer.
3561 * Use a negative "col" to get the filler background color.
3562 */
3563 int
3564term_get_attr(buf_T *buf, linenr_T lnum, int col)
3565{
3566 term_T *term = buf->b_term;
3567 sb_line_T *line;
3568 cellattr_T *cellattr;
3569
3570 if (lnum > term->tl_scrollback.ga_len)
3571 cellattr = &term->tl_default_color;
3572 else
3573 {
3574 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3575 if (col < 0 || col >= line->sb_cols)
3576 cellattr = &line->sb_fill_attr;
3577 else
3578 cellattr = line->sb_cells + col;
3579 }
3580 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3581}
3582
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003583/*
3584 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003585 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003586 */
3587 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003588cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003589{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003590 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003591}
3592
3593/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003594 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003595 */
3596 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003597init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003598{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003599 VTermColor *fg, *bg;
3600 int fgval, bgval;
3601 int id;
3602
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003603 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3604 term->tl_default_color.width = 1;
3605 fg = &term->tl_default_color.fg;
3606 bg = &term->tl_default_color.bg;
3607
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003608 // Vterm uses a default black background. Set it to white when
3609 // 'background' is "light".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003610 if (*p_bg == 'l')
3611 {
3612 fgval = 0;
3613 bgval = 255;
3614 }
3615 else
3616 {
3617 fgval = 255;
3618 bgval = 0;
3619 }
3620 fg->red = fg->green = fg->blue = fgval;
3621 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003622 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003623
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003624 // The "Terminal" highlight group overrules the defaults.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003625 id = syn_name2id((char_u *)"Terminal");
3626
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003627 // Use the actual color for the GUI and when 'termguicolors' is set.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003628#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3629 if (0
3630# ifdef FEAT_GUI
3631 || gui.in_use
3632# endif
3633# ifdef FEAT_TERMGUICOLORS
3634 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003635# ifdef FEAT_VTP
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003636 // Finally get INVALCOLOR on this execution path
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003637 || (!p_tgc && t_colors >= 256)
3638# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003639# endif
3640 )
3641 {
3642 guicolor_T fg_rgb = INVALCOLOR;
3643 guicolor_T bg_rgb = INVALCOLOR;
3644
3645 if (id != 0)
3646 syn_id2colors(id, &fg_rgb, &bg_rgb);
3647
3648# ifdef FEAT_GUI
3649 if (gui.in_use)
3650 {
3651 if (fg_rgb == INVALCOLOR)
3652 fg_rgb = gui.norm_pixel;
3653 if (bg_rgb == INVALCOLOR)
3654 bg_rgb = gui.back_pixel;
3655 }
3656# ifdef FEAT_TERMGUICOLORS
3657 else
3658# endif
3659# endif
3660# ifdef FEAT_TERMGUICOLORS
3661 {
3662 if (fg_rgb == INVALCOLOR)
3663 fg_rgb = cterm_normal_fg_gui_color;
3664 if (bg_rgb == INVALCOLOR)
3665 bg_rgb = cterm_normal_bg_gui_color;
3666 }
3667# endif
3668 if (fg_rgb != INVALCOLOR)
3669 {
3670 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3671
3672 fg->red = (unsigned)(rgb >> 16);
3673 fg->green = (unsigned)(rgb >> 8) & 255;
3674 fg->blue = (unsigned)rgb & 255;
3675 }
3676 if (bg_rgb != INVALCOLOR)
3677 {
3678 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3679
3680 bg->red = (unsigned)(rgb >> 16);
3681 bg->green = (unsigned)(rgb >> 8) & 255;
3682 bg->blue = (unsigned)rgb & 255;
3683 }
3684 }
3685 else
3686#endif
3687 if (id != 0 && t_colors >= 16)
3688 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003689 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003690 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003691 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003692 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003693 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003694 else
3695 {
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003696#if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003697 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003698#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003699
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003700 // In an MS-Windows console we know the normal colors.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003701 if (cterm_normal_fg_color > 0)
3702 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003703 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003704# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3705# ifdef VIMDLL
3706 if (!gui.in_use)
3707# endif
3708 {
3709 tmp = fg->red;
3710 fg->red = fg->blue;
3711 fg->blue = tmp;
3712 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003713# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003714 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003715# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003716 else
3717 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003718# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003719
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003720 if (cterm_normal_bg_color > 0)
3721 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003722 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003723# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3724# ifdef VIMDLL
3725 if (!gui.in_use)
3726# endif
3727 {
3728 tmp = fg->red;
3729 fg->red = fg->blue;
3730 fg->blue = tmp;
3731 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003732# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003733 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003734# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003735 else
3736 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003737# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003738 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003739}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003740
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003741#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3742/*
3743 * Set the 16 ANSI colors from array of RGB values
3744 */
3745 static void
3746set_vterm_palette(VTerm *vterm, long_u *rgb)
3747{
3748 int index = 0;
3749 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003750
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003751 for (; index < 16; index++)
3752 {
3753 VTermColor color;
Bram Moolenaaref8c83c2019-04-11 11:40:13 +02003754
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003755 color.red = (unsigned)(rgb[index] >> 16);
3756 color.green = (unsigned)(rgb[index] >> 8) & 255;
3757 color.blue = (unsigned)rgb[index] & 255;
3758 vterm_state_set_palette_color(state, index, &color);
3759 }
3760}
3761
3762/*
3763 * Set the ANSI color palette from a list of colors
3764 */
3765 static int
3766set_ansi_colors_list(VTerm *vterm, list_T *list)
3767{
3768 int n = 0;
3769 long_u rgb[16];
Bram Moolenaarb0992022020-01-30 14:55:42 +01003770 listitem_T *li;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003771
Bram Moolenaarb0992022020-01-30 14:55:42 +01003772 for (li = list->lv_first; li != NULL && n < 16; li = li->li_next, n++)
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003773 {
3774 char_u *color_name;
3775 guicolor_T guicolor;
3776
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003777 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003778 if (color_name == NULL)
3779 return FAIL;
3780
3781 guicolor = GUI_GET_COLOR(color_name);
3782 if (guicolor == INVALCOLOR)
3783 return FAIL;
3784
3785 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3786 }
3787
3788 if (n != 16 || li != NULL)
3789 return FAIL;
3790
3791 set_vterm_palette(vterm, rgb);
3792
3793 return OK;
3794}
3795
3796/*
3797 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3798 */
3799 static void
3800init_vterm_ansi_colors(VTerm *vterm)
3801{
3802 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3803
3804 if (var != NULL
3805 && (var->di_tv.v_type != VAR_LIST
3806 || var->di_tv.vval.v_list == NULL
Bram Moolenaarb0992022020-01-30 14:55:42 +01003807 || var->di_tv.vval.v_list->lv_first == &range_list_item
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003808 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003809 semsg(_(e_invarg2), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003810}
3811#endif
3812
Bram Moolenaar52acb112018-03-18 19:20:22 +01003813/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003814 * Handles a "drop" command from the job in the terminal.
3815 * "item" is the file name, "item->li_next" may have options.
3816 */
3817 static void
3818handle_drop_command(listitem_T *item)
3819{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003820 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003821 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003822 int bufnr;
3823 win_T *wp;
3824 tabpage_T *tp;
3825 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003826 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003827
3828 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3829 FOR_ALL_TAB_WINDOWS(tp, wp)
3830 {
3831 if (wp->w_buffer->b_fnum == bufnr)
3832 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003833 // buffer is in a window already, go there
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003834 goto_tabpage_win(tp, wp);
3835 return;
3836 }
3837 }
3838
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003839 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003840
3841 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3842 && opt_item->li_tv.vval.v_dict != NULL)
3843 {
3844 dict_T *dict = opt_item->li_tv.vval.v_dict;
3845 char_u *p;
3846
Bram Moolenaar8f667172018-12-14 15:38:31 +01003847 p = dict_get_string(dict, (char_u *)"ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003848 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003849 p = dict_get_string(dict, (char_u *)"fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003850 if (p != NULL)
3851 {
3852 if (check_ff_value(p) == FAIL)
3853 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3854 else
3855 ea.force_ff = *p;
3856 }
Bram Moolenaar8f667172018-12-14 15:38:31 +01003857 p = dict_get_string(dict, (char_u *)"enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003858 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003859 p = dict_get_string(dict, (char_u *)"encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003860 if (p != NULL)
3861 {
Bram Moolenaar51e14382019-05-25 20:21:28 +02003862 ea.cmd = alloc(STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003863 if (ea.cmd != NULL)
3864 {
3865 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3866 ea.force_enc = 11;
3867 tofree = ea.cmd;
3868 }
3869 }
3870
Bram Moolenaar8f667172018-12-14 15:38:31 +01003871 p = dict_get_string(dict, (char_u *)"bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003872 if (p != NULL)
3873 get_bad_opt(p, &ea);
3874
3875 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3876 ea.force_bin = FORCE_BIN;
3877 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3878 ea.force_bin = FORCE_BIN;
3879 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3880 ea.force_bin = FORCE_NOBIN;
3881 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3882 ea.force_bin = FORCE_NOBIN;
3883 }
3884
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003885 // open in new window, like ":split fname"
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003886 if (ea.cmd == NULL)
3887 ea.cmd = (char_u *)"split";
3888 ea.arg = fname;
3889 ea.cmdidx = CMD_split;
3890 ex_splitview(&ea);
3891
3892 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003893}
3894
3895/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003896 * Return TRUE if "func" starts with "pat" and "pat" isn't empty.
3897 */
3898 static int
3899is_permitted_term_api(char_u *func, char_u *pat)
3900{
3901 return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
3902}
3903
3904/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003905 * Handles a function call from the job running in a terminal.
3906 * "item" is the function name, "item->li_next" has the arguments.
3907 */
3908 static void
3909handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3910{
3911 char_u *func;
3912 typval_T argvars[2];
3913 typval_T rettv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02003914 funcexe_T funcexe;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003915
3916 if (item->li_next == NULL)
3917 {
3918 ch_log(channel, "Missing function arguments for call");
3919 return;
3920 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003921 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003922
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003923 if (!is_permitted_term_api(func, term->tl_api))
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003924 {
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003925 ch_log(channel, "Unpermitted function: %s", func);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003926 return;
3927 }
3928
3929 argvars[0].v_type = VAR_NUMBER;
3930 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3931 argvars[1] = item->li_next->li_tv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02003932 vim_memset(&funcexe, 0, sizeof(funcexe));
3933 funcexe.firstline = 1L;
3934 funcexe.lastline = 1L;
3935 funcexe.evaluate = TRUE;
3936 if (call_func(func, -1, &rettv, 2, argvars, &funcexe) == OK)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003937 {
3938 clear_tv(&rettv);
3939 ch_log(channel, "Function %s called", func);
3940 }
3941 else
3942 ch_log(channel, "Calling function %s failed", func);
3943}
3944
3945/*
3946 * Called by libvterm when it cannot recognize an OSC sequence.
3947 * We recognize a terminal API command.
3948 */
3949 static int
3950parse_osc(const char *command, size_t cmdlen, void *user)
3951{
3952 term_T *term = (term_T *)user;
3953 js_read_T reader;
3954 typval_T tv;
3955 channel_T *channel = term->tl_job == NULL ? NULL
3956 : term->tl_job->jv_channel;
3957
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003958 // We recognize only OSC 5 1 ; {command}
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003959 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003960 return 0; // not handled
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003961
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003962 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003963 if (reader.js_buf == NULL)
3964 return 1;
3965 reader.js_fill = NULL;
3966 reader.js_used = 0;
3967 if (json_decode(&reader, &tv, 0) == OK
3968 && tv.v_type == VAR_LIST
3969 && tv.vval.v_list != NULL)
3970 {
3971 listitem_T *item = tv.vval.v_list->lv_first;
3972
3973 if (item == NULL)
3974 ch_log(channel, "Missing command");
3975 else
3976 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003977 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003978
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003979 // Make sure an invoked command doesn't delete the buffer (and the
3980 // terminal) under our fingers.
Bram Moolenaara997b452018-04-17 23:24:06 +02003981 ++term->tl_buffer->b_locked;
3982
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003983 item = item->li_next;
3984 if (item == NULL)
3985 ch_log(channel, "Missing argument for %s", cmd);
3986 else if (STRCMP(cmd, "drop") == 0)
3987 handle_drop_command(item);
3988 else if (STRCMP(cmd, "call") == 0)
3989 handle_call_command(term, channel, item);
3990 else
3991 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003992 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003993 }
3994 }
3995 else
3996 ch_log(channel, "Invalid JSON received");
3997
3998 vim_free(reader.js_buf);
3999 clear_tv(&tv);
4000 return 1;
4001}
4002
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004003/*
4004 * Called by libvterm when it cannot recognize a CSI sequence.
4005 * We recognize the window position report.
4006 */
4007 static int
4008parse_csi(
4009 const char *leader UNUSED,
4010 const long args[],
4011 int argcount,
4012 const char *intermed UNUSED,
4013 char command,
4014 void *user)
4015{
4016 term_T *term = (term_T *)user;
4017 char buf[100];
4018 int len;
4019 int x = 0;
4020 int y = 0;
4021 win_T *wp;
4022
4023 // We recognize only CSI 13 t
4024 if (command != 't' || argcount != 1 || args[0] != 13)
4025 return 0; // not handled
4026
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004027 // When getting the window position is not possible or it fails it results
4028 // in zero/zero.
Bram Moolenaar16c34c32019-04-06 22:01:24 +02004029#if defined(FEAT_GUI) \
4030 || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
4031 || defined(MSWIN)
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004032 (void)ui_get_winpos(&x, &y, (varnumber_T)100);
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004033#endif
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004034
4035 FOR_ALL_WINDOWS(wp)
4036 if (wp->w_buffer == term->tl_buffer)
4037 break;
4038 if (wp != NULL)
4039 {
4040#ifdef FEAT_GUI
4041 if (gui.in_use)
4042 {
4043 x += wp->w_wincol * gui.char_width;
4044 y += W_WINROW(wp) * gui.char_height;
4045 }
4046 else
4047#endif
4048 {
4049 // We roughly estimate the position of the terminal window inside
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004050 // the Vim window by assuming a 10 x 7 character cell.
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004051 x += wp->w_wincol * 7;
4052 y += W_WINROW(wp) * 10;
4053 }
4054 }
4055
4056 len = vim_snprintf(buf, 100, "\x1b[3;%d;%dt", x, y);
4057 channel_send(term->tl_job->jv_channel, get_tty_part(term),
4058 (char_u *)buf, len, NULL);
4059 return 1;
4060}
4061
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004062static VTermParserCallbacks parser_fallbacks = {
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004063 NULL, // text
4064 NULL, // control
4065 NULL, // escape
4066 parse_csi, // csi
4067 parse_osc, // osc
4068 NULL, // dcs
4069 NULL // resize
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004070};
4071
4072/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02004073 * Use Vim's allocation functions for vterm so profiling works.
4074 */
4075 static void *
4076vterm_malloc(size_t size, void *data UNUSED)
4077{
Bram Moolenaar18a4ba22019-05-24 19:39:03 +02004078 return alloc_clear(size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02004079}
4080
4081 static void
4082vterm_memfree(void *ptr, void *data UNUSED)
4083{
4084 vim_free(ptr);
4085}
4086
4087static VTermAllocatorFunctions vterm_allocator = {
4088 &vterm_malloc,
4089 &vterm_memfree
4090};
4091
4092/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004093 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004094 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004095 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004096 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01004097create_vterm(term_T *term, int rows, int cols)
4098{
4099 VTerm *vterm;
4100 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004101 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01004102 VTermValue value;
4103
Bram Moolenaar756ef112018-04-10 12:04:27 +02004104 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004105 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004106 if (vterm == NULL)
4107 return FAIL;
4108
4109 // Allocate screen and state here, so we can bail out if that fails.
4110 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004111 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004112 if (state == NULL || screen == NULL)
4113 {
4114 vterm_free(vterm);
4115 return FAIL;
4116 }
4117
Bram Moolenaar52acb112018-03-18 19:20:22 +01004118 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004119 // TODO: depends on 'encoding'.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004120 vterm_set_utf8(vterm, 1);
4121
4122 init_default_colors(term);
4123
4124 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004125 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01004126 &term->tl_default_color.fg,
4127 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004128
Bram Moolenaar9e587872019-05-13 20:27:23 +02004129 if (t_colors < 16)
4130 // Less than 16 colors: assume that bold means using a bright color for
4131 // the foreground color.
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004132 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
4133
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004134 // Required to initialize most things.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004135 vterm_screen_reset(screen, 1 /* hard */);
4136
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004137 // Allow using alternate screen.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004138 vterm_screen_enable_altscreen(screen, 1);
4139
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004140 // For unix do not use a blinking cursor. In an xterm this causes the
4141 // cursor to blink if it's blinking in the xterm.
4142 // For Windows we respect the system wide setting.
Bram Moolenaar4f974752019-02-17 17:44:42 +01004143#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004144 if (GetCaretBlinkTime() == INFINITE)
4145 value.boolean = 0;
4146 else
4147 value.boolean = 1;
4148#else
4149 value.boolean = 0;
4150#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004151 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
4152 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004153
4154 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004155}
4156
4157/*
4158 * Return the text to show for the buffer name and status.
4159 */
4160 char_u *
4161term_get_status_text(term_T *term)
4162{
4163 if (term->tl_status_text == NULL)
4164 {
4165 char_u *txt;
4166 size_t len;
4167
4168 if (term->tl_normal_mode)
4169 {
4170 if (term_job_running(term))
4171 txt = (char_u *)_("Terminal");
4172 else
4173 txt = (char_u *)_("Terminal-finished");
4174 }
4175 else if (term->tl_title != NULL)
4176 txt = term->tl_title;
4177 else if (term_none_open(term))
4178 txt = (char_u *)_("active");
4179 else if (term_job_running(term))
4180 txt = (char_u *)_("running");
4181 else
4182 txt = (char_u *)_("finished");
4183 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
Bram Moolenaar51e14382019-05-25 20:21:28 +02004184 term->tl_status_text = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004185 if (term->tl_status_text != NULL)
4186 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
4187 term->tl_buffer->b_fname, txt);
4188 }
4189 return term->tl_status_text;
4190}
4191
4192/*
4193 * Mark references in jobs of terminals.
4194 */
4195 int
4196set_ref_in_term(int copyID)
4197{
4198 int abort = FALSE;
4199 term_T *term;
4200 typval_T tv;
4201
Bram Moolenaar75a1a942019-06-20 03:45:36 +02004202 for (term = first_term; !abort && term != NULL; term = term->tl_next)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004203 if (term->tl_job != NULL)
4204 {
4205 tv.v_type = VAR_JOB;
4206 tv.vval.v_job = term->tl_job;
4207 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4208 }
4209 return abort;
4210}
4211
4212/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01004213 * Cache "Terminal" highlight group colors.
4214 */
4215 void
4216set_terminal_default_colors(int cterm_fg, int cterm_bg)
4217{
4218 term_default_cterm_fg = cterm_fg - 1;
4219 term_default_cterm_bg = cterm_bg - 1;
4220}
4221
4222/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004223 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004224 * Returns NULL when the buffer is not for a terminal window and logs a message
4225 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004226 */
4227 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004228term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004229{
4230 buf_T *buf;
4231
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004232 (void)tv_get_number(&argvars[0]); // issue errmsg if type error
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004233 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01004234 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004235 --emsg_off;
4236 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004237 {
4238 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004239 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004240 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004241 return buf;
4242}
4243
Bram Moolenaard96ff162018-02-18 22:13:29 +01004244 static int
4245same_color(VTermColor *a, VTermColor *b)
4246{
4247 return a->red == b->red
4248 && a->green == b->green
4249 && a->blue == b->blue
4250 && a->ansi_index == b->ansi_index;
4251}
4252
4253 static void
4254dump_term_color(FILE *fd, VTermColor *color)
4255{
4256 fprintf(fd, "%02x%02x%02x%d",
4257 (int)color->red, (int)color->green, (int)color->blue,
4258 (int)color->ansi_index);
4259}
4260
4261/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004262 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01004263 *
4264 * Each screen cell in full is:
4265 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
4266 * {characters} is a space for an empty cell
4267 * For a double-width character "+" is changed to "*" and the next cell is
4268 * skipped.
4269 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
4270 * when "&" use the same as the previous cell.
4271 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
4272 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
4273 * {color-idx} is a number from 0 to 255
4274 *
4275 * Screen cell with same width, attributes and color as the previous one:
4276 * |{characters}
4277 *
4278 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
4279 *
4280 * Repeating the previous screen cell:
4281 * @{count}
4282 */
4283 void
4284f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
4285{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004286 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01004287 term_T *term;
4288 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004289 int max_height = 0;
4290 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004291 stat_T st;
4292 FILE *fd;
4293 VTermPos pos;
4294 VTermScreen *screen;
4295 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004296 VTermState *state;
4297 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004298
4299 if (check_restricted() || check_secure())
4300 return;
4301 if (buf == NULL)
4302 return;
4303 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004304 if (term->tl_vterm == NULL)
4305 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004306 emsg(_("E958: Job already finished"));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004307 return;
4308 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004309
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004310 if (argvars[2].v_type != VAR_UNKNOWN)
4311 {
4312 dict_T *d;
4313
4314 if (argvars[2].v_type != VAR_DICT)
4315 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004316 emsg(_(e_dictreq));
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004317 return;
4318 }
4319 d = argvars[2].vval.v_dict;
4320 if (d != NULL)
4321 {
Bram Moolenaar8f667172018-12-14 15:38:31 +01004322 max_height = dict_get_number(d, (char_u *)"rows");
4323 max_width = dict_get_number(d, (char_u *)"columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004324 }
4325 }
4326
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004327 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004328 if (fname == NULL)
4329 return;
4330 if (mch_stat((char *)fname, &st) >= 0)
4331 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004332 semsg(_("E953: File exists: %s"), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004333 return;
4334 }
4335
Bram Moolenaard96ff162018-02-18 22:13:29 +01004336 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
4337 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004338 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004339 return;
4340 }
4341
4342 vim_memset(&prev_cell, 0, sizeof(prev_cell));
4343
4344 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004345 state = vterm_obtain_state(term->tl_vterm);
4346 vterm_state_get_cursorpos(state, &cursor_pos);
4347
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004348 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
4349 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004350 {
4351 int repeat = 0;
4352
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004353 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
4354 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004355 {
4356 VTermScreenCell cell;
4357 int same_attr;
4358 int same_chars = TRUE;
4359 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004360 int is_cursor_pos = (pos.col == cursor_pos.col
4361 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004362
4363 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4364 vim_memset(&cell, 0, sizeof(cell));
4365
4366 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4367 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01004368 int c = cell.chars[i];
4369 int pc = prev_cell.chars[i];
4370
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004371 // For the first character NUL is the same as space.
Bram Moolenaar47015b82018-03-23 22:10:34 +01004372 if (i == 0)
4373 {
4374 c = (c == NUL) ? ' ' : c;
4375 pc = (pc == NUL) ? ' ' : pc;
4376 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004377 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004378 same_chars = FALSE;
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004379 if (c == NUL || pc == NUL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004380 break;
4381 }
4382 same_attr = vtermAttr2hl(cell.attrs)
4383 == vtermAttr2hl(prev_cell.attrs)
4384 && same_color(&cell.fg, &prev_cell.fg)
4385 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004386 if (same_chars && cell.width == prev_cell.width && same_attr
4387 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004388 {
4389 ++repeat;
4390 }
4391 else
4392 {
4393 if (repeat > 0)
4394 {
4395 fprintf(fd, "@%d", repeat);
4396 repeat = 0;
4397 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004398 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004399
4400 if (cell.chars[0] == NUL)
4401 fputs(" ", fd);
4402 else
4403 {
4404 char_u charbuf[10];
4405 int len;
4406
4407 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
4408 && cell.chars[i] != NUL; ++i)
4409 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02004410 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004411 fwrite(charbuf, len, 1, fd);
4412 }
4413 }
4414
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004415 // When only the characters differ we don't write anything, the
4416 // following "|", "@" or NL will indicate using the same
4417 // attributes.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004418 if (cell.width != prev_cell.width || !same_attr)
4419 {
4420 if (cell.width == 2)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004421 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004422 else
4423 fputs("+", fd);
4424
4425 if (same_attr)
4426 {
4427 fputs("&", fd);
4428 }
4429 else
4430 {
4431 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
4432 if (same_color(&cell.fg, &prev_cell.fg))
4433 fputs("&", fd);
4434 else
4435 {
4436 fputs("#", fd);
4437 dump_term_color(fd, &cell.fg);
4438 }
4439 if (same_color(&cell.bg, &prev_cell.bg))
4440 fputs("&", fd);
4441 else
4442 {
4443 fputs("#", fd);
4444 dump_term_color(fd, &cell.bg);
4445 }
4446 }
4447 }
4448
4449 prev_cell = cell;
4450 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004451
4452 if (cell.width == 2)
4453 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004454 }
4455 if (repeat > 0)
4456 fprintf(fd, "@%d", repeat);
4457 fputs("\n", fd);
4458 }
4459
4460 fclose(fd);
4461}
4462
4463/*
4464 * Called when a dump is corrupted. Put a breakpoint here when debugging.
4465 */
4466 static void
4467dump_is_corrupt(garray_T *gap)
4468{
4469 ga_concat(gap, (char_u *)"CORRUPT");
4470}
4471
4472 static void
4473append_cell(garray_T *gap, cellattr_T *cell)
4474{
4475 if (ga_grow(gap, 1) == OK)
4476 {
4477 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4478 ++gap->ga_len;
4479 }
4480}
4481
4482/*
4483 * Read the dump file from "fd" and append lines to the current buffer.
4484 * Return the cell width of the longest line.
4485 */
4486 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01004487read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004488{
4489 int c;
4490 garray_T ga_text;
4491 garray_T ga_cell;
4492 char_u *prev_char = NULL;
4493 int attr = 0;
4494 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004495 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004496 term_T *term = curbuf->b_term;
4497 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004498 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004499
4500 ga_init2(&ga_text, 1, 90);
4501 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4502 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004503 vim_memset(&empty_cell, 0, sizeof(empty_cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01004504 cursor_pos->row = -1;
4505 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004506
4507 c = fgetc(fd);
4508 for (;;)
4509 {
4510 if (c == EOF)
4511 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02004512 if (c == '\r')
4513 {
4514 // DOS line endings? Ignore.
4515 c = fgetc(fd);
4516 }
4517 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004518 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004519 // End of a line: append it to the buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004520 if (ga_text.ga_data == NULL)
4521 dump_is_corrupt(&ga_text);
4522 if (ga_grow(&term->tl_scrollback, 1) == OK)
4523 {
4524 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
4525 + term->tl_scrollback.ga_len;
4526
4527 if (max_cells < ga_cell.ga_len)
4528 max_cells = ga_cell.ga_len;
4529 line->sb_cols = ga_cell.ga_len;
4530 line->sb_cells = ga_cell.ga_data;
4531 line->sb_fill_attr = term->tl_default_color;
4532 ++term->tl_scrollback.ga_len;
4533 ga_init(&ga_cell);
4534
4535 ga_append(&ga_text, NUL);
4536 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4537 ga_text.ga_len, FALSE);
4538 }
4539 else
4540 ga_clear(&ga_cell);
4541 ga_text.ga_len = 0;
4542
4543 c = fgetc(fd);
4544 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004545 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004546 {
4547 int prev_len = ga_text.ga_len;
4548
Bram Moolenaar9271d052018-02-25 21:39:46 +01004549 if (c == '>')
4550 {
4551 if (cursor_pos->row != -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004552 dump_is_corrupt(&ga_text); // duplicate cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01004553 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4554 cursor_pos->col = ga_cell.ga_len;
4555 }
4556
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004557 // normal character(s) followed by "+", "*", "|", "@" or NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01004558 c = fgetc(fd);
4559 if (c != EOF)
4560 ga_append(&ga_text, c);
4561 for (;;)
4562 {
4563 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004564 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004565 || c == EOF || c == '\n')
4566 break;
4567 ga_append(&ga_text, c);
4568 }
4569
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004570 // save the character for repeating it
Bram Moolenaard96ff162018-02-18 22:13:29 +01004571 vim_free(prev_char);
4572 if (ga_text.ga_data != NULL)
4573 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4574 ga_text.ga_len - prev_len);
4575
Bram Moolenaar9271d052018-02-25 21:39:46 +01004576 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004577 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004578 // use all attributes from previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01004579 }
4580 else if (c == '+' || c == '*')
4581 {
4582 int is_bg;
4583
4584 cell.width = c == '+' ? 1 : 2;
4585
4586 c = fgetc(fd);
4587 if (c == '&')
4588 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004589 // use same attr as previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01004590 c = fgetc(fd);
4591 }
4592 else if (isdigit(c))
4593 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004594 // get the decimal attribute
Bram Moolenaard96ff162018-02-18 22:13:29 +01004595 attr = 0;
4596 while (isdigit(c))
4597 {
4598 attr = attr * 10 + (c - '0');
4599 c = fgetc(fd);
4600 }
4601 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004602
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004603 // is_bg == 0: fg, is_bg == 1: bg
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004604 for (is_bg = 0; is_bg <= 1; ++is_bg)
4605 {
4606 if (c == '&')
4607 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004608 // use same color as previous cell
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004609 c = fgetc(fd);
4610 }
4611 else if (c == '#')
4612 {
4613 int red, green, blue, index = 0;
4614
4615 c = fgetc(fd);
4616 red = hex2nr(c);
4617 c = fgetc(fd);
4618 red = (red << 4) + hex2nr(c);
4619 c = fgetc(fd);
4620 green = hex2nr(c);
4621 c = fgetc(fd);
4622 green = (green << 4) + hex2nr(c);
4623 c = fgetc(fd);
4624 blue = hex2nr(c);
4625 c = fgetc(fd);
4626 blue = (blue << 4) + hex2nr(c);
4627 c = fgetc(fd);
4628 if (!isdigit(c))
4629 dump_is_corrupt(&ga_text);
4630 while (isdigit(c))
4631 {
4632 index = index * 10 + (c - '0');
4633 c = fgetc(fd);
4634 }
4635
4636 if (is_bg)
4637 {
4638 cell.bg.red = red;
4639 cell.bg.green = green;
4640 cell.bg.blue = blue;
4641 cell.bg.ansi_index = index;
4642 }
4643 else
4644 {
4645 cell.fg.red = red;
4646 cell.fg.green = green;
4647 cell.fg.blue = blue;
4648 cell.fg.ansi_index = index;
4649 }
4650 }
4651 else
4652 dump_is_corrupt(&ga_text);
4653 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004654 }
4655 else
4656 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004657 }
4658 else
4659 dump_is_corrupt(&ga_text);
4660
4661 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004662 if (cell.width == 2)
4663 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004664 }
4665 else if (c == '@')
4666 {
4667 if (prev_char == NULL)
4668 dump_is_corrupt(&ga_text);
4669 else
4670 {
4671 int count = 0;
4672
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004673 // repeat previous character, get the count
Bram Moolenaard96ff162018-02-18 22:13:29 +01004674 for (;;)
4675 {
4676 c = fgetc(fd);
4677 if (!isdigit(c))
4678 break;
4679 count = count * 10 + (c - '0');
4680 }
4681
4682 while (count-- > 0)
4683 {
4684 ga_concat(&ga_text, prev_char);
4685 append_cell(&ga_cell, &cell);
4686 }
4687 }
4688 }
4689 else
4690 {
4691 dump_is_corrupt(&ga_text);
4692 c = fgetc(fd);
4693 }
4694 }
4695
4696 if (ga_text.ga_len > 0)
4697 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004698 // trailing characters after last NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01004699 dump_is_corrupt(&ga_text);
4700 ga_append(&ga_text, NUL);
4701 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4702 ga_text.ga_len, FALSE);
4703 }
4704
4705 ga_clear(&ga_text);
Bram Moolenaar86173482019-10-01 17:02:16 +02004706 ga_clear(&ga_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004707 vim_free(prev_char);
4708
4709 return max_cells;
4710}
4711
4712/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004713 * Return an allocated string with at least "text_width" "=" characters and
4714 * "fname" inserted in the middle.
4715 */
4716 static char_u *
4717get_separator(int text_width, char_u *fname)
4718{
4719 int width = MAX(text_width, curwin->w_width);
4720 char_u *textline;
4721 int fname_size;
4722 char_u *p = fname;
4723 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004724 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004725
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004726 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004727 if (textline == NULL)
4728 return NULL;
4729
4730 fname_size = vim_strsize(fname);
4731 if (fname_size < width - 8)
4732 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004733 // enough room, don't use the full window width
Bram Moolenaar4a696342018-04-05 18:45:26 +02004734 width = MAX(text_width, fname_size + 8);
4735 }
4736 else if (fname_size > width - 8)
4737 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004738 // full name doesn't fit, use only the tail
Bram Moolenaar4a696342018-04-05 18:45:26 +02004739 p = gettail(fname);
4740 fname_size = vim_strsize(p);
4741 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004742 // skip characters until the name fits
Bram Moolenaar4a696342018-04-05 18:45:26 +02004743 while (fname_size > width - 8)
4744 {
4745 p += (*mb_ptr2len)(p);
4746 fname_size = vim_strsize(p);
4747 }
4748
4749 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4750 textline[i] = '=';
4751 textline[i++] = ' ';
4752
4753 STRCPY(textline + i, p);
4754 off = STRLEN(textline);
4755 textline[off] = ' ';
4756 for (i = 1; i < (width - fname_size) / 2; ++i)
4757 textline[off + i] = '=';
4758 textline[off + i] = NUL;
4759
4760 return textline;
4761}
4762
4763/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004764 * Common for "term_dumpdiff()" and "term_dumpload()".
4765 */
4766 static void
4767term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4768{
4769 jobopt_T opt;
Bram Moolenaar87abab92019-06-03 21:14:59 +02004770 buf_T *buf = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004771 char_u buf1[NUMBUFLEN];
4772 char_u buf2[NUMBUFLEN];
4773 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004774 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004775 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004776 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004777 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004778 char_u *textline = NULL;
4779
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004780 // First open the files. If this fails bail out.
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004781 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004782 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004783 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004784 if (fname1 == NULL || (do_diff && fname2 == NULL))
4785 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004786 emsg(_(e_invarg));
Bram Moolenaard96ff162018-02-18 22:13:29 +01004787 return;
4788 }
4789 fd1 = mch_fopen((char *)fname1, READBIN);
4790 if (fd1 == NULL)
4791 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004792 semsg(_(e_notread), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004793 return;
4794 }
4795 if (do_diff)
4796 {
4797 fd2 = mch_fopen((char *)fname2, READBIN);
4798 if (fd2 == NULL)
4799 {
4800 fclose(fd1);
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004801 semsg(_(e_notread), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004802 return;
4803 }
4804 }
4805
4806 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004807 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4808 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4809 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4810 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4811 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004812
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004813 if (opt.jo_term_name == NULL)
4814 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004815 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004816
Bram Moolenaar51e14382019-05-25 20:21:28 +02004817 fname_tofree = alloc(len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004818 if (fname_tofree != NULL)
4819 {
4820 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4821 opt.jo_term_name = fname_tofree;
4822 }
4823 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004824
Bram Moolenaar87abab92019-06-03 21:14:59 +02004825 if (opt.jo_bufnr_buf != NULL)
4826 {
4827 win_T *wp = buf_jump_open_win(opt.jo_bufnr_buf);
4828
4829 // With "bufnr" argument: enter the window with this buffer and make it
4830 // empty.
4831 if (wp == NULL)
4832 semsg(_(e_invarg2), "bufnr");
4833 else
4834 {
4835 buf = curbuf;
4836 while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
4837 ml_delete((linenr_T)1, FALSE);
Bram Moolenaar86173482019-10-01 17:02:16 +02004838 free_scrollback(curbuf->b_term);
Bram Moolenaar87abab92019-06-03 21:14:59 +02004839 redraw_later(NOT_VALID);
4840 }
4841 }
4842 else
4843 // Create a new terminal window.
4844 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
4845
Bram Moolenaard96ff162018-02-18 22:13:29 +01004846 if (buf != NULL && buf->b_term != NULL)
4847 {
4848 int i;
4849 linenr_T bot_lnum;
4850 linenr_T lnum;
4851 term_T *term = buf->b_term;
4852 int width;
4853 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004854 VTermPos cursor_pos1;
4855 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004856
Bram Moolenaar52acb112018-03-18 19:20:22 +01004857 init_default_colors(term);
4858
Bram Moolenaard96ff162018-02-18 22:13:29 +01004859 rettv->vval.v_number = buf->b_fnum;
4860
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004861 // read the files, fill the buffer with the diff
Bram Moolenaar9271d052018-02-25 21:39:46 +01004862 width = read_dump_file(fd1, &cursor_pos1);
4863
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004864 // position the cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01004865 if (cursor_pos1.row >= 0)
4866 {
4867 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4868 coladvance(cursor_pos1.col);
4869 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004870
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004871 // Delete the empty line that was in the empty buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004872 ml_delete(1, FALSE);
4873
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004874 // For term_dumpload() we are done here.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004875 if (!do_diff)
4876 goto theend;
4877
4878 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4879
Bram Moolenaar4a696342018-04-05 18:45:26 +02004880 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004881 if (textline == NULL)
4882 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004883 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4884 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4885 vim_free(textline);
4886
4887 textline = get_separator(width, fname2);
4888 if (textline == NULL)
4889 goto theend;
4890 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4891 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004892 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004893
4894 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004895 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004896 if (width2 > width)
4897 {
4898 vim_free(textline);
4899 textline = alloc(width2 + 1);
4900 if (textline == NULL)
4901 goto theend;
4902 width = width2;
4903 textline[width] = NUL;
4904 }
4905 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4906
4907 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4908 {
4909 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4910 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004911 // bottom part has fewer rows, fill with "-"
Bram Moolenaard96ff162018-02-18 22:13:29 +01004912 for (i = 0; i < width; ++i)
4913 textline[i] = '-';
4914 }
4915 else
4916 {
4917 char_u *line1;
4918 char_u *line2;
4919 char_u *p1;
4920 char_u *p2;
4921 int col;
4922 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4923 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4924 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4925 ->sb_cells;
4926
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004927 // Make a copy, getting the second line will invalidate it.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004928 line1 = vim_strsave(ml_get(lnum));
4929 if (line1 == NULL)
4930 break;
4931 p1 = line1;
4932
4933 line2 = ml_get(lnum + bot_lnum);
4934 p2 = line2;
4935 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4936 {
4937 int len1 = utfc_ptr2len(p1);
4938 int len2 = utfc_ptr2len(p2);
4939
4940 textline[col] = ' ';
4941 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004942 // text differs
Bram Moolenaard96ff162018-02-18 22:13:29 +01004943 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004944 else if (lnum == cursor_pos1.row + 1
4945 && col == cursor_pos1.col
4946 && (cursor_pos1.row != cursor_pos2.row
4947 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004948 // cursor in first but not in second
Bram Moolenaar9271d052018-02-25 21:39:46 +01004949 textline[col] = '>';
4950 else if (lnum == cursor_pos2.row + 1
4951 && col == cursor_pos2.col
4952 && (cursor_pos1.row != cursor_pos2.row
4953 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004954 // cursor in second but not in first
Bram Moolenaar9271d052018-02-25 21:39:46 +01004955 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004956 else if (cellattr1 != NULL && cellattr2 != NULL)
4957 {
4958 if ((cellattr1 + col)->width
4959 != (cellattr2 + col)->width)
4960 textline[col] = 'w';
4961 else if (!same_color(&(cellattr1 + col)->fg,
4962 &(cellattr2 + col)->fg))
4963 textline[col] = 'f';
4964 else if (!same_color(&(cellattr1 + col)->bg,
4965 &(cellattr2 + col)->bg))
4966 textline[col] = 'b';
4967 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4968 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4969 textline[col] = 'a';
4970 }
4971 p1 += len1;
4972 p2 += len2;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004973 // TODO: handle different width
Bram Moolenaard96ff162018-02-18 22:13:29 +01004974 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004975
4976 while (col < width)
4977 {
4978 if (*p1 == NUL && *p2 == NUL)
4979 textline[col] = '?';
4980 else if (*p1 == NUL)
4981 {
4982 textline[col] = '+';
4983 p2 += utfc_ptr2len(p2);
4984 }
4985 else
4986 {
4987 textline[col] = '-';
4988 p1 += utfc_ptr2len(p1);
4989 }
4990 ++col;
4991 }
Bram Moolenaar81aa0f52019-02-14 23:23:19 +01004992
4993 vim_free(line1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004994 }
4995 if (add_empty_scrollback(term, &term->tl_default_color,
4996 term->tl_top_diff_rows) == OK)
4997 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
4998 ++bot_lnum;
4999 }
5000
5001 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
5002 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005003 // bottom part has more rows, fill with "+"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005004 for (i = 0; i < width; ++i)
5005 textline[i] = '+';
5006 if (add_empty_scrollback(term, &term->tl_default_color,
5007 term->tl_top_diff_rows) == OK)
5008 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5009 ++lnum;
5010 ++bot_lnum;
5011 }
5012
5013 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005014
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005015 // looks better without wrapping
Bram Moolenaar4a696342018-04-05 18:45:26 +02005016 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005017 }
5018
5019theend:
5020 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005021 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005022 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005023 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005024 fclose(fd2);
5025}
5026
5027/*
5028 * If the current buffer shows the output of term_dumpdiff(), swap the top and
5029 * bottom files.
5030 * Return FAIL when this is not possible.
5031 */
5032 int
5033term_swap_diff()
5034{
5035 term_T *term = curbuf->b_term;
5036 linenr_T line_count;
5037 linenr_T top_rows;
5038 linenr_T bot_rows;
5039 linenr_T bot_start;
5040 linenr_T lnum;
5041 char_u *p;
5042 sb_line_T *sb_line;
5043
5044 if (term == NULL
5045 || !term_is_finished(curbuf)
5046 || term->tl_top_diff_rows == 0
5047 || term->tl_scrollback.ga_len == 0)
5048 return FAIL;
5049
5050 line_count = curbuf->b_ml.ml_line_count;
5051 top_rows = term->tl_top_diff_rows;
5052 bot_rows = term->tl_bot_diff_rows;
5053 bot_start = line_count - bot_rows;
5054 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5055
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005056 // move lines from top to above the bottom part
Bram Moolenaard96ff162018-02-18 22:13:29 +01005057 for (lnum = 1; lnum <= top_rows; ++lnum)
5058 {
5059 p = vim_strsave(ml_get(1));
5060 if (p == NULL)
5061 return OK;
5062 ml_append(bot_start, p, 0, FALSE);
5063 ml_delete(1, FALSE);
5064 vim_free(p);
5065 }
5066
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005067 // move lines from bottom to the top
Bram Moolenaard96ff162018-02-18 22:13:29 +01005068 for (lnum = 1; lnum <= bot_rows; ++lnum)
5069 {
5070 p = vim_strsave(ml_get(bot_start + lnum));
5071 if (p == NULL)
5072 return OK;
5073 ml_delete(bot_start + lnum, FALSE);
5074 ml_append(lnum - 1, p, 0, FALSE);
5075 vim_free(p);
5076 }
5077
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005078 // move top title to bottom
5079 p = vim_strsave(ml_get(bot_rows + 1));
5080 if (p == NULL)
5081 return OK;
5082 ml_append(line_count - top_rows - 1, p, 0, FALSE);
5083 ml_delete(bot_rows + 1, FALSE);
5084 vim_free(p);
5085
5086 // move bottom title to top
5087 p = vim_strsave(ml_get(line_count - top_rows));
5088 if (p == NULL)
5089 return OK;
5090 ml_delete(line_count - top_rows, FALSE);
5091 ml_append(bot_rows, p, 0, FALSE);
5092 vim_free(p);
5093
Bram Moolenaard96ff162018-02-18 22:13:29 +01005094 if (top_rows == bot_rows)
5095 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005096 // rows counts are equal, can swap cell properties
Bram Moolenaard96ff162018-02-18 22:13:29 +01005097 for (lnum = 0; lnum < top_rows; ++lnum)
5098 {
5099 sb_line_T temp;
5100
5101 temp = *(sb_line + lnum);
5102 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
5103 *(sb_line + bot_start + lnum) = temp;
5104 }
5105 }
5106 else
5107 {
5108 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005109 sb_line_T *temp = alloc(size);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005110
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005111 // need to copy cell properties into temp memory
Bram Moolenaard96ff162018-02-18 22:13:29 +01005112 if (temp != NULL)
5113 {
5114 mch_memmove(temp, term->tl_scrollback.ga_data, size);
5115 mch_memmove(term->tl_scrollback.ga_data,
5116 temp + bot_start,
5117 sizeof(sb_line_T) * bot_rows);
5118 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
5119 temp + top_rows,
5120 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
5121 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
5122 + line_count - top_rows,
5123 temp,
5124 sizeof(sb_line_T) * top_rows);
5125 vim_free(temp);
5126 }
5127 }
5128
5129 term->tl_top_diff_rows = bot_rows;
5130 term->tl_bot_diff_rows = top_rows;
5131
5132 update_screen(NOT_VALID);
5133 return OK;
5134}
5135
5136/*
5137 * "term_dumpdiff(filename, filename, options)" function
5138 */
5139 void
5140f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
5141{
5142 term_load_dump(argvars, rettv, TRUE);
5143}
5144
5145/*
5146 * "term_dumpload(filename, options)" function
5147 */
5148 void
5149f_term_dumpload(typval_T *argvars, typval_T *rettv)
5150{
5151 term_load_dump(argvars, rettv, FALSE);
5152}
5153
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005154/*
5155 * "term_getaltscreen(buf)" function
5156 */
5157 void
5158f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
5159{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005160 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005161
5162 if (buf == NULL)
5163 return;
5164 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
5165}
5166
5167/*
5168 * "term_getattr(attr, name)" function
5169 */
5170 void
5171f_term_getattr(typval_T *argvars, typval_T *rettv)
5172{
5173 int attr;
5174 size_t i;
5175 char_u *name;
5176
5177 static struct {
5178 char *name;
5179 int attr;
5180 } attrs[] = {
5181 {"bold", HL_BOLD},
5182 {"italic", HL_ITALIC},
5183 {"underline", HL_UNDERLINE},
5184 {"strike", HL_STRIKETHROUGH},
5185 {"reverse", HL_INVERSE},
5186 };
5187
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005188 attr = tv_get_number(&argvars[0]);
5189 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005190 if (name == NULL)
5191 return;
5192
Bram Moolenaar7ee80f72019-09-08 20:55:06 +02005193 if (attr > HL_ALL)
5194 attr = syn_attr2attr(attr);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005195 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
5196 if (STRCMP(name, attrs[i].name) == 0)
5197 {
5198 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
5199 break;
5200 }
5201}
5202
5203/*
5204 * "term_getcursor(buf)" function
5205 */
5206 void
5207f_term_getcursor(typval_T *argvars, typval_T *rettv)
5208{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005209 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005210 term_T *term;
5211 list_T *l;
5212 dict_T *d;
5213
5214 if (rettv_list_alloc(rettv) == FAIL)
5215 return;
5216 if (buf == NULL)
5217 return;
5218 term = buf->b_term;
5219
5220 l = rettv->vval.v_list;
5221 list_append_number(l, term->tl_cursor_pos.row + 1);
5222 list_append_number(l, term->tl_cursor_pos.col + 1);
5223
5224 d = dict_alloc();
5225 if (d != NULL)
5226 {
Bram Moolenaare0be1672018-07-08 16:50:37 +02005227 dict_add_number(d, "visible", term->tl_cursor_visible);
5228 dict_add_number(d, "blink", blink_state_is_inverted()
5229 ? !term->tl_cursor_blink : term->tl_cursor_blink);
5230 dict_add_number(d, "shape", term->tl_cursor_shape);
5231 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005232 list_append_dict(l, d);
5233 }
5234}
5235
5236/*
5237 * "term_getjob(buf)" function
5238 */
5239 void
5240f_term_getjob(typval_T *argvars, typval_T *rettv)
5241{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005242 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005243
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005244 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005245 {
5246 rettv->v_type = VAR_SPECIAL;
5247 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005248 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005249 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005250
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005251 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005252 rettv->vval.v_job = buf->b_term->tl_job;
5253 if (rettv->vval.v_job != NULL)
5254 ++rettv->vval.v_job->jv_refcount;
5255}
5256
5257 static int
5258get_row_number(typval_T *tv, term_T *term)
5259{
5260 if (tv->v_type == VAR_STRING
5261 && tv->vval.v_string != NULL
5262 && STRCMP(tv->vval.v_string, ".") == 0)
5263 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005264 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005265}
5266
5267/*
5268 * "term_getline(buf, row)" function
5269 */
5270 void
5271f_term_getline(typval_T *argvars, typval_T *rettv)
5272{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005273 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005274 term_T *term;
5275 int row;
5276
5277 rettv->v_type = VAR_STRING;
5278 if (buf == NULL)
5279 return;
5280 term = buf->b_term;
5281 row = get_row_number(&argvars[1], term);
5282
5283 if (term->tl_vterm == NULL)
5284 {
5285 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
5286
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005287 // vterm is finished, get the text from the buffer
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005288 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
5289 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
5290 }
5291 else
5292 {
5293 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
5294 VTermRect rect;
5295 int len;
5296 char_u *p;
5297
5298 if (row < 0 || row >= term->tl_rows)
5299 return;
5300 len = term->tl_cols * MB_MAXBYTES + 1;
5301 p = alloc(len);
5302 if (p == NULL)
5303 return;
5304 rettv->vval.v_string = p;
5305
5306 rect.start_col = 0;
5307 rect.end_col = term->tl_cols;
5308 rect.start_row = row;
5309 rect.end_row = row + 1;
5310 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
5311 }
5312}
5313
5314/*
5315 * "term_getscrolled(buf)" function
5316 */
5317 void
5318f_term_getscrolled(typval_T *argvars, typval_T *rettv)
5319{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005320 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005321
5322 if (buf == NULL)
5323 return;
5324 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
5325}
5326
5327/*
5328 * "term_getsize(buf)" function
5329 */
5330 void
5331f_term_getsize(typval_T *argvars, typval_T *rettv)
5332{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005333 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005334 list_T *l;
5335
5336 if (rettv_list_alloc(rettv) == FAIL)
5337 return;
5338 if (buf == NULL)
5339 return;
5340
5341 l = rettv->vval.v_list;
5342 list_append_number(l, buf->b_term->tl_rows);
5343 list_append_number(l, buf->b_term->tl_cols);
5344}
5345
5346/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005347 * "term_setsize(buf, rows, cols)" function
5348 */
5349 void
5350f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5351{
5352 buf_T *buf = term_get_buf(argvars, "term_setsize()");
5353 term_T *term;
5354 varnumber_T rows, cols;
5355
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005356 if (buf == NULL)
5357 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005358 emsg(_("E955: Not a terminal buffer"));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005359 return;
5360 }
5361 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02005362 return;
5363 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005364 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005365 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005366 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005367 cols = cols <= 0 ? term->tl_cols : cols;
5368 vterm_set_size(term->tl_vterm, rows, cols);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005369 // handle_resize() will resize the windows
Bram Moolenaara42d3632018-04-14 17:05:38 +02005370
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005371 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaara42d3632018-04-14 17:05:38 +02005372 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
5373 term_report_winsize(term, term->tl_rows, term->tl_cols);
5374}
5375
5376/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005377 * "term_getstatus(buf)" function
5378 */
5379 void
5380f_term_getstatus(typval_T *argvars, typval_T *rettv)
5381{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005382 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005383 term_T *term;
5384 char_u val[100];
5385
5386 rettv->v_type = VAR_STRING;
5387 if (buf == NULL)
5388 return;
5389 term = buf->b_term;
5390
5391 if (term_job_running(term))
5392 STRCPY(val, "running");
5393 else
5394 STRCPY(val, "finished");
5395 if (term->tl_normal_mode)
5396 STRCAT(val, ",normal");
5397 rettv->vval.v_string = vim_strsave(val);
5398}
5399
5400/*
5401 * "term_gettitle(buf)" function
5402 */
5403 void
5404f_term_gettitle(typval_T *argvars, typval_T *rettv)
5405{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005406 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005407
5408 rettv->v_type = VAR_STRING;
5409 if (buf == NULL)
5410 return;
5411
5412 if (buf->b_term->tl_title != NULL)
5413 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
5414}
5415
5416/*
5417 * "term_gettty(buf)" function
5418 */
5419 void
5420f_term_gettty(typval_T *argvars, typval_T *rettv)
5421{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005422 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02005423 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005424 int num = 0;
5425
5426 rettv->v_type = VAR_STRING;
5427 if (buf == NULL)
5428 return;
5429 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005430 num = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005431
5432 switch (num)
5433 {
5434 case 0:
5435 if (buf->b_term->tl_job != NULL)
5436 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005437 break;
5438 case 1:
5439 if (buf->b_term->tl_job != NULL)
5440 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005441 break;
5442 default:
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005443 semsg(_(e_invarg2), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005444 return;
5445 }
5446 if (p != NULL)
5447 rettv->vval.v_string = vim_strsave(p);
5448}
5449
5450/*
5451 * "term_list()" function
5452 */
5453 void
5454f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
5455{
5456 term_T *tp;
5457 list_T *l;
5458
5459 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
5460 return;
5461
5462 l = rettv->vval.v_list;
5463 for (tp = first_term; tp != NULL; tp = tp->tl_next)
5464 if (tp != NULL && tp->tl_buffer != NULL)
5465 if (list_append_number(l,
5466 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
5467 return;
5468}
5469
5470/*
5471 * "term_scrape(buf, row)" function
5472 */
5473 void
5474f_term_scrape(typval_T *argvars, typval_T *rettv)
5475{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005476 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005477 VTermScreen *screen = NULL;
5478 VTermPos pos;
5479 list_T *l;
5480 term_T *term;
5481 char_u *p;
5482 sb_line_T *line;
5483
5484 if (rettv_list_alloc(rettv) == FAIL)
5485 return;
5486 if (buf == NULL)
5487 return;
5488 term = buf->b_term;
5489
5490 l = rettv->vval.v_list;
5491 pos.row = get_row_number(&argvars[1], term);
5492
5493 if (term->tl_vterm != NULL)
5494 {
5495 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01005496 if (screen == NULL) // can't really happen
5497 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005498 p = NULL;
5499 line = NULL;
5500 }
5501 else
5502 {
5503 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
5504
5505 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
5506 return;
5507 p = ml_get_buf(buf, lnum + 1, FALSE);
5508 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
5509 }
5510
5511 for (pos.col = 0; pos.col < term->tl_cols; )
5512 {
5513 dict_T *dcell;
5514 int width;
5515 VTermScreenCellAttrs attrs;
5516 VTermColor fg, bg;
5517 char_u rgb[8];
5518 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5519 int off = 0;
5520 int i;
5521
5522 if (screen == NULL)
5523 {
5524 cellattr_T *cellattr;
5525 int len;
5526
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005527 // vterm has finished, get the cell from scrollback
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005528 if (pos.col >= line->sb_cols)
5529 break;
5530 cellattr = line->sb_cells + pos.col;
5531 width = cellattr->width;
5532 attrs = cellattr->attrs;
5533 fg = cellattr->fg;
5534 bg = cellattr->bg;
Bram Moolenaar1614a142019-10-06 22:00:13 +02005535 len = mb_ptr2len(p);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005536 mch_memmove(mbs, p, len);
5537 mbs[len] = NUL;
5538 p += len;
5539 }
5540 else
5541 {
5542 VTermScreenCell cell;
5543 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5544 break;
5545 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5546 {
5547 if (cell.chars[i] == 0)
5548 break;
5549 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5550 }
5551 mbs[off] = NUL;
5552 width = cell.width;
5553 attrs = cell.attrs;
5554 fg = cell.fg;
5555 bg = cell.bg;
5556 }
5557 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01005558 if (dcell == NULL)
5559 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005560 list_append_dict(l, dcell);
5561
Bram Moolenaare0be1672018-07-08 16:50:37 +02005562 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005563
5564 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5565 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005566 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005567 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5568 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005569 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005570
Bram Moolenaare0be1672018-07-08 16:50:37 +02005571 dict_add_number(dcell, "attr", cell2attr(attrs, fg, bg));
5572 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005573
5574 ++pos.col;
5575 if (width == 2)
5576 ++pos.col;
5577 }
5578}
5579
5580/*
5581 * "term_sendkeys(buf, keys)" function
5582 */
5583 void
5584f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5585{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005586 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005587 char_u *msg;
5588 term_T *term;
5589
5590 rettv->v_type = VAR_UNKNOWN;
5591 if (buf == NULL)
5592 return;
5593
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005594 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005595 if (msg == NULL)
5596 return;
5597 term = buf->b_term;
5598 if (term->tl_vterm == NULL)
5599 return;
5600
5601 while (*msg != NUL)
5602 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02005603 int c;
5604
5605 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
5606 {
5607 c = TO_SPECIAL(msg[1], msg[2]);
5608 msg += 3;
5609 }
5610 else
5611 {
5612 c = PTR2CHAR(msg);
5613 msg += MB_CPTR2LEN(msg);
5614 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01005615 send_keys_to_term(term, c, 0, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005616 }
5617}
5618
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005619#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5620/*
5621 * "term_getansicolors(buf)" function
5622 */
5623 void
5624f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5625{
5626 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5627 term_T *term;
5628 VTermState *state;
5629 VTermColor color;
5630 char_u hexbuf[10];
5631 int index;
5632 list_T *list;
5633
5634 if (rettv_list_alloc(rettv) == FAIL)
5635 return;
5636
5637 if (buf == NULL)
5638 return;
5639 term = buf->b_term;
5640 if (term->tl_vterm == NULL)
5641 return;
5642
5643 list = rettv->vval.v_list;
5644 state = vterm_obtain_state(term->tl_vterm);
5645 for (index = 0; index < 16; index++)
5646 {
5647 vterm_state_get_palette_color(state, index, &color);
5648 sprintf((char *)hexbuf, "#%02x%02x%02x",
5649 color.red, color.green, color.blue);
5650 if (list_append_string(list, hexbuf, 7) == FAIL)
5651 return;
5652 }
5653}
5654
5655/*
5656 * "term_setansicolors(buf, list)" function
5657 */
5658 void
5659f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5660{
5661 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5662 term_T *term;
5663
5664 if (buf == NULL)
5665 return;
5666 term = buf->b_term;
5667 if (term->tl_vterm == NULL)
5668 return;
5669
5670 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5671 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005672 emsg(_(e_listreq));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005673 return;
5674 }
5675
5676 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005677 emsg(_(e_invarg));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005678}
5679#endif
5680
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005681/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005682 * "term_setapi(buf, api)" function
5683 */
5684 void
5685f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
5686{
5687 buf_T *buf = term_get_buf(argvars, "term_setapi()");
5688 term_T *term;
5689 char_u *api;
5690
5691 if (buf == NULL)
5692 return;
5693 term = buf->b_term;
5694 vim_free(term->tl_api);
5695 api = tv_get_string_chk(&argvars[1]);
5696 if (api != NULL)
5697 term->tl_api = vim_strsave(api);
5698 else
5699 term->tl_api = NULL;
5700}
5701
5702/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005703 * "term_setrestore(buf, command)" function
5704 */
5705 void
5706f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5707{
5708#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005709 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005710 term_T *term;
5711 char_u *cmd;
5712
5713 if (buf == NULL)
5714 return;
5715 term = buf->b_term;
5716 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005717 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005718 if (cmd != NULL)
5719 term->tl_command = vim_strsave(cmd);
5720 else
5721 term->tl_command = NULL;
5722#endif
5723}
5724
5725/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005726 * "term_setkill(buf, how)" function
5727 */
5728 void
5729f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5730{
5731 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5732 term_T *term;
5733 char_u *how;
5734
5735 if (buf == NULL)
5736 return;
5737 term = buf->b_term;
5738 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005739 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005740 if (how != NULL)
5741 term->tl_kill = vim_strsave(how);
5742 else
5743 term->tl_kill = NULL;
5744}
5745
5746/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005747 * "term_start(command, options)" function
5748 */
5749 void
5750f_term_start(typval_T *argvars, typval_T *rettv)
5751{
5752 jobopt_T opt;
5753 buf_T *buf;
5754
5755 init_job_options(&opt);
5756 if (argvars[1].v_type != VAR_UNKNOWN
5757 && get_job_options(&argvars[1], &opt,
5758 JO_TIMEOUT_ALL + JO_STOPONEXIT
5759 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5760 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5761 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5762 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005763 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005764 + JO2_NORESTORE + JO2_TERM_KILL
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005765 + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005766 return;
5767
Bram Moolenaar13568252018-03-16 20:46:58 +01005768 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005769
5770 if (buf != NULL && buf->b_term != NULL)
5771 rettv->vval.v_number = buf->b_fnum;
5772}
5773
5774/*
5775 * "term_wait" function
5776 */
5777 void
5778f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5779{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005780 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005781
5782 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005783 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005784 if (buf->b_term->tl_job == NULL)
5785 {
5786 ch_log(NULL, "term_wait(): no job to wait for");
5787 return;
5788 }
5789 if (buf->b_term->tl_job->jv_channel == NULL)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005790 // channel is closed, nothing to do
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005791 return;
5792
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005793 // Get the job status, this will detect a job that finished.
Bram Moolenaara15ef452018-02-09 16:46:00 +01005794 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005795 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5796 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005797 // The job is dead, keep reading channel I/O until the channel is
5798 // closed. buf->b_term may become NULL if the terminal was closed while
5799 // waiting.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005800 ch_log(NULL, "term_wait(): waiting for channel to close");
5801 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5802 {
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005803 term_flush_messages();
5804
Bram Moolenaard45aa552018-05-21 22:50:29 +02005805 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01005806 if (!buf_valid(buf))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005807 // If the terminal is closed when the channel is closed the
5808 // buffer disappears.
Bram Moolenaare5182262017-11-19 15:05:44 +01005809 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005810 }
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005811
5812 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005813 }
5814 else
5815 {
5816 long wait = 10L;
5817
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005818 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005819
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005820 // Wait for some time for any channel I/O.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005821 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005822 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005823 ui_delay(wait, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005824
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005825 // Flushing messages on channels is hopefully sufficient.
5826 // TODO: is there a better way?
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005827 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005828 }
5829}
5830
5831/*
5832 * Called when a channel has sent all the lines to a terminal.
5833 * Send a CTRL-D to mark the end of the text.
5834 */
5835 void
5836term_send_eof(channel_T *ch)
5837{
5838 term_T *term;
5839
5840 for (term = first_term; term != NULL; term = term->tl_next)
5841 if (term->tl_job == ch->ch_job)
5842 {
5843 if (term->tl_eof_chars != NULL)
5844 {
5845 channel_send(ch, PART_IN, term->tl_eof_chars,
5846 (int)STRLEN(term->tl_eof_chars), NULL);
5847 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5848 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01005849# ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005850 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005851 // Default: CTRL-D
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005852 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5853# endif
5854 }
5855}
5856
Bram Moolenaar113e1072019-01-20 15:30:40 +01005857#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005858 job_T *
5859term_getjob(term_T *term)
5860{
5861 return term != NULL ? term->tl_job : NULL;
5862}
Bram Moolenaar113e1072019-01-20 15:30:40 +01005863#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005864
Bram Moolenaar4f974752019-02-17 17:44:42 +01005865# if defined(MSWIN) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005866
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005867///////////////////////////////////////
5868// 2. MS-Windows implementation.
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005869#ifdef PROTO
5870typedef int COORD;
5871typedef int DWORD;
5872typedef int HANDLE;
5873typedef int *DWORD_PTR;
5874typedef int HPCON;
5875typedef int HRESULT;
5876typedef int LPPROC_THREAD_ATTRIBUTE_LIST;
Bram Moolenaarad3ec762019-04-21 00:00:13 +02005877typedef int SIZE_T;
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005878typedef int PSIZE_T;
5879typedef int PVOID;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01005880typedef int BOOL;
5881# define WINAPI
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005882#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005883
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005884HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
5885HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
5886HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
Bram Moolenaar48773f12019-02-12 21:46:46 +01005887BOOL (WINAPI *pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
5888BOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
5889void (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005890
5891 static int
5892dyn_conpty_init(int verbose)
5893{
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005894 static HMODULE hKerneldll = NULL;
5895 int i;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005896 static struct
5897 {
5898 char *name;
5899 FARPROC *ptr;
5900 } conpty_entry[] =
5901 {
5902 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
5903 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
5904 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
5905 {"InitializeProcThreadAttributeList",
5906 (FARPROC*)&pInitializeProcThreadAttributeList},
5907 {"UpdateProcThreadAttribute",
5908 (FARPROC*)&pUpdateProcThreadAttribute},
5909 {"DeleteProcThreadAttributeList",
5910 (FARPROC*)&pDeleteProcThreadAttributeList},
5911 {NULL, NULL}
5912 };
5913
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01005914 if (!has_conpty_working())
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005915 {
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005916 if (verbose)
5917 emsg(_("E982: ConPTY is not available"));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005918 return FAIL;
5919 }
5920
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005921 // No need to initialize twice.
5922 if (hKerneldll)
5923 return OK;
5924
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005925 hKerneldll = vimLoadLib("kernel32.dll");
5926 for (i = 0; conpty_entry[i].name != NULL
5927 && conpty_entry[i].ptr != NULL; ++i)
5928 {
5929 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
5930 conpty_entry[i].name)) == NULL)
5931 {
5932 if (verbose)
5933 semsg(_(e_loadfunc), conpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005934 hKerneldll = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005935 return FAIL;
5936 }
5937 }
5938
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005939 return OK;
5940}
5941
5942 static int
5943conpty_term_and_job_init(
5944 term_T *term,
5945 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02005946 char **argv UNUSED,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005947 jobopt_T *opt,
5948 jobopt_T *orig_opt)
5949{
5950 WCHAR *cmd_wchar = NULL;
5951 WCHAR *cmd_wchar_copy = NULL;
5952 WCHAR *cwd_wchar = NULL;
5953 WCHAR *env_wchar = NULL;
5954 channel_T *channel = NULL;
5955 job_T *job = NULL;
5956 HANDLE jo = NULL;
5957 garray_T ga_cmd, ga_env;
5958 char_u *cmd = NULL;
5959 HRESULT hr;
5960 COORD consize;
5961 SIZE_T breq;
5962 PROCESS_INFORMATION proc_info;
5963 HANDLE i_theirs = NULL;
5964 HANDLE o_theirs = NULL;
5965 HANDLE i_ours = NULL;
5966 HANDLE o_ours = NULL;
5967
5968 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5969 ga_init2(&ga_env, (int)sizeof(char*), 20);
5970
5971 if (argvar->v_type == VAR_STRING)
5972 {
5973 cmd = argvar->vval.v_string;
5974 }
5975 else if (argvar->v_type == VAR_LIST)
5976 {
5977 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
5978 goto failed;
5979 cmd = ga_cmd.ga_data;
5980 }
5981 if (cmd == NULL || *cmd == NUL)
5982 {
5983 emsg(_(e_invarg));
5984 goto failed;
5985 }
5986
5987 term->tl_arg0_cmd = vim_strsave(cmd);
5988
5989 cmd_wchar = enc_to_utf16(cmd, NULL);
5990
5991 if (cmd_wchar != NULL)
5992 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005993 // Request by CreateProcessW
5994 breq = wcslen(cmd_wchar) + 1 + 1; // Addition of NUL by API
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005995 cmd_wchar_copy = ALLOC_MULT(WCHAR, breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005996 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
5997 }
5998
5999 ga_clear(&ga_cmd);
6000 if (cmd_wchar == NULL)
6001 goto failed;
6002 if (opt->jo_cwd != NULL)
6003 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
6004
6005 win32_build_env(opt->jo_env, &ga_env, TRUE);
6006 env_wchar = ga_env.ga_data;
6007
6008 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
6009 goto failed;
6010 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
6011 goto failed;
6012
6013 consize.X = term->tl_cols;
6014 consize.Y = term->tl_rows;
6015 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
6016 &term->tl_conpty);
6017 if (FAILED(hr))
6018 goto failed;
6019
6020 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
6021
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006022 // Set up pipe inheritance safely: Vista or later.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006023 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006024 term->tl_siex.lpAttributeList = alloc(breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006025 if (!term->tl_siex.lpAttributeList)
6026 goto failed;
6027 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
6028 0, &breq))
6029 goto failed;
6030 if (!pUpdateProcThreadAttribute(
6031 term->tl_siex.lpAttributeList, 0,
6032 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
6033 sizeof(HPCON), NULL, NULL))
6034 goto failed;
6035
6036 channel = add_channel();
6037 if (channel == NULL)
6038 goto failed;
6039
6040 job = job_alloc();
6041 if (job == NULL)
6042 goto failed;
6043 if (argvar->v_type == VAR_STRING)
6044 {
6045 int argc;
6046
6047 build_argv_from_string(cmd, &job->jv_argv, &argc);
6048 }
6049 else
6050 {
6051 int argc;
6052
6053 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6054 }
6055
6056 if (opt->jo_set & JO_IN_BUF)
6057 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6058
6059 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
6060 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
6061 | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP
6062 | CREATE_DEFAULT_ERROR_MODE,
6063 env_wchar, cwd_wchar,
6064 &term->tl_siex.StartupInfo, &proc_info))
6065 goto failed;
6066
6067 CloseHandle(i_theirs);
6068 CloseHandle(o_theirs);
6069
6070 channel_set_pipes(channel,
6071 (sock_T)i_ours,
6072 (sock_T)o_ours,
6073 (sock_T)o_ours);
6074
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006075 // Write lines with CR instead of NL.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006076 channel->ch_write_text_mode = TRUE;
6077
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006078 // Use to explicitly delete anonymous pipe handle.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006079 channel->ch_anonymous_pipe = TRUE;
6080
6081 jo = CreateJobObject(NULL, NULL);
6082 if (jo == NULL)
6083 goto failed;
6084
6085 if (!AssignProcessToJobObject(jo, proc_info.hProcess))
6086 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006087 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006088 CloseHandle(jo);
6089 jo = NULL;
6090 }
6091
6092 ResumeThread(proc_info.hThread);
6093 CloseHandle(proc_info.hThread);
6094
6095 vim_free(cmd_wchar);
6096 vim_free(cmd_wchar_copy);
6097 vim_free(cwd_wchar);
6098 vim_free(env_wchar);
6099
6100 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6101 goto failed;
6102
6103#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6104 if (opt->jo_set2 & JO2_ANSI_COLORS)
6105 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6106 else
6107 init_vterm_ansi_colors(term->tl_vterm);
6108#endif
6109
6110 channel_set_job(channel, job, opt);
6111 job_set_options(job, opt);
6112
6113 job->jv_channel = channel;
6114 job->jv_proc_info = proc_info;
6115 job->jv_job_object = jo;
6116 job->jv_status = JOB_STARTED;
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006117 job->jv_tty_type = vim_strsave((char_u *)"conpty");
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006118 ++job->jv_refcount;
6119 term->tl_job = job;
6120
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006121 // Redirecting stdout and stderr doesn't work at the job level. Instead
6122 // open the file here and handle it in. opt->jo_io was changed in
6123 // setup_job_options(), use the original flags here.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006124 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6125 {
6126 char_u *fname = opt->jo_io_name[PART_OUT];
6127
6128 ch_log(channel, "Opening output file %s", fname);
6129 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6130 if (term->tl_out_fd == NULL)
6131 semsg(_(e_notopen), fname);
6132 }
6133
6134 return OK;
6135
6136failed:
6137 ga_clear(&ga_cmd);
6138 ga_clear(&ga_env);
6139 vim_free(cmd_wchar);
6140 vim_free(cmd_wchar_copy);
6141 vim_free(cwd_wchar);
6142 if (channel != NULL)
6143 channel_clear(channel);
6144 if (job != NULL)
6145 {
6146 job->jv_channel = NULL;
6147 job_cleanup(job);
6148 }
6149 term->tl_job = NULL;
6150 if (jo != NULL)
6151 CloseHandle(jo);
6152
6153 if (term->tl_siex.lpAttributeList != NULL)
6154 {
6155 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6156 vim_free(term->tl_siex.lpAttributeList);
6157 }
6158 term->tl_siex.lpAttributeList = NULL;
6159 if (o_theirs != NULL)
6160 CloseHandle(o_theirs);
6161 if (o_ours != NULL)
6162 CloseHandle(o_ours);
6163 if (i_ours != NULL)
6164 CloseHandle(i_ours);
6165 if (i_theirs != NULL)
6166 CloseHandle(i_theirs);
6167 if (term->tl_conpty != NULL)
6168 pClosePseudoConsole(term->tl_conpty);
6169 term->tl_conpty = NULL;
6170 return FAIL;
6171}
6172
6173 static void
6174conpty_term_report_winsize(term_T *term, int rows, int cols)
6175{
6176 COORD consize;
6177
6178 consize.X = cols;
6179 consize.Y = rows;
6180 pResizePseudoConsole(term->tl_conpty, consize);
6181}
6182
Bram Moolenaar840d16f2019-09-10 21:27:18 +02006183 static void
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006184term_free_conpty(term_T *term)
6185{
6186 if (term->tl_siex.lpAttributeList != NULL)
6187 {
6188 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6189 vim_free(term->tl_siex.lpAttributeList);
6190 }
6191 term->tl_siex.lpAttributeList = NULL;
6192 if (term->tl_conpty != NULL)
6193 pClosePseudoConsole(term->tl_conpty);
6194 term->tl_conpty = NULL;
6195}
6196
6197 int
6198use_conpty(void)
6199{
6200 return has_conpty;
6201}
6202
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006203# ifndef PROTO
6204
6205#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
6206#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01006207#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006208
6209void* (*winpty_config_new)(UINT64, void*);
6210void* (*winpty_open)(void*, void*);
6211void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
6212BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
6213void (*winpty_config_set_mouse_mode)(void*, int);
6214void (*winpty_config_set_initial_size)(void*, int, int);
6215LPCWSTR (*winpty_conin_name)(void*);
6216LPCWSTR (*winpty_conout_name)(void*);
6217LPCWSTR (*winpty_conerr_name)(void*);
6218void (*winpty_free)(void*);
6219void (*winpty_config_free)(void*);
6220void (*winpty_spawn_config_free)(void*);
6221void (*winpty_error_free)(void*);
6222LPCWSTR (*winpty_error_msg)(void*);
6223BOOL (*winpty_set_size)(void*, int, int, void*);
6224HANDLE (*winpty_agent_process)(void*);
6225
6226#define WINPTY_DLL "winpty.dll"
6227
6228static HINSTANCE hWinPtyDLL = NULL;
6229# endif
6230
6231 static int
6232dyn_winpty_init(int verbose)
6233{
6234 int i;
6235 static struct
6236 {
6237 char *name;
6238 FARPROC *ptr;
6239 } winpty_entry[] =
6240 {
6241 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
6242 {"winpty_config_free", (FARPROC*)&winpty_config_free},
6243 {"winpty_config_new", (FARPROC*)&winpty_config_new},
6244 {"winpty_config_set_mouse_mode",
6245 (FARPROC*)&winpty_config_set_mouse_mode},
6246 {"winpty_config_set_initial_size",
6247 (FARPROC*)&winpty_config_set_initial_size},
6248 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
6249 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
6250 {"winpty_error_free", (FARPROC*)&winpty_error_free},
6251 {"winpty_free", (FARPROC*)&winpty_free},
6252 {"winpty_open", (FARPROC*)&winpty_open},
6253 {"winpty_spawn", (FARPROC*)&winpty_spawn},
6254 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
6255 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
6256 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
6257 {"winpty_set_size", (FARPROC*)&winpty_set_size},
6258 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
6259 {NULL, NULL}
6260 };
6261
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006262 // No need to initialize twice.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006263 if (hWinPtyDLL)
6264 return OK;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006265 // Load winpty.dll, prefer using the 'winptydll' option, fall back to just
6266 // winpty.dll.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006267 if (*p_winptydll != NUL)
6268 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
6269 if (!hWinPtyDLL)
6270 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
6271 if (!hWinPtyDLL)
6272 {
6273 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006274 semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006275 : (char_u *)WINPTY_DLL);
6276 return FAIL;
6277 }
6278 for (i = 0; winpty_entry[i].name != NULL
6279 && winpty_entry[i].ptr != NULL; ++i)
6280 {
6281 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
6282 winpty_entry[i].name)) == NULL)
6283 {
6284 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006285 semsg(_(e_loadfunc), winpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006286 hWinPtyDLL = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006287 return FAIL;
6288 }
6289 }
6290
6291 return OK;
6292}
6293
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006294 static int
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006295winpty_term_and_job_init(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006296 term_T *term,
6297 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02006298 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006299 jobopt_T *opt,
6300 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006301{
6302 WCHAR *cmd_wchar = NULL;
6303 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006304 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006305 channel_T *channel = NULL;
6306 job_T *job = NULL;
6307 DWORD error;
6308 HANDLE jo = NULL;
6309 HANDLE child_process_handle;
6310 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01006311 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006312 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006313 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006314 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006315
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006316 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
6317 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006318
6319 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006320 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006321 cmd = argvar->vval.v_string;
6322 }
6323 else if (argvar->v_type == VAR_LIST)
6324 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006325 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006326 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006327 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006328 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006329 if (cmd == NULL || *cmd == NUL)
6330 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006331 emsg(_(e_invarg));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006332 goto failed;
6333 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006334
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006335 term->tl_arg0_cmd = vim_strsave(cmd);
6336
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006337 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006338 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006339 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006340 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006341 if (opt->jo_cwd != NULL)
6342 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006343
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006344 win32_build_env(opt->jo_env, &ga_env, TRUE);
6345 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006346
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006347 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
6348 if (term->tl_winpty_config == NULL)
6349 goto failed;
6350
6351 winpty_config_set_mouse_mode(term->tl_winpty_config,
6352 WINPTY_MOUSE_MODE_FORCE);
6353 winpty_config_set_initial_size(term->tl_winpty_config,
6354 term->tl_cols, term->tl_rows);
6355 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
6356 if (term->tl_winpty == NULL)
6357 goto failed;
6358
6359 spawn_config = winpty_spawn_config_new(
6360 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
6361 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
6362 NULL,
6363 cmd_wchar,
6364 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006365 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006366 &winpty_err);
6367 if (spawn_config == NULL)
6368 goto failed;
6369
6370 channel = add_channel();
6371 if (channel == NULL)
6372 goto failed;
6373
6374 job = job_alloc();
6375 if (job == NULL)
6376 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02006377 if (argvar->v_type == VAR_STRING)
6378 {
6379 int argc;
6380
6381 build_argv_from_string(cmd, &job->jv_argv, &argc);
6382 }
6383 else
6384 {
6385 int argc;
6386
6387 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6388 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006389
6390 if (opt->jo_set & JO_IN_BUF)
6391 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6392
6393 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
6394 &child_thread_handle, &error, &winpty_err))
6395 goto failed;
6396
6397 channel_set_pipes(channel,
6398 (sock_T)CreateFileW(
6399 winpty_conin_name(term->tl_winpty),
6400 GENERIC_WRITE, 0, NULL,
6401 OPEN_EXISTING, 0, NULL),
6402 (sock_T)CreateFileW(
6403 winpty_conout_name(term->tl_winpty),
6404 GENERIC_READ, 0, NULL,
6405 OPEN_EXISTING, 0, NULL),
6406 (sock_T)CreateFileW(
6407 winpty_conerr_name(term->tl_winpty),
6408 GENERIC_READ, 0, NULL,
6409 OPEN_EXISTING, 0, NULL));
6410
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006411 // Write lines with CR instead of NL.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006412 channel->ch_write_text_mode = TRUE;
6413
6414 jo = CreateJobObject(NULL, NULL);
6415 if (jo == NULL)
6416 goto failed;
6417
6418 if (!AssignProcessToJobObject(jo, child_process_handle))
6419 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006420 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006421 CloseHandle(jo);
6422 jo = NULL;
6423 }
6424
6425 winpty_spawn_config_free(spawn_config);
6426 vim_free(cmd_wchar);
6427 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006428 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006429
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006430 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6431 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006432
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006433#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6434 if (opt->jo_set2 & JO2_ANSI_COLORS)
6435 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6436 else
6437 init_vterm_ansi_colors(term->tl_vterm);
6438#endif
6439
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006440 channel_set_job(channel, job, opt);
6441 job_set_options(job, opt);
6442
6443 job->jv_channel = channel;
6444 job->jv_proc_info.hProcess = child_process_handle;
6445 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
6446 job->jv_job_object = jo;
6447 job->jv_status = JOB_STARTED;
6448 job->jv_tty_in = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006449 (short_u *)winpty_conin_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006450 job->jv_tty_out = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006451 (short_u *)winpty_conout_name(term->tl_winpty), NULL);
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006452 job->jv_tty_type = vim_strsave((char_u *)"winpty");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006453 ++job->jv_refcount;
6454 term->tl_job = job;
6455
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006456 // Redirecting stdout and stderr doesn't work at the job level. Instead
6457 // open the file here and handle it in. opt->jo_io was changed in
6458 // setup_job_options(), use the original flags here.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006459 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6460 {
6461 char_u *fname = opt->jo_io_name[PART_OUT];
6462
6463 ch_log(channel, "Opening output file %s", fname);
6464 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6465 if (term->tl_out_fd == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006466 semsg(_(e_notopen), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006467 }
6468
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006469 return OK;
6470
6471failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006472 ga_clear(&ga_cmd);
6473 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006474 vim_free(cmd_wchar);
6475 vim_free(cwd_wchar);
6476 if (spawn_config != NULL)
6477 winpty_spawn_config_free(spawn_config);
6478 if (channel != NULL)
6479 channel_clear(channel);
6480 if (job != NULL)
6481 {
6482 job->jv_channel = NULL;
6483 job_cleanup(job);
6484 }
6485 term->tl_job = NULL;
6486 if (jo != NULL)
6487 CloseHandle(jo);
6488 if (term->tl_winpty != NULL)
6489 winpty_free(term->tl_winpty);
6490 term->tl_winpty = NULL;
6491 if (term->tl_winpty_config != NULL)
6492 winpty_config_free(term->tl_winpty_config);
6493 term->tl_winpty_config = NULL;
6494 if (winpty_err != NULL)
6495 {
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006496 char *msg = (char *)utf16_to_enc(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006497 (short_u *)winpty_error_msg(winpty_err), NULL);
6498
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006499 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006500 winpty_error_free(winpty_err);
6501 }
6502 return FAIL;
6503}
6504
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006505/*
6506 * Create a new terminal of "rows" by "cols" cells.
6507 * Store a reference in "term".
6508 * Return OK or FAIL.
6509 */
6510 static int
6511term_and_job_init(
6512 term_T *term,
6513 typval_T *argvar,
Bram Moolenaar197c6b72019-11-03 23:37:12 +01006514 char **argv,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006515 jobopt_T *opt,
6516 jobopt_T *orig_opt)
6517{
6518 int use_winpty = FALSE;
6519 int use_conpty = FALSE;
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006520 int tty_type = *p_twt;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006521
6522 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
6523 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
6524
6525 if (!has_winpty && !has_conpty)
6526 // If neither is available give the errors for winpty, since when
6527 // conpty is not available it can't be installed either.
6528 return dyn_winpty_init(TRUE);
6529
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006530 if (opt->jo_tty_type != NUL)
6531 tty_type = opt->jo_tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006532
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006533 if (tty_type == NUL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006534 {
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01006535 if (has_conpty && (is_conpty_stable() || !has_winpty))
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006536 use_conpty = TRUE;
6537 else if (has_winpty)
6538 use_winpty = TRUE;
6539 // else: error
6540 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006541 else if (tty_type == 'w') // winpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006542 {
6543 if (has_winpty)
6544 use_winpty = TRUE;
6545 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006546 else if (tty_type == 'c') // conpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006547 {
6548 if (has_conpty)
6549 use_conpty = TRUE;
6550 else
6551 return dyn_conpty_init(TRUE);
6552 }
6553
6554 if (use_conpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006555 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006556
6557 if (use_winpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006558 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006559
6560 // error
6561 return dyn_winpty_init(TRUE);
6562}
6563
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006564 static int
6565create_pty_only(term_T *term, jobopt_T *options)
6566{
6567 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
6568 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
6569 char in_name[80], out_name[80];
6570 channel_T *channel = NULL;
6571
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006572 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6573 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006574
6575 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
6576 GetCurrentProcessId(),
6577 curbuf->b_fnum);
6578 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
6579 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6580 PIPE_UNLIMITED_INSTANCES,
6581 0, 0, NMPWAIT_NOWAIT, NULL);
6582 if (hPipeIn == INVALID_HANDLE_VALUE)
6583 goto failed;
6584
6585 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
6586 GetCurrentProcessId(),
6587 curbuf->b_fnum);
6588 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
6589 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6590 PIPE_UNLIMITED_INSTANCES,
6591 0, 0, 0, NULL);
6592 if (hPipeOut == INVALID_HANDLE_VALUE)
6593 goto failed;
6594
6595 ConnectNamedPipe(hPipeIn, NULL);
6596 ConnectNamedPipe(hPipeOut, NULL);
6597
6598 term->tl_job = job_alloc();
6599 if (term->tl_job == NULL)
6600 goto failed;
6601 ++term->tl_job->jv_refcount;
6602
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006603 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006604 term->tl_job->jv_status = JOB_FINISHED;
6605
6606 channel = add_channel();
6607 if (channel == NULL)
6608 goto failed;
6609 term->tl_job->jv_channel = channel;
6610 channel->ch_keep_open = TRUE;
6611 channel->ch_named_pipe = TRUE;
6612
6613 channel_set_pipes(channel,
6614 (sock_T)hPipeIn,
6615 (sock_T)hPipeOut,
6616 (sock_T)hPipeOut);
6617 channel_set_job(channel, term->tl_job, options);
6618 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
6619 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
6620
6621 return OK;
6622
6623failed:
6624 if (hPipeIn != NULL)
6625 CloseHandle(hPipeIn);
6626 if (hPipeOut != NULL)
6627 CloseHandle(hPipeOut);
6628 return FAIL;
6629}
6630
6631/*
6632 * Free the terminal emulator part of "term".
6633 */
6634 static void
6635term_free_vterm(term_T *term)
6636{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006637 term_free_conpty(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006638 if (term->tl_winpty != NULL)
6639 winpty_free(term->tl_winpty);
6640 term->tl_winpty = NULL;
6641 if (term->tl_winpty_config != NULL)
6642 winpty_config_free(term->tl_winpty_config);
6643 term->tl_winpty_config = NULL;
6644 if (term->tl_vterm != NULL)
6645 vterm_free(term->tl_vterm);
6646 term->tl_vterm = NULL;
6647}
6648
6649/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006650 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006651 */
6652 static void
6653term_report_winsize(term_T *term, int rows, int cols)
6654{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006655 if (term->tl_conpty)
6656 conpty_term_report_winsize(term, rows, cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006657 if (term->tl_winpty)
6658 winpty_set_size(term->tl_winpty, cols, rows, NULL);
6659}
6660
6661 int
6662terminal_enabled(void)
6663{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006664 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006665}
6666
6667# else
6668
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006669///////////////////////////////////////
6670// 3. Unix-like implementation.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006671
6672/*
6673 * Create a new terminal of "rows" by "cols" cells.
6674 * Start job for "cmd".
6675 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01006676 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006677 * Return OK or FAIL.
6678 */
6679 static int
6680term_and_job_init(
6681 term_T *term,
6682 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01006683 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006684 jobopt_T *opt,
6685 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006686{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006687 term->tl_arg0_cmd = NULL;
6688
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006689 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6690 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006691
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006692#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6693 if (opt->jo_set2 & JO2_ANSI_COLORS)
6694 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6695 else
6696 init_vterm_ansi_colors(term->tl_vterm);
6697#endif
6698
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006699 // This may change a string in "argvar".
Bram Moolenaar493359e2018-06-12 20:25:52 +02006700 term->tl_job = job_start(argvar, argv, opt, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006701 if (term->tl_job != NULL)
6702 ++term->tl_job->jv_refcount;
6703
6704 return term->tl_job != NULL
6705 && term->tl_job->jv_channel != NULL
6706 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
6707}
6708
6709 static int
6710create_pty_only(term_T *term, jobopt_T *opt)
6711{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006712 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6713 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006714
6715 term->tl_job = job_alloc();
6716 if (term->tl_job == NULL)
6717 return FAIL;
6718 ++term->tl_job->jv_refcount;
6719
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006720 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006721 term->tl_job->jv_status = JOB_FINISHED;
6722
6723 return mch_create_pty_channel(term->tl_job, opt);
6724}
6725
6726/*
6727 * Free the terminal emulator part of "term".
6728 */
6729 static void
6730term_free_vterm(term_T *term)
6731{
6732 if (term->tl_vterm != NULL)
6733 vterm_free(term->tl_vterm);
6734 term->tl_vterm = NULL;
6735}
6736
6737/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006738 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006739 */
6740 static void
6741term_report_winsize(term_T *term, int rows, int cols)
6742{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006743 // Use an ioctl() to report the new window size to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006744 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
6745 {
6746 int fd = -1;
6747 int part;
6748
6749 for (part = PART_OUT; part < PART_COUNT; ++part)
6750 {
6751 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01006752 if (mch_isatty(fd))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006753 break;
6754 }
6755 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
6756 mch_signal_job(term->tl_job, (char_u *)"winch");
6757 }
6758}
6759
6760# endif
6761
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006762#endif // FEAT_TERMINAL