blob: d789ab0563603dc6eb93da291a0e51a84698c1c3 [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 term->tl_command = vim_strsave((char_u *)"NONE");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100599 else if (argvar->v_type == VAR_STRING)
600 {
601 char_u *cmd = argvar->vval.v_string;
602
603 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
604 term->tl_command = vim_strsave(cmd);
605 }
606 else if (argvar->v_type == VAR_LIST
607 && argvar->vval.v_list != NULL
608 && argvar->vval.v_list->lv_len > 0)
609 {
610 garray_T ga;
611 listitem_T *item;
612
613 ga_init2(&ga, 1, 100);
614 for (item = argvar->vval.v_list->lv_first;
615 item != NULL; item = item->li_next)
616 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100617 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100618 char_u *p;
619
620 if (s == NULL)
621 break;
622 p = vim_strsave_fnameescape(s, FALSE);
623 if (p == NULL)
624 break;
625 ga_concat(&ga, p);
626 vim_free(p);
627 ga_append(&ga, ' ');
628 }
629 if (item == NULL)
630 {
631 ga_append(&ga, NUL);
632 term->tl_command = ga.ga_data;
633 }
634 else
635 ga_clear(&ga);
636 }
637#endif
638
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100639 if (opt->jo_term_kill != NULL)
640 {
641 char_u *p = skiptowhite(opt->jo_term_kill);
642
643 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
644 }
645
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200646 if (opt->jo_term_api != NULL)
Bram Moolenaar21109272020-01-30 16:27:20 +0100647 {
648 char_u *p = skiptowhite(opt->jo_term_api);
649
650 term->tl_api = vim_strnsave(opt->jo_term_api, p - opt->jo_term_api);
651 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200652 else
653 term->tl_api = vim_strsave((char_u *)"Tapi_");
654
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100655 // System dependent: setup the vterm and maybe start the job in it.
Bram Moolenaar13568252018-03-16 20:46:58 +0100656 if (argv == NULL
657 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200658 && argvar->vval.v_string != NULL
659 && STRCMP(argvar->vval.v_string, "NONE") == 0)
660 res = create_pty_only(term, opt);
661 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200662 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200663
664 newbuf = curbuf;
665 if (res == OK)
666 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100667 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200668 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
669 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100670#ifdef FEAT_GUI
671 if (term->tl_system)
672 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100673 // display first line below typed command
Bram Moolenaar13568252018-03-16 20:46:58 +0100674 term->tl_toprow = msg_row + 1;
675 term->tl_dirty_row_end = 0;
676 }
677#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200678
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100679 // Make sure we don't get stuck on sending keys to the job, it leads to
680 // a deadlock if the job is waiting for Vim to read.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200681 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
682
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200683 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200684 {
685 --curbuf->b_nwindows;
686 curbuf = old_curbuf;
687 curwin->w_buffer = curbuf;
688 ++curbuf->b_nwindows;
689 }
690 }
691 else
692 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100693 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200694 return NULL;
695 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100696
Bram Moolenaar13568252018-03-16 20:46:58 +0100697 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar28ed4df2019-10-26 16:21:40 +0200698 if (!opt->jo_hidden && !(flags & TERM_START_SYSTEM))
699 apply_autocmds(EVENT_TERMINALWINOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200700 return newbuf;
701}
702
703/*
704 * ":terminal": open a terminal window and execute a job in it.
705 */
706 void
707ex_terminal(exarg_T *eap)
708{
709 typval_T argvar[2];
710 jobopt_T opt;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100711 int opt_shell = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200712 char_u *cmd;
713 char_u *tofree = NULL;
714
715 init_job_options(&opt);
716
717 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100718 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200719 {
720 char_u *p, *ep;
721
722 cmd += 2;
723 p = skiptowhite(cmd);
724 ep = vim_strchr(cmd, '=');
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200725 if (ep != NULL)
726 {
727 if (ep < p)
728 p = ep;
729 else
730 ep = NULL;
731 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200732
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200733# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
734 && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
735 if (OPTARG_HAS("close"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200736 opt.jo_term_finish = 'c';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200737 else if (OPTARG_HAS("noclose"))
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100738 opt.jo_term_finish = 'n';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200739 else if (OPTARG_HAS("open"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200740 opt.jo_term_finish = 'o';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200741 else if (OPTARG_HAS("curwin"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200742 opt.jo_curwin = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200743 else if (OPTARG_HAS("hidden"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200744 opt.jo_hidden = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200745 else if (OPTARG_HAS("norestore"))
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100746 opt.jo_term_norestore = 1;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100747 else if (OPTARG_HAS("shell"))
748 opt_shell = TRUE;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200749 else if (OPTARG_HAS("kill") && ep != NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100750 {
751 opt.jo_set2 |= JO2_TERM_KILL;
752 opt.jo_term_kill = ep + 1;
753 p = skiptowhite(cmd);
754 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200755 else if (OPTARG_HAS("api"))
756 {
757 opt.jo_set2 |= JO2_TERM_API;
758 if (ep != NULL)
759 {
760 opt.jo_term_api = ep + 1;
761 p = skiptowhite(cmd);
762 }
763 else
764 opt.jo_term_api = NULL;
765 }
766 else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200767 {
768 opt.jo_set2 |= JO2_TERM_ROWS;
769 opt.jo_term_rows = atoi((char *)ep + 1);
770 p = skiptowhite(cmd);
771 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200772 else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200773 {
774 opt.jo_set2 |= JO2_TERM_COLS;
775 opt.jo_term_cols = atoi((char *)ep + 1);
776 p = skiptowhite(cmd);
777 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200778 else if (OPTARG_HAS("eof") && ep != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200779 {
780 char_u *buf = NULL;
781 char_u *keys;
782
Bram Moolenaar21109272020-01-30 16:27:20 +0100783 vim_free(opt.jo_eof_chars);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200784 p = skiptowhite(cmd);
785 *p = NUL;
Bram Moolenaar459fd782019-10-13 16:43:39 +0200786 keys = replace_termcodes(ep + 1, &buf,
787 REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200788 opt.jo_set2 |= JO2_EOF_CHARS;
789 opt.jo_eof_chars = vim_strsave(keys);
790 vim_free(buf);
791 *p = ' ';
792 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100793#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100794 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "type", 4) == 0
795 && ep != NULL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100796 {
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100797 int tty_type = NUL;
798
799 p = skiptowhite(cmd);
800 if (STRNICMP(ep + 1, "winpty", p - (ep + 1)) == 0)
801 tty_type = 'w';
802 else if (STRNICMP(ep + 1, "conpty", p - (ep + 1)) == 0)
803 tty_type = 'c';
804 else
805 {
806 semsg(e_invargval, "type");
807 goto theend;
808 }
809 opt.jo_set2 |= JO2_TTY_TYPE;
810 opt.jo_tty_type = tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100811 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100812#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200813 else
814 {
815 if (*p)
816 *p = NUL;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100817 semsg(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100818 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200819 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200820# undef OPTARG_HAS
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200821 cmd = skipwhite(p);
822 }
823 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100824 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100825 // Make a copy of 'shell', an autocommand may change the option.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200826 tofree = cmd = vim_strsave(p_sh);
827
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100828 // default to close when the shell exits
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100829 if (opt.jo_term_finish == NUL)
830 opt.jo_term_finish = 'c';
831 }
832
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200833 if (eap->addr_count > 0)
834 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100835 // Write lines from current buffer to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200836 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
837 opt.jo_io[PART_IN] = JIO_BUFFER;
838 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
839 opt.jo_in_top = eap->line1;
840 opt.jo_in_bot = eap->line2;
841 }
842
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100843 if (opt_shell && tofree == NULL)
844 {
845#ifdef UNIX
846 char **argv = NULL;
847 char_u *tofree1 = NULL;
848 char_u *tofree2 = NULL;
849
850 // :term ++shell command
851 if (unix_build_argv(cmd, &argv, &tofree1, &tofree2) == OK)
852 term_start(NULL, argv, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaaradf4aa22019-11-10 22:36:44 +0100853 vim_free(argv);
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100854 vim_free(tofree1);
855 vim_free(tofree2);
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100856 goto theend;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100857#else
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100858# ifdef MSWIN
859 long_u cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
860 char_u *newcmd;
861
862 newcmd = alloc(cmdlen);
863 if (newcmd == NULL)
864 goto theend;
865 tofree = newcmd;
866 vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd);
867 cmd = newcmd;
868# else
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100869 emsg(_("E279: Sorry, ++shell is not supported on this system"));
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100870 goto theend;
871# endif
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100872#endif
873 }
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100874 argvar[0].v_type = VAR_STRING;
875 argvar[0].vval.v_string = cmd;
876 argvar[1].v_type = VAR_UNKNOWN;
877 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100878
879theend:
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100880 vim_free(tofree);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200881 vim_free(opt.jo_eof_chars);
882}
883
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100884#if defined(FEAT_SESSION) || defined(PROTO)
885/*
886 * Write a :terminal command to the session file to restore the terminal in
887 * window "wp".
888 * Return FAIL if writing fails.
889 */
890 int
891term_write_session(FILE *fd, win_T *wp)
892{
893 term_T *term = wp->w_buffer->b_term;
894
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100895 // Create the terminal and run the command. This is not without
896 // risk, but let's assume the user only creates a session when this
897 // will be OK.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100898 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
899 term->tl_cols, term->tl_rows) < 0)
900 return FAIL;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100901#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100902 if (fprintf(fd, "++type=%s ", term->tl_job->jv_tty_type) < 0)
903 return FAIL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100904#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100905 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
906 return FAIL;
907
908 return put_eol(fd);
909}
910
911/*
912 * Return TRUE if "buf" has a terminal that should be restored.
913 */
914 int
915term_should_restore(buf_T *buf)
916{
917 term_T *term = buf->b_term;
918
919 return term != NULL && (term->tl_command == NULL
920 || STRCMP(term->tl_command, "NONE") != 0);
921}
922#endif
923
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200924/*
925 * Free the scrollback buffer for "term".
926 */
927 static void
928free_scrollback(term_T *term)
929{
930 int i;
931
932 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
933 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
934 ga_clear(&term->tl_scrollback);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100935 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
936 vim_free(((sb_line_T *)term->tl_scrollback_postponed.ga_data + i)->sb_cells);
937 ga_clear(&term->tl_scrollback_postponed);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200938}
939
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100940
941// Terminals that need to be freed soon.
Bram Moolenaar840d16f2019-09-10 21:27:18 +0200942static term_T *terminals_to_free = NULL;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100943
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200944/*
945 * Free a terminal and everything it refers to.
946 * Kills the job if there is one.
947 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100948 * The actual terminal structure is freed later in free_unused_terminals(),
949 * because callbacks may wipe out a buffer while the terminal is still
950 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200951 */
952 void
953free_terminal(buf_T *buf)
954{
955 term_T *term = buf->b_term;
956 term_T *tp;
957
958 if (term == NULL)
959 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100960
961 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200962 if (first_term == term)
963 first_term = term->tl_next;
964 else
965 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
966 if (tp->tl_next == term)
967 {
968 tp->tl_next = term->tl_next;
969 break;
970 }
971
972 if (term->tl_job != NULL)
973 {
974 if (term->tl_job->jv_status != JOB_ENDED
975 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100976 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200977 job_stop(term->tl_job, NULL, "kill");
978 job_unref(term->tl_job);
979 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100980 term->tl_next = terminals_to_free;
981 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200982
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200983 buf->b_term = NULL;
984 if (in_terminal_loop == term)
985 in_terminal_loop = NULL;
986}
987
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100988 void
989free_unused_terminals()
990{
991 while (terminals_to_free != NULL)
992 {
993 term_T *term = terminals_to_free;
994
995 terminals_to_free = term->tl_next;
996
997 free_scrollback(term);
998
999 term_free_vterm(term);
Bram Moolenaard2842ea2019-09-26 23:08:54 +02001000 vim_free(term->tl_api);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001001 vim_free(term->tl_title);
1002#ifdef FEAT_SESSION
1003 vim_free(term->tl_command);
1004#endif
1005 vim_free(term->tl_kill);
1006 vim_free(term->tl_status_text);
1007 vim_free(term->tl_opencmd);
1008 vim_free(term->tl_eof_chars);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001009 vim_free(term->tl_arg0_cmd);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001010#ifdef MSWIN
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001011 if (term->tl_out_fd != NULL)
1012 fclose(term->tl_out_fd);
1013#endif
1014 vim_free(term->tl_cursor_color);
1015 vim_free(term);
1016 }
1017}
1018
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001019/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001020 * Get the part that is connected to the tty. Normally this is PART_IN, but
1021 * when writing buffer lines to the job it can be another. This makes it
1022 * possible to do "1,5term vim -".
1023 */
1024 static ch_part_T
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02001025get_tty_part(term_T *term UNUSED)
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001026{
1027#ifdef UNIX
1028 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
1029 int i;
1030
1031 for (i = 0; i < 3; ++i)
1032 {
1033 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
1034
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01001035 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001036 return parts[i];
1037 }
1038#endif
1039 return PART_IN;
1040}
1041
1042/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001043 * Write job output "msg[len]" to the vterm.
1044 */
1045 static void
1046term_write_job_output(term_T *term, char_u *msg, size_t len)
1047{
1048 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001049 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001050
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001051 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001052
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001053 // flush vterm buffer when vterm responded to control sequence
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001054 if (prevlen != vterm_output_get_buffer_current(vterm))
1055 {
1056 char buf[KEY_BUF_LEN];
1057 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
1058
1059 if (curlen > 0)
1060 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1061 (char_u *)buf, (int)curlen, NULL);
1062 }
1063
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001064 // this invokes the damage callbacks
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001065 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
1066}
1067
1068 static void
1069update_cursor(term_T *term, int redraw)
1070{
1071 if (term->tl_normal_mode)
1072 return;
Bram Moolenaar13568252018-03-16 20:46:58 +01001073#ifdef FEAT_GUI
1074 if (term->tl_system)
1075 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
1076 term->tl_cursor_pos.col);
1077 else
1078#endif
1079 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001080 if (redraw)
1081 {
1082 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
1083 cursor_on();
1084 out_flush();
1085#ifdef FEAT_GUI
1086 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001087 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001088 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001089 gui_mch_flush();
1090 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001091#endif
1092 }
1093}
1094
1095/*
1096 * Invoked when "msg" output from a job was received. Write it to the terminal
1097 * of "buffer".
1098 */
1099 void
1100write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1101{
1102 size_t len = STRLEN(msg);
1103 term_T *term = buffer->b_term;
1104
Bram Moolenaar4f974752019-02-17 17:44:42 +01001105#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001106 // Win32: Cannot redirect output of the job, intercept it here and write to
1107 // the file.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02001108 if (term->tl_out_fd != NULL)
1109 {
1110 ch_log(channel, "Writing %d bytes to output file", (int)len);
1111 fwrite(msg, len, 1, term->tl_out_fd);
1112 return;
1113 }
1114#endif
1115
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001116 if (term->tl_vterm == NULL)
1117 {
1118 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1119 return;
1120 }
1121 ch_log(channel, "writing %d bytes to terminal", (int)len);
1122 term_write_job_output(term, msg, len);
1123
Bram Moolenaar13568252018-03-16 20:46:58 +01001124#ifdef FEAT_GUI
1125 if (term->tl_system)
1126 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001127 // show system output, scrolling up the screen as needed
Bram Moolenaar13568252018-03-16 20:46:58 +01001128 update_system_term(term);
1129 update_cursor(term, TRUE);
1130 }
1131 else
1132#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001133 // In Terminal-Normal mode we are displaying the buffer, not the terminal
1134 // contents, thus no screen update is needed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001135 if (!term->tl_normal_mode)
1136 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001137 // Don't use update_screen() when editing the command line, it gets
1138 // cleared.
1139 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001140 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001141 if (buffer == curbuf && (State & CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001142 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001143 update_screen(VALID_NO_UPDATE);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001144 // update_screen() can be slow, check the terminal wasn't closed
1145 // already
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001146 if (buffer == curbuf && curbuf->b_term != NULL)
1147 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001148 }
1149 else
1150 redraw_after_callback(TRUE);
1151 }
1152}
1153
1154/*
1155 * Send a mouse position and click to the vterm
1156 */
1157 static int
1158term_send_mouse(VTerm *vterm, int button, int pressed)
1159{
1160 VTermModifier mod = VTERM_MOD_NONE;
1161
1162 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
Bram Moolenaar53f81742017-09-22 14:35:51 +02001163 mouse_col - curwin->w_wincol, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001164 if (button != 0)
1165 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001166 return TRUE;
1167}
1168
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001169static int enter_mouse_col = -1;
1170static int enter_mouse_row = -1;
1171
1172/*
1173 * Handle a mouse click, drag or release.
1174 * Return TRUE when a mouse event is sent to the terminal.
1175 */
1176 static int
1177term_mouse_click(VTerm *vterm, int key)
1178{
1179#if defined(FEAT_CLIPBOARD)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001180 // For modeless selection mouse drag and release events are ignored, unless
1181 // they are preceded with a mouse down event
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001182 static int ignore_drag_release = TRUE;
1183 VTermMouseState mouse_state;
1184
1185 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1186 if (mouse_state.flags == 0)
1187 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001188 // Terminal is not using the mouse, use modeless selection.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001189 switch (key)
1190 {
1191 case K_LEFTDRAG:
1192 case K_LEFTRELEASE:
1193 case K_RIGHTDRAG:
1194 case K_RIGHTRELEASE:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001195 // Ignore drag and release events when the button-down wasn't
1196 // seen before.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001197 if (ignore_drag_release)
1198 {
1199 int save_mouse_col, save_mouse_row;
1200
1201 if (enter_mouse_col < 0)
1202 break;
1203
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001204 // mouse click in the window gave us focus, handle that
1205 // click now
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001206 save_mouse_col = mouse_col;
1207 save_mouse_row = mouse_row;
1208 mouse_col = enter_mouse_col;
1209 mouse_row = enter_mouse_row;
1210 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1211 mouse_col = save_mouse_col;
1212 mouse_row = save_mouse_row;
1213 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001214 // FALLTHROUGH
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001215 case K_LEFTMOUSE:
1216 case K_RIGHTMOUSE:
1217 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1218 ignore_drag_release = TRUE;
1219 else
1220 ignore_drag_release = FALSE;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001221 // Should we call mouse_has() here?
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001222 if (clip_star.available)
1223 {
1224 int button, is_click, is_drag;
1225
1226 button = get_mouse_button(KEY2TERMCAP1(key),
1227 &is_click, &is_drag);
1228 if (mouse_model_popup() && button == MOUSE_LEFT
1229 && (mod_mask & MOD_MASK_SHIFT))
1230 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001231 // Translate shift-left to right button.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001232 button = MOUSE_RIGHT;
1233 mod_mask &= ~MOD_MASK_SHIFT;
1234 }
1235 clip_modeless(button, is_click, is_drag);
1236 }
1237 break;
1238
1239 case K_MIDDLEMOUSE:
1240 if (clip_star.available)
1241 insert_reg('*', TRUE);
1242 break;
1243 }
1244 enter_mouse_col = -1;
1245 return FALSE;
1246 }
1247#endif
1248 enter_mouse_col = -1;
1249
1250 switch (key)
1251 {
1252 case K_LEFTMOUSE:
1253 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1254 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1255 case K_LEFTRELEASE:
1256 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1257 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1258 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1259 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1260 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1261 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1262 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1263 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1264 }
1265 return TRUE;
1266}
1267
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001268/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001269 * Convert typed key "c" with modifiers "modmask" into bytes to send to the
1270 * job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001271 * Return the number of bytes in "buf".
1272 */
1273 static int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001274term_convert_key(term_T *term, int c, int modmask, char *buf)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001275{
1276 VTerm *vterm = term->tl_vterm;
1277 VTermKey key = VTERM_KEY_NONE;
1278 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001279 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001280
1281 switch (c)
1282 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001283 // don't use VTERM_KEY_ENTER, it may do an unwanted conversion
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001284
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001285 // don't use VTERM_KEY_BACKSPACE, it always
1286 // becomes 0x7f DEL
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001287 case K_BS: c = term_backspace_char; break;
1288
1289 case ESC: key = VTERM_KEY_ESCAPE; break;
1290 case K_DEL: key = VTERM_KEY_DEL; break;
1291 case K_DOWN: key = VTERM_KEY_DOWN; break;
1292 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1293 key = VTERM_KEY_DOWN; break;
1294 case K_END: key = VTERM_KEY_END; break;
1295 case K_S_END: mod = VTERM_MOD_SHIFT;
1296 key = VTERM_KEY_END; break;
1297 case K_C_END: mod = VTERM_MOD_CTRL;
1298 key = VTERM_KEY_END; break;
1299 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1300 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1301 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1302 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1303 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1304 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1305 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1306 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1307 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1308 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1309 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1310 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1311 case K_HOME: key = VTERM_KEY_HOME; break;
1312 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1313 key = VTERM_KEY_HOME; break;
1314 case K_C_HOME: mod = VTERM_MOD_CTRL;
1315 key = VTERM_KEY_HOME; break;
1316 case K_INS: key = VTERM_KEY_INS; break;
1317 case K_K0: key = VTERM_KEY_KP_0; break;
1318 case K_K1: key = VTERM_KEY_KP_1; break;
1319 case K_K2: key = VTERM_KEY_KP_2; break;
1320 case K_K3: key = VTERM_KEY_KP_3; break;
1321 case K_K4: key = VTERM_KEY_KP_4; break;
1322 case K_K5: key = VTERM_KEY_KP_5; break;
1323 case K_K6: key = VTERM_KEY_KP_6; break;
1324 case K_K7: key = VTERM_KEY_KP_7; break;
1325 case K_K8: key = VTERM_KEY_KP_8; break;
1326 case K_K9: key = VTERM_KEY_KP_9; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001327 case K_KDEL: key = VTERM_KEY_DEL; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001328 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001329 case K_KEND: key = VTERM_KEY_KP_1; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001330 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001331 case K_KHOME: key = VTERM_KEY_KP_7; break; // TODO
1332 case K_KINS: key = VTERM_KEY_KP_0; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001333 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1334 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001335 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; // TODO
1336 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001337 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1338 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1339 case K_LEFT: key = VTERM_KEY_LEFT; break;
1340 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1341 key = VTERM_KEY_LEFT; break;
1342 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1343 key = VTERM_KEY_LEFT; break;
1344 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1345 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1346 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1347 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1348 key = VTERM_KEY_RIGHT; break;
1349 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1350 key = VTERM_KEY_RIGHT; break;
1351 case K_UP: key = VTERM_KEY_UP; break;
1352 case K_S_UP: mod = VTERM_MOD_SHIFT;
1353 key = VTERM_KEY_UP; break;
1354 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001355 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1356 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001357
Bram Moolenaara42ad572017-11-16 13:08:04 +01001358 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1359 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001360 case K_MOUSELEFT: /* TODO */ return 0;
1361 case K_MOUSERIGHT: /* TODO */ return 0;
1362
1363 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001364 case K_LEFTMOUSE_NM:
1365 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001366 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001367 case K_LEFTRELEASE_NM:
1368 case K_MOUSEMOVE:
1369 case K_MIDDLEMOUSE:
1370 case K_MIDDLEDRAG:
1371 case K_MIDDLERELEASE:
1372 case K_RIGHTMOUSE:
1373 case K_RIGHTDRAG:
1374 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1375 return 0;
1376 other = TRUE;
1377 break;
1378
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001379 case K_X1MOUSE: /* TODO */ return 0;
1380 case K_X1DRAG: /* TODO */ return 0;
1381 case K_X1RELEASE: /* TODO */ return 0;
1382 case K_X2MOUSE: /* TODO */ return 0;
1383 case K_X2DRAG: /* TODO */ return 0;
1384 case K_X2RELEASE: /* TODO */ return 0;
1385
1386 case K_IGNORE: return 0;
1387 case K_NOP: return 0;
1388 case K_UNDO: return 0;
1389 case K_HELP: return 0;
1390 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1391 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1392 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1393 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1394 case K_SELECT: return 0;
1395#ifdef FEAT_GUI
1396 case K_VER_SCROLLBAR: return 0;
1397 case K_HOR_SCROLLBAR: return 0;
1398#endif
1399#ifdef FEAT_GUI_TABLINE
1400 case K_TABLINE: return 0;
1401 case K_TABMENU: return 0;
1402#endif
1403#ifdef FEAT_NETBEANS_INTG
1404 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1405#endif
1406#ifdef FEAT_DND
1407 case K_DROP: return 0;
1408#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001409 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001410 case K_PS: vterm_keyboard_start_paste(vterm);
1411 other = TRUE;
1412 break;
1413 case K_PE: vterm_keyboard_end_paste(vterm);
1414 other = TRUE;
1415 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001416 }
1417
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001418 // add modifiers for the typed key
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001419 if (modmask & MOD_MASK_SHIFT)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001420 mod |= VTERM_MOD_SHIFT;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001421 if (modmask & MOD_MASK_CTRL)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001422 mod |= VTERM_MOD_CTRL;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001423 if (modmask & (MOD_MASK_ALT | MOD_MASK_META))
Bram Moolenaar459fd782019-10-13 16:43:39 +02001424 mod |= VTERM_MOD_ALT;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001425
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001426 /*
1427 * Convert special keys to vterm keys:
1428 * - Write keys to vterm: vterm_keyboard_key()
1429 * - Write output to channel.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001430 */
1431 if (key != VTERM_KEY_NONE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001432 // Special key, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001433 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001434 else if (!other)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001435 // Normal character, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001436 vterm_keyboard_unichar(vterm, c, mod);
1437
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001438 // Read back the converted escape sequence.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001439 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1440}
1441
1442/*
1443 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001444 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001445 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001446 */
1447 static int
1448term_job_running_check(term_T *term, int check_job_status)
1449{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001450 // Also consider the job finished when the channel is closed, to avoid a
1451 // race condition when updating the title.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001452 if (term != NULL
1453 && term->tl_job != NULL
1454 && channel_is_open(term->tl_job->jv_channel))
1455 {
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001456 job_T *job = term->tl_job;
1457
1458 // Careful: Checking the job status may invoked callbacks, which close
1459 // the buffer and terminate "term". However, "job" will not be freed
1460 // yet.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001461 if (check_job_status)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001462 job_status(job);
1463 return (job->jv_status == JOB_STARTED
1464 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001465 }
1466 return FALSE;
1467}
1468
1469/*
1470 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001471 */
1472 int
1473term_job_running(term_T *term)
1474{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001475 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001476}
1477
1478/*
1479 * Return TRUE if "term" has an active channel and used ":term NONE".
1480 */
1481 int
1482term_none_open(term_T *term)
1483{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001484 // Also consider the job finished when the channel is closed, to avoid a
1485 // race condition when updating the title.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001486 return term != NULL
1487 && term->tl_job != NULL
1488 && channel_is_open(term->tl_job->jv_channel)
1489 && term->tl_job->jv_channel->ch_keep_open;
1490}
1491
1492/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001493 * Used when exiting: kill the job in "buf" if so desired.
1494 * Return OK when the job finished.
1495 * Return FAIL when the job is still running.
1496 */
1497 int
1498term_try_stop_job(buf_T *buf)
1499{
1500 int count;
1501 char *how = (char *)buf->b_term->tl_kill;
1502
1503#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1504 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1505 {
1506 char_u buff[DIALOG_MSG_SIZE];
1507 int ret;
1508
1509 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1510 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1511 if (ret == VIM_YES)
1512 how = "kill";
1513 else if (ret == VIM_CANCEL)
1514 return FAIL;
1515 }
1516#endif
1517 if (how == NULL || *how == NUL)
1518 return FAIL;
1519
1520 job_stop(buf->b_term->tl_job, NULL, how);
1521
Bram Moolenaar9172d232019-01-29 23:06:54 +01001522 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001523 for (count = 0; count < 100; ++count)
1524 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001525 job_T *job;
1526
1527 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001528 if (!buf_valid(buf)
1529 || buf->b_term == NULL
1530 || buf->b_term->tl_job == NULL)
1531 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001532 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001533
Bram Moolenaar9172d232019-01-29 23:06:54 +01001534 // Call job_status() to update jv_status. It may cause the job to be
1535 // cleaned up but it won't be freed.
1536 job_status(job);
1537 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001538 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001539
Bram Moolenaar8f7ab4b2019-10-23 23:16:45 +02001540 ui_delay(10L, TRUE);
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02001541 term_flush_messages();
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001542 }
1543 return FAIL;
1544}
1545
1546/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001547 * Add the last line of the scrollback buffer to the buffer in the window.
1548 */
1549 static void
1550add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1551{
1552 buf_T *buf = term->tl_buffer;
1553 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1554 linenr_T lnum = buf->b_ml.ml_line_count;
1555
Bram Moolenaar4f974752019-02-17 17:44:42 +01001556#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001557 if (!enc_utf8 && enc_codepage > 0)
1558 {
1559 WCHAR *ret = NULL;
1560 int length = 0;
1561
1562 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1563 &ret, &length);
1564 if (ret != NULL)
1565 {
1566 WideCharToMultiByte_alloc(enc_codepage, 0,
1567 ret, length, (char **)&text, &len, 0, 0);
1568 vim_free(ret);
1569 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1570 vim_free(text);
1571 }
1572 }
1573 else
1574#endif
1575 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1576 if (empty)
1577 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001578 // Delete the empty line that was in the empty buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001579 curbuf = buf;
1580 ml_delete(1, FALSE);
1581 curbuf = curwin->w_buffer;
1582 }
1583}
1584
1585 static void
1586cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1587{
1588 attr->width = cell->width;
1589 attr->attrs = cell->attrs;
1590 attr->fg = cell->fg;
1591 attr->bg = cell->bg;
1592}
1593
1594 static int
1595equal_celattr(cellattr_T *a, cellattr_T *b)
1596{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001597 // Comparing the colors should be sufficient.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001598 return a->fg.red == b->fg.red
1599 && a->fg.green == b->fg.green
1600 && a->fg.blue == b->fg.blue
1601 && a->bg.red == b->bg.red
1602 && a->bg.green == b->bg.green
1603 && a->bg.blue == b->bg.blue;
1604}
1605
Bram Moolenaard96ff162018-02-18 22:13:29 +01001606/*
1607 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1608 * line at this position. Otherwise at the end.
1609 */
1610 static int
1611add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1612{
1613 if (ga_grow(&term->tl_scrollback, 1) == OK)
1614 {
1615 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1616 + term->tl_scrollback.ga_len;
1617
1618 if (lnum > 0)
1619 {
1620 int i;
1621
1622 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1623 {
1624 *line = *(line - 1);
1625 --line;
1626 }
1627 }
1628 line->sb_cols = 0;
1629 line->sb_cells = NULL;
1630 line->sb_fill_attr = *fill_attr;
1631 ++term->tl_scrollback.ga_len;
1632 return OK;
1633 }
1634 return FALSE;
1635}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001636
1637/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001638 * Remove the terminal contents from the scrollback and the buffer.
1639 * Used before adding a new scrollback line or updating the buffer for lines
1640 * displayed in the terminal.
1641 */
1642 static void
1643cleanup_scrollback(term_T *term)
1644{
1645 sb_line_T *line;
1646 garray_T *gap;
1647
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001648 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001649 gap = &term->tl_scrollback;
1650 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1651 && gap->ga_len > 0)
1652 {
1653 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1654 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1655 vim_free(line->sb_cells);
1656 --gap->ga_len;
1657 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001658 curbuf = curwin->w_buffer;
1659 if (curbuf == term->tl_buffer)
1660 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001661}
1662
1663/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001664 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001665 */
1666 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001667update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001668{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001669 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001670 int len;
1671 int lines_skipped = 0;
1672 VTermPos pos;
1673 VTermScreenCell cell;
1674 cellattr_T fill_attr, new_fill_attr;
1675 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001676
1677 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1678 "Adding terminal window snapshot to buffer");
1679
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001680 // First remove the lines that were appended before, they might be
1681 // outdated.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001682 cleanup_scrollback(term);
1683
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001684 screen = vterm_obtain_screen(term->tl_vterm);
1685 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001686 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1687 {
1688 len = 0;
1689 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1690 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1691 && cell.chars[0] != NUL)
1692 {
1693 len = pos.col + 1;
1694 new_fill_attr = term->tl_default_color;
1695 }
1696 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001697 // Assume the last attr is the filler attr.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001698 cell2cellattr(&cell, &new_fill_attr);
1699
1700 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1701 ++lines_skipped;
1702 else
1703 {
1704 while (lines_skipped > 0)
1705 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001706 // Line was skipped, add an empty line.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001707 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001708 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001709 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001710 }
1711
1712 if (len == 0)
1713 p = NULL;
1714 else
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001715 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001716 if ((p != NULL || len == 0)
1717 && ga_grow(&term->tl_scrollback, 1) == OK)
1718 {
1719 garray_T ga;
1720 int width;
1721 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1722 + term->tl_scrollback.ga_len;
1723
1724 ga_init2(&ga, 1, 100);
1725 for (pos.col = 0; pos.col < len; pos.col += width)
1726 {
1727 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1728 {
1729 width = 1;
1730 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1731 if (ga_grow(&ga, 1) == OK)
1732 ga.ga_len += utf_char2bytes(' ',
1733 (char_u *)ga.ga_data + ga.ga_len);
1734 }
1735 else
1736 {
1737 width = cell.width;
1738
1739 cell2cellattr(&cell, &p[pos.col]);
1740
Bram Moolenaara79fd562018-12-20 20:47:32 +01001741 // Each character can be up to 6 bytes.
1742 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001743 {
1744 int i;
1745 int c;
1746
1747 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1748 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1749 (char_u *)ga.ga_data + ga.ga_len);
1750 }
1751 }
1752 }
1753 line->sb_cols = len;
1754 line->sb_cells = p;
1755 line->sb_fill_attr = new_fill_attr;
1756 fill_attr = new_fill_attr;
1757 ++term->tl_scrollback.ga_len;
1758
1759 if (ga_grow(&ga, 1) == FAIL)
1760 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1761 else
1762 {
1763 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1764 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1765 }
1766 ga_clear(&ga);
1767 }
1768 else
1769 vim_free(p);
1770 }
1771 }
1772
Bram Moolenaarf3aea592018-11-11 22:18:21 +01001773 // Add trailing empty lines.
1774 for (pos.row = term->tl_scrollback.ga_len;
1775 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1776 ++pos.row)
1777 {
1778 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1779 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1780 }
1781
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001782 term->tl_dirty_snapshot = FALSE;
1783#ifdef FEAT_TIMERS
1784 term->tl_timer_set = FALSE;
1785#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001786}
1787
1788/*
1789 * If needed, add the current lines of the terminal to scrollback and to the
1790 * buffer. Called after the job has ended and when switching to
1791 * Terminal-Normal mode.
1792 * When "redraw" is TRUE redraw the windows that show the terminal.
1793 */
1794 static void
1795may_move_terminal_to_buffer(term_T *term, int redraw)
1796{
1797 win_T *wp;
1798
1799 if (term->tl_vterm == NULL)
1800 return;
1801
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001802 // Update the snapshot only if something changes or the buffer does not
1803 // have all the lines.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001804 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
1805 <= term->tl_scrollback_scrolled)
1806 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001807
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001808 // Obtain the current background color.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001809 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1810 &term->tl_default_color.fg, &term->tl_default_color.bg);
1811
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001812 if (redraw)
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001813 FOR_ALL_WINDOWS(wp)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001814 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001815 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001816 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001817 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1818 wp->w_cursor.col = 0;
1819 wp->w_valid = 0;
1820 if (wp->w_cursor.lnum >= wp->w_height)
1821 {
1822 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001823
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001824 if (wp->w_topline < min_topline)
1825 wp->w_topline = min_topline;
1826 }
1827 redraw_win_later(wp, NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001828 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001829 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001830}
1831
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001832#if defined(FEAT_TIMERS) || defined(PROTO)
1833/*
1834 * Check if any terminal timer expired. If so, copy text from the terminal to
1835 * the buffer.
1836 * Return the time until the next timer will expire.
1837 */
1838 int
1839term_check_timers(int next_due_arg, proftime_T *now)
1840{
1841 term_T *term;
1842 int next_due = next_due_arg;
1843
1844 for (term = first_term; term != NULL; term = term->tl_next)
1845 {
1846 if (term->tl_timer_set && !term->tl_normal_mode)
1847 {
1848 long this_due = proftime_time_left(&term->tl_timer_due, now);
1849
1850 if (this_due <= 1)
1851 {
1852 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001853 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001854 }
1855 else if (next_due == -1 || next_due > this_due)
1856 next_due = this_due;
1857 }
1858 }
1859
1860 return next_due;
1861}
1862#endif
1863
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001864/*
1865 * When "normal_mode" is TRUE set the terminal to Terminal-Normal mode,
1866 * otherwise end it.
1867 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001868 static void
1869set_terminal_mode(term_T *term, int normal_mode)
1870{
1871 term->tl_normal_mode = normal_mode;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001872 if (!normal_mode)
1873 handle_postponed_scrollback(term);
Bram Moolenaard23a8232018-02-10 18:45:26 +01001874 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001875 if (term->tl_buffer == curbuf)
1876 maketitle();
1877}
1878
1879/*
1880 * Called after the job if finished and Terminal mode is not active:
1881 * Move the vterm contents into the scrollback buffer and free the vterm.
1882 */
1883 static void
1884cleanup_vterm(term_T *term)
1885{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001886 set_terminal_mode(term, FALSE);
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001887 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001888 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001889 term_free_vterm(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001890}
1891
1892/*
1893 * Switch from Terminal-Job mode to Terminal-Normal mode.
1894 * Suspends updating the terminal window.
1895 */
1896 static void
1897term_enter_normal_mode(void)
1898{
1899 term_T *term = curbuf->b_term;
1900
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001901 set_terminal_mode(term, TRUE);
1902
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001903 // Append the current terminal contents to the buffer.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001904 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001905
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001906 // Move the window cursor to the position of the cursor in the
1907 // terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001908 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1909 + term->tl_cursor_pos.row + 1;
1910 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02001911 if (coladvance(term->tl_cursor_pos.col) == FAIL)
1912 coladvance(MAXCOL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001913
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001914 // Display the same lines as in the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001915 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1916}
1917
1918/*
1919 * Returns TRUE if the current window contains a terminal and we are in
1920 * Terminal-Normal mode.
1921 */
1922 int
1923term_in_normal_mode(void)
1924{
1925 term_T *term = curbuf->b_term;
1926
1927 return term != NULL && term->tl_normal_mode;
1928}
1929
1930/*
1931 * Switch from Terminal-Normal mode to Terminal-Job mode.
1932 * Restores updating the terminal window.
1933 */
1934 void
1935term_enter_job_mode()
1936{
1937 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001938
1939 set_terminal_mode(term, FALSE);
1940
1941 if (term->tl_channel_closed)
1942 cleanup_vterm(term);
1943 redraw_buf_and_status_later(curbuf, NOT_VALID);
1944}
1945
1946/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001947 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001948 * Note: while waiting a terminal may be closed and freed if the channel is
1949 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001950 */
1951 static int
1952term_vgetc()
1953{
1954 int c;
1955 int save_State = State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001956 int modify_other_keys =
1957 vterm_is_modify_other_keys(curbuf->b_term->tl_vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001958
1959 State = TERMINAL;
1960 got_int = FALSE;
Bram Moolenaar4f974752019-02-17 17:44:42 +01001961#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001962 ctrl_break_was_pressed = FALSE;
1963#endif
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001964 if (modify_other_keys)
1965 ++no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001966 c = vgetc();
1967 got_int = FALSE;
1968 State = save_State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001969 if (modify_other_keys)
1970 --no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001971 return c;
1972}
1973
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001974static int mouse_was_outside = FALSE;
1975
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001976/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001977 * Send key "c" with modifiers "modmask" to terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001978 * Return FAIL when the key needs to be handled in Normal mode.
1979 * Return OK when the key was dropped or sent to the terminal.
1980 */
1981 int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001982send_keys_to_term(term_T *term, int c, int modmask, int typed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001983{
1984 char msg[KEY_BUF_LEN];
1985 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001986 int dragging_outside = FALSE;
1987
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001988 // Catch keys that need to be handled as in Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001989 switch (c)
1990 {
1991 case NUL:
1992 case K_ZERO:
1993 if (typed)
1994 stuffcharReadbuff(c);
1995 return FAIL;
1996
Bram Moolenaar231a2db2018-05-06 13:53:50 +02001997 case K_TABLINE:
1998 stuffcharReadbuff(c);
1999 return FAIL;
2000
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002001 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002002 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002003 return FAIL;
2004
2005 case K_LEFTDRAG:
2006 case K_MIDDLEDRAG:
2007 case K_RIGHTDRAG:
2008 case K_X1DRAG:
2009 case K_X2DRAG:
2010 dragging_outside = mouse_was_outside;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002011 // FALLTHROUGH
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002012 case K_LEFTMOUSE:
2013 case K_LEFTMOUSE_NM:
2014 case K_LEFTRELEASE:
2015 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01002016 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002017 case K_MIDDLEMOUSE:
2018 case K_MIDDLERELEASE:
2019 case K_RIGHTMOUSE:
2020 case K_RIGHTRELEASE:
2021 case K_X1MOUSE:
2022 case K_X1RELEASE:
2023 case K_X2MOUSE:
2024 case K_X2RELEASE:
2025
2026 case K_MOUSEUP:
2027 case K_MOUSEDOWN:
2028 case K_MOUSELEFT:
2029 case K_MOUSERIGHT:
2030 if (mouse_row < W_WINROW(curwin)
Bram Moolenaarce6179c2017-12-05 13:06:16 +01002031 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
Bram Moolenaar53f81742017-09-22 14:35:51 +02002032 || mouse_col < curwin->w_wincol
Bram Moolenaarce6179c2017-12-05 13:06:16 +01002033 || mouse_col >= W_ENDCOL(curwin)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002034 || dragging_outside)
2035 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002036 // click or scroll outside the current window or on status line
2037 // or vertical separator
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002038 if (typed)
2039 {
2040 stuffcharReadbuff(c);
2041 mouse_was_outside = TRUE;
2042 }
2043 return FAIL;
2044 }
2045 }
2046 if (typed)
2047 mouse_was_outside = FALSE;
2048
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002049 // Convert the typed key to a sequence of bytes for the job.
2050 len = term_convert_key(term, c, modmask, msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002051 if (len > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002052 // TODO: if FAIL is returned, stop?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002053 channel_send(term->tl_job->jv_channel, get_tty_part(term),
2054 (char_u *)msg, (int)len, NULL);
2055
2056 return OK;
2057}
2058
2059 static void
2060position_cursor(win_T *wp, VTermPos *pos)
2061{
2062 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
2063 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
2064 wp->w_valid |= (VALID_WCOL|VALID_WROW);
2065}
2066
2067/*
2068 * Handle CTRL-W "": send register contents to the job.
2069 */
2070 static void
2071term_paste_register(int prev_c UNUSED)
2072{
2073 int c;
2074 list_T *l;
2075 listitem_T *item;
2076 long reglen = 0;
2077 int type;
2078
2079#ifdef FEAT_CMDL_INFO
2080 if (add_to_showcmd(prev_c))
2081 if (add_to_showcmd('"'))
2082 out_flush();
2083#endif
2084 c = term_vgetc();
2085#ifdef FEAT_CMDL_INFO
2086 clear_showcmd();
2087#endif
2088 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002089 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002090 return;
2091
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002092 // CTRL-W "= prompt for expression to evaluate.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002093 if (c == '=' && get_expr_register() != '=')
2094 return;
2095 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002096 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002097 return;
2098
2099 l = (list_T *)get_reg_contents(c, GREG_LIST);
2100 if (l != NULL)
2101 {
2102 type = get_reg_type(c, &reglen);
2103 for (item = l->lv_first; item != NULL; item = item->li_next)
2104 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01002105 char_u *s = tv_get_string(&item->li_tv);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002106#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002107 char_u *tmp = s;
2108
2109 if (!enc_utf8 && enc_codepage > 0)
2110 {
2111 WCHAR *ret = NULL;
2112 int length = 0;
2113
2114 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2115 (int)STRLEN(s), &ret, &length);
2116 if (ret != NULL)
2117 {
2118 WideCharToMultiByte_alloc(CP_UTF8, 0,
2119 ret, length, (char **)&s, &length, 0, 0);
2120 vim_free(ret);
2121 }
2122 }
2123#endif
2124 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2125 s, (int)STRLEN(s), NULL);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002126#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002127 if (tmp != s)
2128 vim_free(s);
2129#endif
2130
2131 if (item->li_next != NULL || type == MLINE)
2132 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2133 (char_u *)"\r", 1, NULL);
2134 }
2135 list_free(l);
2136 }
2137}
2138
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002139/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002140 * Return TRUE when waiting for a character in the terminal, the cursor of the
2141 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002142 */
2143 int
2144terminal_is_active()
2145{
2146 return in_terminal_loop != NULL;
2147}
2148
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002149#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002150 cursorentry_T *
2151term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2152{
2153 term_T *term = in_terminal_loop;
2154 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002155 int id;
2156 guicolor_T term_fg, term_bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002157
2158 vim_memset(&entry, 0, sizeof(entry));
2159 entry.shape = entry.mshape =
2160 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2161 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2162 SHAPE_BLOCK;
2163 entry.percentage = 20;
2164 if (term->tl_cursor_blink)
2165 {
2166 entry.blinkwait = 700;
2167 entry.blinkon = 400;
2168 entry.blinkoff = 250;
2169 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002170
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002171 // The "Terminal" highlight group overrules the defaults.
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002172 id = syn_name2id((char_u *)"Terminal");
2173 if (id != 0)
2174 {
2175 syn_id2colors(id, &term_fg, &term_bg);
2176 *fg = term_bg;
2177 }
2178 else
2179 *fg = gui.back_pixel;
2180
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002181 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002182 {
2183 if (id != 0)
2184 *bg = term_fg;
2185 else
2186 *bg = gui.norm_pixel;
2187 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002188 else
2189 *bg = color_name2handle(term->tl_cursor_color);
2190 entry.name = "n";
2191 entry.used_for = SHAPE_CURSOR;
2192
2193 return &entry;
2194}
2195#endif
2196
Bram Moolenaard317b382018-02-08 22:33:31 +01002197 static void
2198may_output_cursor_props(void)
2199{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002200 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
Bram Moolenaard317b382018-02-08 22:33:31 +01002201 || last_set_cursor_shape != desired_cursor_shape
2202 || last_set_cursor_blink != desired_cursor_blink)
2203 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002204 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002205 last_set_cursor_shape = desired_cursor_shape;
2206 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002207 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002208 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002209 // this will restore the initial cursor style, if possible
Bram Moolenaard317b382018-02-08 22:33:31 +01002210 ui_cursor_shape_forced(TRUE);
2211 else
2212 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2213 }
2214}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002215
Bram Moolenaard317b382018-02-08 22:33:31 +01002216/*
2217 * Set the cursor color and shape, if not last set to these.
2218 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002219 static void
2220may_set_cursor_props(term_T *term)
2221{
2222#ifdef FEAT_GUI
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002223 // For the GUI the cursor properties are obtained with
2224 // term_get_cursor_shape().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002225 if (gui.in_use)
2226 return;
2227#endif
2228 if (in_terminal_loop == term)
2229 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002230 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002231 desired_cursor_shape = term->tl_cursor_shape;
2232 desired_cursor_blink = term->tl_cursor_blink;
2233 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002234 }
2235}
2236
Bram Moolenaard317b382018-02-08 22:33:31 +01002237/*
2238 * Reset the desired cursor properties and restore them when needed.
2239 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002240 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002241prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002242{
2243#ifdef FEAT_GUI
2244 if (gui.in_use)
2245 return;
2246#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002247 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002248 desired_cursor_shape = -1;
2249 desired_cursor_blink = -1;
2250 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002251}
2252
2253/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002254 * Returns TRUE if the current window contains a terminal and we are sending
2255 * keys to the job.
2256 * If "check_job_status" is TRUE update the job status.
2257 */
2258 static int
2259term_use_loop_check(int check_job_status)
2260{
2261 term_T *term = curbuf->b_term;
2262
2263 return term != NULL
2264 && !term->tl_normal_mode
2265 && term->tl_vterm != NULL
2266 && term_job_running_check(term, check_job_status);
2267}
2268
2269/*
2270 * Returns TRUE if the current window contains a terminal and we are sending
2271 * keys to the job.
2272 */
2273 int
2274term_use_loop(void)
2275{
2276 return term_use_loop_check(FALSE);
2277}
2278
2279/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002280 * Called when entering a window with the mouse. If this is a terminal window
2281 * we may want to change state.
2282 */
2283 void
2284term_win_entered()
2285{
2286 term_T *term = curbuf->b_term;
2287
2288 if (term != NULL)
2289 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002290 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002291 {
2292 reset_VIsual_and_resel();
2293 if (State & INSERT)
2294 stop_insert_mode = TRUE;
2295 }
2296 mouse_was_outside = FALSE;
2297 enter_mouse_col = mouse_col;
2298 enter_mouse_row = mouse_row;
2299 }
2300}
2301
2302/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002303 * vgetc() may not include CTRL in the key when modify_other_keys is set.
2304 * Return the Ctrl-key value in that case.
2305 */
2306 static int
2307raw_c_to_ctrl(int c)
2308{
2309 if ((mod_mask & MOD_MASK_CTRL)
2310 && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')))
2311 return c & 0x1f;
2312 return c;
2313}
2314
2315/*
2316 * When modify_other_keys is set then do the reverse of raw_c_to_ctrl().
2317 * May set "mod_mask".
2318 */
2319 static int
2320ctrl_to_raw_c(int c)
2321{
2322 if (c < 0x20 && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
2323 {
2324 mod_mask |= MOD_MASK_CTRL;
2325 return c + '@';
2326 }
2327 return c;
2328}
2329
2330/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002331 * Wait for input and send it to the job.
2332 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2333 * when there is no more typahead.
2334 * Return when the start of a CTRL-W command is typed or anything else that
2335 * should be handled as a Normal mode command.
2336 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2337 * the terminal was closed.
2338 */
2339 int
2340terminal_loop(int blocking)
2341{
2342 int c;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002343 int raw_c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002344 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002345 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002346#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002347 int tty_fd = curbuf->b_term->tl_job->jv_channel
2348 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002349#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002350 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002351
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002352 // Remember the terminal we are sending keys to. However, the terminal
2353 // might be closed while waiting for a character, e.g. typing "exit" in a
2354 // shell and ++close was used. Therefore use curbuf->b_term instead of a
2355 // stored reference.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002356 in_terminal_loop = curbuf->b_term;
2357
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002358 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002359 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002360 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002361 if (termwinkey == Ctrl_W)
2362 termwinkey = 0;
2363 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002364 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
2365 may_set_cursor_props(curbuf->b_term);
2366
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002367 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002368 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002369#ifdef FEAT_GUI
2370 if (!curbuf->b_term->tl_system)
2371#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002372 // TODO: skip screen update when handling a sequence of keys.
2373 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002374 while (must_redraw != 0)
2375 if (update_screen(0) == FAIL)
2376 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002377 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002378 // job finished while redrawing
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002379 break;
2380
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002381 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002382 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002383
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002384 raw_c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002385 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002386 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002387 // Job finished while waiting for a character. Push back the
2388 // received character.
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002389 if (raw_c != K_IGNORE)
2390 vungetc(raw_c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002391 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002392 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002393 if (raw_c == K_IGNORE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002394 continue;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002395 c = raw_c_to_ctrl(raw_c);
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002396
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002397#ifdef UNIX
2398 /*
2399 * The shell or another program may change the tty settings. Getting
2400 * them for every typed character is a bit of overhead, but it's needed
2401 * for the first character typed, e.g. when Vim starts in a shell.
2402 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002403 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002404 {
2405 ttyinfo_T info;
2406
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002407 // Get the current backspace character of the pty.
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002408 if (get_tty_info(tty_fd, &info) == OK)
2409 term_backspace_char = info.backspace;
2410 }
2411#endif
2412
Bram Moolenaar4f974752019-02-17 17:44:42 +01002413#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002414 // On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2415 // Use CTRL-BREAK to kill the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002416 if (ctrl_break_was_pressed)
2417 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2418#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002419 // Was either CTRL-W (termwinkey) or CTRL-\ pressed?
2420 // Not in a system terminal.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002421 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002422#ifdef FEAT_GUI
2423 && !curbuf->b_term->tl_system
2424#endif
2425 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002426 {
2427 int prev_c = c;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002428 int prev_raw_c = raw_c;
2429 int prev_mod_mask = mod_mask;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002430
2431#ifdef FEAT_CMDL_INFO
2432 if (add_to_showcmd(c))
2433 out_flush();
2434#endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002435 raw_c = term_vgetc();
2436 c = raw_c_to_ctrl(raw_c);
2437
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002438#ifdef FEAT_CMDL_INFO
2439 clear_showcmd();
2440#endif
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002441 if (!term_use_loop_check(TRUE)
2442 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002443 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002444 break;
2445
2446 if (prev_c == Ctrl_BSL)
2447 {
2448 if (c == Ctrl_N)
2449 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002450 // CTRL-\ CTRL-N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002451 term_enter_normal_mode();
2452 ret = FAIL;
2453 goto theend;
2454 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002455 // Send both keys to the terminal, first one here, second one
2456 // below.
2457 send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask,
2458 TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002459 }
2460 else if (c == Ctrl_C)
2461 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002462 // "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002463 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2464 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002465 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002466 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002467 // "CTRL-W .": send CTRL-W to the job
2468 // "'termwinkey' .": send 'termwinkey' to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002469 raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002470 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002471 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002472 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002473 // "CTRL-W CTRL-\": send CTRL-\ to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002474 raw_c = ctrl_to_raw_c(Ctrl_BSL);
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002475 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002476 else if (c == 'N')
2477 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002478 // CTRL-W N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002479 term_enter_normal_mode();
2480 ret = FAIL;
2481 goto theend;
2482 }
2483 else if (c == '"')
2484 {
2485 term_paste_register(prev_c);
2486 continue;
2487 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002488 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002489 {
Bram Moolenaara4b26992019-08-15 20:58:54 +02002490 char_u buf[MB_MAXBYTES + 2];
2491
2492 // Put the command into the typeahead buffer, when using the
2493 // stuff buffer KeyStuffed is set and 'langmap' won't be used.
2494 buf[0] = Ctrl_W;
2495 buf[(*mb_char2bytes)(c, buf + 1) + 1] = NUL;
2496 ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002497 ret = OK;
2498 goto theend;
2499 }
2500 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01002501# ifdef MSWIN
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002502 if (!enc_utf8 && has_mbyte && raw_c >= 0x80)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002503 {
2504 WCHAR wc;
2505 char_u mb[3];
2506
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002507 mb[0] = (unsigned)raw_c >> 8;
2508 mb[1] = raw_c;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002509 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002510 raw_c = wc;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002511 }
2512# endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002513 if (send_keys_to_term(curbuf->b_term, raw_c, mod_mask, TRUE) != OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002514 {
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002515 if (raw_c == K_MOUSEMOVE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002516 // We are sure to come back here, don't reset the cursor color
2517 // and shape to avoid flickering.
Bram Moolenaard317b382018-02-08 22:33:31 +01002518 restore_cursor = FALSE;
2519
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002520 ret = OK;
2521 goto theend;
2522 }
2523 }
2524 ret = FAIL;
2525
2526theend:
2527 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002528 if (restore_cursor)
2529 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002530
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002531 // Move a snapshot of the screen contents to the buffer, so that completion
2532 // works in other buffers.
Bram Moolenaar620020e2018-05-13 19:06:12 +02002533 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2534 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002535
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002536 return ret;
2537}
2538
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002539 static void
2540may_toggle_cursor(term_T *term)
2541{
2542 if (in_terminal_loop == term)
2543 {
2544 if (term->tl_cursor_visible)
2545 cursor_on();
2546 else
2547 cursor_off();
2548 }
2549}
2550
2551/*
2552 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002553 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002554 */
2555 static int
2556color2index(VTermColor *color, int fg, int *boldp)
2557{
2558 int red = color->red;
2559 int blue = color->blue;
2560 int green = color->green;
2561
Bram Moolenaar46359e12017-11-29 22:33:38 +01002562 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002563 {
Bram Moolenaar1d79ce82019-04-12 22:27:39 +02002564 // The first 16 colors and default: use the ANSI index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002565 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002566 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002567 case 0: return 0;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002568 case 1: return lookup_color( 0, fg, boldp) + 1; // black
2569 case 2: return lookup_color( 4, fg, boldp) + 1; // dark red
2570 case 3: return lookup_color( 2, fg, boldp) + 1; // dark green
2571 case 4: return lookup_color( 6, fg, boldp) + 1; // brown
2572 case 5: return lookup_color( 1, fg, boldp) + 1; // dark blue
2573 case 6: return lookup_color( 5, fg, boldp) + 1; // dark magenta
2574 case 7: return lookup_color( 3, fg, boldp) + 1; // dark cyan
2575 case 8: return lookup_color( 8, fg, boldp) + 1; // light grey
2576 case 9: return lookup_color(12, fg, boldp) + 1; // dark grey
2577 case 10: return lookup_color(20, fg, boldp) + 1; // red
2578 case 11: return lookup_color(16, fg, boldp) + 1; // green
2579 case 12: return lookup_color(24, fg, boldp) + 1; // yellow
2580 case 13: return lookup_color(14, fg, boldp) + 1; // blue
2581 case 14: return lookup_color(22, fg, boldp) + 1; // magenta
2582 case 15: return lookup_color(18, fg, boldp) + 1; // cyan
2583 case 16: return lookup_color(26, fg, boldp) + 1; // white
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002584 }
2585 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002586
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002587 if (t_colors >= 256)
2588 {
2589 if (red == blue && red == green)
2590 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002591 // 24-color greyscale plus white and black
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002592 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002593 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2594 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2595 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002596 int i;
2597
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002598 if (red < 5)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002599 return 17; // 00/00/00
2600 if (red > 245) // ff/ff/ff
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002601 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002602 for (i = 0; i < 23; ++i)
2603 if (red < cutoff[i])
2604 return i + 233;
2605 return 256;
2606 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002607 {
2608 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2609 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002610
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002611 // 216-color cube
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002612 for (ri = 0; ri < 5; ++ri)
2613 if (red < cutoff[ri])
2614 break;
2615 for (gi = 0; gi < 5; ++gi)
2616 if (green < cutoff[gi])
2617 break;
2618 for (bi = 0; bi < 5; ++bi)
2619 if (blue < cutoff[bi])
2620 break;
2621 return 17 + ri * 36 + gi * 6 + bi;
2622 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002623 }
2624 return 0;
2625}
2626
2627/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002628 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002629 */
2630 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002631vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002632{
2633 int attr = 0;
2634
2635 if (cellattrs.bold)
2636 attr |= HL_BOLD;
2637 if (cellattrs.underline)
2638 attr |= HL_UNDERLINE;
2639 if (cellattrs.italic)
2640 attr |= HL_ITALIC;
2641 if (cellattrs.strike)
2642 attr |= HL_STRIKETHROUGH;
2643 if (cellattrs.reverse)
2644 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002645 return attr;
2646}
2647
2648/*
2649 * Store Vterm attributes in "cell" from highlight flags.
2650 */
2651 static void
2652hl2vtermAttr(int attr, cellattr_T *cell)
2653{
2654 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2655 if (attr & HL_BOLD)
2656 cell->attrs.bold = 1;
2657 if (attr & HL_UNDERLINE)
2658 cell->attrs.underline = 1;
2659 if (attr & HL_ITALIC)
2660 cell->attrs.italic = 1;
2661 if (attr & HL_STRIKETHROUGH)
2662 cell->attrs.strike = 1;
2663 if (attr & HL_INVERSE)
2664 cell->attrs.reverse = 1;
2665}
2666
2667/*
2668 * Convert the attributes of a vterm cell into an attribute index.
2669 */
2670 static int
2671cell2attr(VTermScreenCellAttrs cellattrs, VTermColor cellfg, VTermColor cellbg)
2672{
2673 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002674
2675#ifdef FEAT_GUI
2676 if (gui.in_use)
2677 {
2678 guicolor_T fg, bg;
2679
2680 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2681 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2682 return get_gui_attr_idx(attr, fg, bg);
2683 }
2684 else
2685#endif
2686#ifdef FEAT_TERMGUICOLORS
2687 if (p_tgc)
2688 {
2689 guicolor_T fg, bg;
2690
2691 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2692 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2693
2694 return get_tgc_attr_idx(attr, fg, bg);
2695 }
2696 else
2697#endif
2698 {
2699 int bold = MAYBE;
2700 int fg = color2index(&cellfg, TRUE, &bold);
2701 int bg = color2index(&cellbg, FALSE, &bold);
2702
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002703 // Use the "Terminal" highlighting for the default colors.
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002704 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002705 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002706 if (fg == 0 && term_default_cterm_fg >= 0)
2707 fg = term_default_cterm_fg + 1;
2708 if (bg == 0 && term_default_cterm_bg >= 0)
2709 bg = term_default_cterm_bg + 1;
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002710 }
2711
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002712 // with 8 colors set the bold attribute to get a bright foreground
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002713 if (bold == TRUE)
2714 attr |= HL_BOLD;
2715 return get_cterm_attr_idx(attr, fg, bg);
2716 }
2717 return 0;
2718}
2719
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002720 static void
2721set_dirty_snapshot(term_T *term)
2722{
2723 term->tl_dirty_snapshot = TRUE;
2724#ifdef FEAT_TIMERS
2725 if (!term->tl_normal_mode)
2726 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002727 // Update the snapshot after 100 msec of not getting updates.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002728 profile_setlimit(100L, &term->tl_timer_due);
2729 term->tl_timer_set = TRUE;
2730 }
2731#endif
2732}
2733
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002734 static int
2735handle_damage(VTermRect rect, void *user)
2736{
2737 term_T *term = (term_T *)user;
2738
2739 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2740 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002741 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002742 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002743 return 1;
2744}
2745
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002746 static void
2747term_scroll_up(term_T *term, int start_row, int count)
2748{
2749 win_T *wp;
2750 VTermColor fg, bg;
2751 VTermScreenCellAttrs attr;
2752 int clear_attr;
2753
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002754 // Set the color to clear lines with.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002755 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2756 &fg, &bg);
2757 vim_memset(&attr, 0, sizeof(attr));
2758 clear_attr = cell2attr(attr, fg, bg);
2759
2760 FOR_ALL_WINDOWS(wp)
2761 {
2762 if (wp->w_buffer == term->tl_buffer)
2763 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
2764 }
2765}
2766
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002767 static int
2768handle_moverect(VTermRect dest, VTermRect src, void *user)
2769{
2770 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002771 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002772
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002773 // Scrolling up is done much more efficiently by deleting lines instead of
2774 // redrawing the text. But avoid doing this multiple times, postpone until
2775 // the redraw happens.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002776 if (dest.start_col == src.start_col
2777 && dest.end_col == src.end_col
2778 && dest.start_row < src.start_row)
2779 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002780 if (dest.start_row == 0)
2781 term->tl_postponed_scroll += count;
2782 else
2783 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002784 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002785
2786 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2787 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002788 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002789
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002790 // Note sure if the scrolling will work correctly, let's do a complete
2791 // redraw later.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002792 redraw_buf_later(term->tl_buffer, NOT_VALID);
2793 return 1;
2794}
2795
2796 static int
2797handle_movecursor(
2798 VTermPos pos,
2799 VTermPos oldpos UNUSED,
2800 int visible,
2801 void *user)
2802{
2803 term_T *term = (term_T *)user;
2804 win_T *wp;
2805
2806 term->tl_cursor_pos = pos;
2807 term->tl_cursor_visible = visible;
2808
2809 FOR_ALL_WINDOWS(wp)
2810 {
2811 if (wp->w_buffer == term->tl_buffer)
2812 position_cursor(wp, &pos);
2813 }
2814 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2815 {
2816 may_toggle_cursor(term);
2817 update_cursor(term, term->tl_cursor_visible);
2818 }
2819
2820 return 1;
2821}
2822
2823 static int
2824handle_settermprop(
2825 VTermProp prop,
2826 VTermValue *value,
2827 void *user)
2828{
2829 term_T *term = (term_T *)user;
2830
2831 switch (prop)
2832 {
2833 case VTERM_PROP_TITLE:
2834 vim_free(term->tl_title);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002835 // a blank title isn't useful, make it empty, so that "running" is
2836 // displayed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002837 if (*skipwhite((char_u *)value->string) == NUL)
2838 term->tl_title = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002839 // Same as blank
2840 else if (term->tl_arg0_cmd != NULL
2841 && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string,
2842 (int)STRLEN(term->tl_arg0_cmd)) == 0)
2843 term->tl_title = NULL;
2844 // Empty corrupted data of winpty
2845 else if (STRNCMP(" - ", (char_u *)value->string, 4) == 0)
2846 term->tl_title = NULL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01002847#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002848 else if (!enc_utf8 && enc_codepage > 0)
2849 {
2850 WCHAR *ret = NULL;
2851 int length = 0;
2852
2853 MultiByteToWideChar_alloc(CP_UTF8, 0,
2854 (char*)value->string, (int)STRLEN(value->string),
2855 &ret, &length);
2856 if (ret != NULL)
2857 {
2858 WideCharToMultiByte_alloc(enc_codepage, 0,
2859 ret, length, (char**)&term->tl_title,
2860 &length, 0, 0);
2861 vim_free(ret);
2862 }
2863 }
2864#endif
2865 else
2866 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002867 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002868 if (term == curbuf->b_term)
2869 maketitle();
2870 break;
2871
2872 case VTERM_PROP_CURSORVISIBLE:
2873 term->tl_cursor_visible = value->boolean;
2874 may_toggle_cursor(term);
2875 out_flush();
2876 break;
2877
2878 case VTERM_PROP_CURSORBLINK:
2879 term->tl_cursor_blink = value->boolean;
2880 may_set_cursor_props(term);
2881 break;
2882
2883 case VTERM_PROP_CURSORSHAPE:
2884 term->tl_cursor_shape = value->number;
2885 may_set_cursor_props(term);
2886 break;
2887
2888 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002889 cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002890 may_set_cursor_props(term);
2891 break;
2892
2893 case VTERM_PROP_ALTSCREEN:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002894 // TODO: do anything else?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002895 term->tl_using_altscreen = value->boolean;
2896 break;
2897
2898 default:
2899 break;
2900 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002901 // Always return 1, otherwise vterm doesn't store the value internally.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002902 return 1;
2903}
2904
2905/*
2906 * The job running in the terminal resized the terminal.
2907 */
2908 static int
2909handle_resize(int rows, int cols, void *user)
2910{
2911 term_T *term = (term_T *)user;
2912 win_T *wp;
2913
2914 term->tl_rows = rows;
2915 term->tl_cols = cols;
2916 if (term->tl_vterm_size_changed)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002917 // Size was set by vterm_set_size(), don't set the window size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002918 term->tl_vterm_size_changed = FALSE;
2919 else
2920 {
2921 FOR_ALL_WINDOWS(wp)
2922 {
2923 if (wp->w_buffer == term->tl_buffer)
2924 {
2925 win_setheight_win(rows, wp);
2926 win_setwidth_win(cols, wp);
2927 }
2928 }
2929 redraw_buf_later(term->tl_buffer, NOT_VALID);
2930 }
2931 return 1;
2932}
2933
2934/*
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002935 * If the number of lines that are stored goes over 'termscrollback' then
2936 * delete the first 10%.
2937 * "gap" points to tl_scrollback or tl_scrollback_postponed.
2938 * "update_buffer" is TRUE when the buffer should be updated.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002939 */
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002940 static void
2941limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002942{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002943 if (gap->ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002944 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002945 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002946 int i;
2947
2948 curbuf = term->tl_buffer;
2949 for (i = 0; i < todo; ++i)
2950 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002951 vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells);
2952 if (update_buffer)
2953 ml_delete(1, FALSE);
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002954 }
2955 curbuf = curwin->w_buffer;
2956
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002957 gap->ga_len -= todo;
2958 mch_memmove(gap->ga_data,
2959 (sb_line_T *)gap->ga_data + todo,
2960 sizeof(sb_line_T) * gap->ga_len);
2961 if (update_buffer)
2962 term->tl_scrollback_scrolled -= todo;
2963 }
2964}
2965
2966/*
2967 * Handle a line that is pushed off the top of the screen.
2968 */
2969 static int
2970handle_pushline(int cols, const VTermScreenCell *cells, void *user)
2971{
2972 term_T *term = (term_T *)user;
2973 garray_T *gap;
2974 int update_buffer;
2975
2976 if (term->tl_normal_mode)
2977 {
2978 // In Terminal-Normal mode the user interacts with the buffer, thus we
2979 // must not change it. Postpone adding the scrollback lines.
2980 gap = &term->tl_scrollback_postponed;
2981 update_buffer = FALSE;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002982 }
2983 else
2984 {
2985 // First remove the lines that were appended before, the pushed line
2986 // goes above it.
2987 cleanup_scrollback(term);
2988 gap = &term->tl_scrollback;
2989 update_buffer = TRUE;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02002990 }
2991
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002992 limit_scrollback(term, gap, update_buffer);
2993
2994 if (ga_grow(gap, 1) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002995 {
2996 cellattr_T *p = NULL;
2997 int len = 0;
2998 int i;
2999 int c;
3000 int col;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003001 int text_len;
3002 char_u *text;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003003 sb_line_T *line;
3004 garray_T ga;
3005 cellattr_T fill_attr = term->tl_default_color;
3006
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003007 // do not store empty cells at the end
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003008 for (i = 0; i < cols; ++i)
3009 if (cells[i].chars[0] != 0)
3010 len = i + 1;
3011 else
3012 cell2cellattr(&cells[i], &fill_attr);
3013
3014 ga_init2(&ga, 1, 100);
3015 if (len > 0)
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003016 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003017 if (p != NULL)
3018 {
3019 for (col = 0; col < len; col += cells[col].width)
3020 {
3021 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
3022 {
3023 ga.ga_len = 0;
3024 break;
3025 }
3026 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
3027 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
3028 (char_u *)ga.ga_data + ga.ga_len);
3029 cell2cellattr(&cells[col], &p[col]);
3030 }
3031 }
3032 if (ga_grow(&ga, 1) == FAIL)
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003033 {
3034 if (update_buffer)
3035 text = (char_u *)"";
3036 else
3037 text = vim_strsave((char_u *)"");
3038 text_len = 0;
3039 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003040 else
3041 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003042 text = ga.ga_data;
3043 text_len = ga.ga_len;
3044 *(text + text_len) = NUL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003045 }
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003046 if (update_buffer)
3047 add_scrollback_line_to_buffer(term, text, text_len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003048
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003049 line = (sb_line_T *)gap->ga_data + gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003050 line->sb_cols = len;
3051 line->sb_cells = p;
3052 line->sb_fill_attr = fill_attr;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003053 if (update_buffer)
3054 {
3055 line->sb_text = NULL;
3056 ++term->tl_scrollback_scrolled;
3057 ga_clear(&ga); // free the text
3058 }
3059 else
3060 {
3061 line->sb_text = text;
3062 ga_init(&ga); // text is kept in tl_scrollback_postponed
3063 }
3064 ++gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003065 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003066 return 0; // ignored
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003067}
3068
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003069/*
3070 * Called when leaving Terminal-Normal mode: deal with any scrollback that was
3071 * received and stored in tl_scrollback_postponed.
3072 */
3073 static void
3074handle_postponed_scrollback(term_T *term)
3075{
3076 int i;
3077
Bram Moolenaar8376c3d2019-03-19 20:50:43 +01003078 if (term->tl_scrollback_postponed.ga_len == 0)
3079 return;
3080 ch_log(NULL, "Moving postponed scrollback to scrollback");
3081
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003082 // First remove the lines that were appended before, the pushed lines go
3083 // above it.
3084 cleanup_scrollback(term);
3085
3086 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
3087 {
3088 char_u *text;
3089 sb_line_T *pp_line;
3090 sb_line_T *line;
3091
3092 if (ga_grow(&term->tl_scrollback, 1) == FAIL)
3093 break;
3094 pp_line = (sb_line_T *)term->tl_scrollback_postponed.ga_data + i;
3095
3096 text = pp_line->sb_text;
3097 if (text == NULL)
3098 text = (char_u *)"";
3099 add_scrollback_line_to_buffer(term, text, (int)STRLEN(text));
3100 vim_free(pp_line->sb_text);
3101
3102 line = (sb_line_T *)term->tl_scrollback.ga_data
3103 + term->tl_scrollback.ga_len;
3104 line->sb_cols = pp_line->sb_cols;
3105 line->sb_cells = pp_line->sb_cells;
3106 line->sb_fill_attr = pp_line->sb_fill_attr;
3107 line->sb_text = NULL;
3108 ++term->tl_scrollback_scrolled;
3109 ++term->tl_scrollback.ga_len;
3110 }
3111
3112 ga_clear(&term->tl_scrollback_postponed);
3113 limit_scrollback(term, &term->tl_scrollback, TRUE);
3114}
3115
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003116static VTermScreenCallbacks screen_callbacks = {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003117 handle_damage, // damage
3118 handle_moverect, // moverect
3119 handle_movecursor, // movecursor
3120 handle_settermprop, // settermprop
3121 NULL, // bell
3122 handle_resize, // resize
3123 handle_pushline, // sb_pushline
3124 NULL // sb_popline
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003125};
3126
3127/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003128 * Do the work after the channel of a terminal was closed.
3129 * Must be called only when updating_screen is FALSE.
3130 * Returns TRUE when a buffer was closed (list of terminals may have changed).
3131 */
3132 static int
3133term_after_channel_closed(term_T *term)
3134{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003135 // Unless in Terminal-Normal mode: clear the vterm.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003136 if (!term->tl_normal_mode)
3137 {
3138 int fnum = term->tl_buffer->b_fnum;
3139
3140 cleanup_vterm(term);
3141
3142 if (term->tl_finish == TL_FINISH_CLOSE)
3143 {
3144 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003145 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003146
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003147 // If this is the last normal window: exit Vim.
3148 if (term->tl_buffer->b_nwindows > 0 && only_one_window())
3149 {
3150 exarg_T ea;
3151
3152 vim_memset(&ea, 0, sizeof(ea));
3153 ex_quit(&ea);
3154 return TRUE;
3155 }
3156
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003157 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003158 ch_log(NULL, "terminal job finished, closing window");
3159 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003160 // Avoid closing the window if we temporarily use it.
Bram Moolenaar517f71a2019-06-17 22:40:41 +02003161 if (curwin == aucmd_win)
3162 do_set_w_closing = TRUE;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003163 if (do_set_w_closing)
3164 curwin->w_closing = TRUE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003165 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003166 if (do_set_w_closing)
3167 curwin->w_closing = FALSE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003168 aucmd_restbuf(&aco);
3169 return TRUE;
3170 }
3171 if (term->tl_finish == TL_FINISH_OPEN
3172 && term->tl_buffer->b_nwindows == 0)
3173 {
3174 char buf[50];
3175
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003176 // TODO: use term_opencmd
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003177 ch_log(NULL, "terminal job finished, opening window");
3178 vim_snprintf(buf, sizeof(buf),
3179 term->tl_opencmd == NULL
3180 ? "botright sbuf %d"
3181 : (char *)term->tl_opencmd, fnum);
3182 do_cmdline_cmd((char_u *)buf);
3183 }
3184 else
3185 ch_log(NULL, "terminal job finished");
3186 }
3187
3188 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
3189 return FALSE;
3190}
3191
3192/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003193 * Called when a channel has been closed.
3194 * If this was a channel for a terminal window then finish it up.
3195 */
3196 void
3197term_channel_closed(channel_T *ch)
3198{
3199 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003200 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003201 int did_one = FALSE;
3202
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003203 for (term = first_term; term != NULL; term = next_term)
3204 {
3205 next_term = term->tl_next;
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02003206 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003207 {
3208 term->tl_channel_closed = TRUE;
3209 did_one = TRUE;
3210
Bram Moolenaard23a8232018-02-10 18:45:26 +01003211 VIM_CLEAR(term->tl_title);
3212 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar4f974752019-02-17 17:44:42 +01003213#ifdef MSWIN
Bram Moolenaar402c8392018-05-06 22:01:42 +02003214 if (term->tl_out_fd != NULL)
3215 {
3216 fclose(term->tl_out_fd);
3217 term->tl_out_fd = NULL;
3218 }
3219#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003220
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003221 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003222 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003223 // Cannot open or close windows now. Can happen when
3224 // 'lazyredraw' is set.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003225 term->tl_channel_recently_closed = TRUE;
3226 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003227 }
3228
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003229 if (term_after_channel_closed(term))
3230 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003231 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003232 }
3233
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003234 if (did_one)
3235 {
3236 redraw_statuslines();
3237
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003238 // Need to break out of vgetc().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003239 ins_char_typebuf(K_IGNORE);
3240 typebuf_was_filled = TRUE;
3241
3242 term = curbuf->b_term;
3243 if (term != NULL)
3244 {
3245 if (term->tl_job == ch->ch_job)
3246 maketitle();
3247 update_cursor(term, term->tl_cursor_visible);
3248 }
3249 }
3250}
3251
3252/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003253 * To be called after resetting updating_screen: handle any terminal where the
3254 * channel was closed.
3255 */
3256 void
3257term_check_channel_closed_recently()
3258{
3259 term_T *term;
3260 term_T *next_term;
3261
3262 for (term = first_term; term != NULL; term = next_term)
3263 {
3264 next_term = term->tl_next;
3265 if (term->tl_channel_recently_closed)
3266 {
3267 term->tl_channel_recently_closed = FALSE;
3268 if (term_after_channel_closed(term))
3269 // start over, the list may have changed
3270 next_term = first_term;
3271 }
3272 }
3273}
3274
3275/*
Bram Moolenaar13568252018-03-16 20:46:58 +01003276 * Fill one screen line from a line of the terminal.
3277 * Advances "pos" to past the last column.
3278 */
3279 static void
3280term_line2screenline(VTermScreen *screen, VTermPos *pos, int max_col)
3281{
3282 int off = screen_get_current_line_off();
3283
3284 for (pos->col = 0; pos->col < max_col; )
3285 {
3286 VTermScreenCell cell;
3287 int c;
3288
3289 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
3290 vim_memset(&cell, 0, sizeof(cell));
3291
3292 c = cell.chars[0];
3293 if (c == NUL)
3294 {
3295 ScreenLines[off] = ' ';
3296 if (enc_utf8)
3297 ScreenLinesUC[off] = NUL;
3298 }
3299 else
3300 {
3301 if (enc_utf8)
3302 {
3303 int i;
3304
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003305 // composing chars
Bram Moolenaar13568252018-03-16 20:46:58 +01003306 for (i = 0; i < Screen_mco
3307 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3308 {
3309 ScreenLinesC[i][off] = cell.chars[i + 1];
3310 if (cell.chars[i + 1] == 0)
3311 break;
3312 }
3313 if (c >= 0x80 || (Screen_mco > 0
3314 && ScreenLinesC[0][off] != 0))
3315 {
3316 ScreenLines[off] = ' ';
3317 ScreenLinesUC[off] = c;
3318 }
3319 else
3320 {
3321 ScreenLines[off] = c;
3322 ScreenLinesUC[off] = NUL;
3323 }
3324 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01003325#ifdef MSWIN
Bram Moolenaar13568252018-03-16 20:46:58 +01003326 else if (has_mbyte && c >= 0x80)
3327 {
3328 char_u mb[MB_MAXBYTES+1];
3329 WCHAR wc = c;
3330
3331 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3332 (char*)mb, 2, 0, 0) > 1)
3333 {
3334 ScreenLines[off] = mb[0];
3335 ScreenLines[off + 1] = mb[1];
3336 cell.width = mb_ptr2cells(mb);
3337 }
3338 else
3339 ScreenLines[off] = c;
3340 }
3341#endif
3342 else
3343 ScreenLines[off] = c;
3344 }
3345 ScreenAttrs[off] = cell2attr(cell.attrs, cell.fg, cell.bg);
3346
3347 ++pos->col;
3348 ++off;
3349 if (cell.width == 2)
3350 {
3351 if (enc_utf8)
3352 ScreenLinesUC[off] = NUL;
3353
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003354 // don't set the second byte to NUL for a DBCS encoding, it
3355 // has been set above
Bram Moolenaar13568252018-03-16 20:46:58 +01003356 if (enc_utf8 || !has_mbyte)
3357 ScreenLines[off] = NUL;
3358
3359 ++pos->col;
3360 ++off;
3361 }
3362 }
3363}
3364
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003365#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003366 static void
3367update_system_term(term_T *term)
3368{
3369 VTermPos pos;
3370 VTermScreen *screen;
3371
3372 if (term->tl_vterm == NULL)
3373 return;
3374 screen = vterm_obtain_screen(term->tl_vterm);
3375
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003376 // Scroll up to make more room for terminal lines if needed.
Bram Moolenaar13568252018-03-16 20:46:58 +01003377 while (term->tl_toprow > 0
3378 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3379 {
3380 int save_p_more = p_more;
3381
3382 p_more = FALSE;
3383 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003384 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003385 p_more = save_p_more;
3386 --term->tl_toprow;
3387 }
3388
3389 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3390 && pos.row < Rows; ++pos.row)
3391 {
3392 if (pos.row < term->tl_rows)
3393 {
3394 int max_col = MIN(Columns, term->tl_cols);
3395
3396 term_line2screenline(screen, &pos, max_col);
3397 }
3398 else
3399 pos.col = 0;
3400
Bram Moolenaar4d784b22019-05-25 19:51:39 +02003401 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, 0);
Bram Moolenaar13568252018-03-16 20:46:58 +01003402 }
3403
3404 term->tl_dirty_row_start = MAX_ROW;
3405 term->tl_dirty_row_end = 0;
Bram Moolenaar13568252018-03-16 20:46:58 +01003406}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003407#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003408
3409/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003410 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3411 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003412 * Terminal-Normal mode.
3413 */
3414 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003415term_do_update_window(win_T *wp)
3416{
3417 term_T *term = wp->w_buffer->b_term;
3418
3419 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3420}
3421
3422/*
3423 * Called to update a window that contains an active terminal.
3424 */
3425 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003426term_update_window(win_T *wp)
3427{
3428 term_T *term = wp->w_buffer->b_term;
3429 VTerm *vterm;
3430 VTermScreen *screen;
3431 VTermState *state;
3432 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003433 int rows, cols;
3434 int newrows, newcols;
3435 int minsize;
3436 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003437
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003438 vterm = term->tl_vterm;
3439 screen = vterm_obtain_screen(vterm);
3440 state = vterm_obtain_state(vterm);
3441
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003442 // We use NOT_VALID on a resize or scroll, redraw everything then. With
3443 // SOME_VALID only redraw what was marked dirty.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003444 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003445 {
3446 term->tl_dirty_row_start = 0;
3447 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003448
3449 if (term->tl_postponed_scroll > 0
3450 && term->tl_postponed_scroll < term->tl_rows / 3)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003451 // Scrolling is usually faster than redrawing, when there are only
3452 // a few lines to scroll.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003453 term_scroll_up(term, 0, term->tl_postponed_scroll);
3454 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003455 }
3456
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003457 /*
3458 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003459 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003460 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003461 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003462
Bram Moolenaar498c2562018-04-15 23:45:15 +02003463 newrows = 99999;
3464 newcols = 99999;
3465 FOR_ALL_WINDOWS(twp)
3466 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003467 // When more than one window shows the same terminal, use the
3468 // smallest size.
Bram Moolenaar498c2562018-04-15 23:45:15 +02003469 if (twp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003470 {
Bram Moolenaar498c2562018-04-15 23:45:15 +02003471 newrows = MIN(newrows, twp->w_height);
3472 newcols = MIN(newcols, twp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003473 }
Bram Moolenaar498c2562018-04-15 23:45:15 +02003474 }
Bram Moolenaare0d749a2019-09-25 22:14:48 +02003475 if (newrows == 99999 || newcols == 99999)
3476 return; // safety exit
Bram Moolenaar498c2562018-04-15 23:45:15 +02003477 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3478 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3479
3480 if (term->tl_rows != newrows || term->tl_cols != newcols)
3481 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003482 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003483 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003484 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003485 newrows);
3486 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003487
3488 // Updating the terminal size will cause the snapshot to be cleared.
3489 // When not in terminal_loop() we need to restore it.
3490 if (term != in_terminal_loop)
3491 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003492 }
3493
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003494 // The cursor may have been moved when resizing.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003495 vterm_state_get_cursorpos(state, &pos);
3496 position_cursor(wp, &pos);
3497
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003498 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3499 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003500 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003501 if (pos.row < term->tl_rows)
3502 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003503 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003504
Bram Moolenaar13568252018-03-16 20:46:58 +01003505 term_line2screenline(screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003506 }
3507 else
3508 pos.col = 0;
3509
Bram Moolenaarf118d482018-03-13 13:14:00 +01003510 screen_line(wp->w_winrow + pos.row
3511#ifdef FEAT_MENU
3512 + winbar_height(wp)
3513#endif
Bram Moolenaar4d784b22019-05-25 19:51:39 +02003514 , wp->w_wincol, pos.col, wp->w_width, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003515 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003516 term->tl_dirty_row_start = MAX_ROW;
3517 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003518}
3519
3520/*
3521 * Return TRUE if "wp" is a terminal window where the job has finished.
3522 */
3523 int
3524term_is_finished(buf_T *buf)
3525{
3526 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3527}
3528
3529/*
3530 * Return TRUE if "wp" is a terminal window where the job has finished or we
3531 * are in Terminal-Normal mode, thus we show the buffer contents.
3532 */
3533 int
3534term_show_buffer(buf_T *buf)
3535{
3536 term_T *term = buf->b_term;
3537
3538 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3539}
3540
3541/*
3542 * The current buffer is going to be changed. If there is terminal
3543 * highlighting remove it now.
3544 */
3545 void
3546term_change_in_curbuf(void)
3547{
3548 term_T *term = curbuf->b_term;
3549
3550 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3551 {
3552 free_scrollback(term);
3553 redraw_buf_later(term->tl_buffer, NOT_VALID);
3554
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003555 // The buffer is now like a normal buffer, it cannot be easily
3556 // abandoned when changed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003557 set_string_option_direct((char_u *)"buftype", -1,
3558 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3559 }
3560}
3561
3562/*
3563 * Get the screen attribute for a position in the buffer.
3564 * Use a negative "col" to get the filler background color.
3565 */
3566 int
3567term_get_attr(buf_T *buf, linenr_T lnum, int col)
3568{
3569 term_T *term = buf->b_term;
3570 sb_line_T *line;
3571 cellattr_T *cellattr;
3572
3573 if (lnum > term->tl_scrollback.ga_len)
3574 cellattr = &term->tl_default_color;
3575 else
3576 {
3577 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3578 if (col < 0 || col >= line->sb_cols)
3579 cellattr = &line->sb_fill_attr;
3580 else
3581 cellattr = line->sb_cells + col;
3582 }
3583 return cell2attr(cellattr->attrs, cellattr->fg, cellattr->bg);
3584}
3585
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003586/*
3587 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003588 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003589 */
3590 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003591cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003592{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003593 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003594}
3595
3596/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003597 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003598 */
3599 static void
Bram Moolenaar52acb112018-03-18 19:20:22 +01003600init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003601{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003602 VTermColor *fg, *bg;
3603 int fgval, bgval;
3604 int id;
3605
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003606 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3607 term->tl_default_color.width = 1;
3608 fg = &term->tl_default_color.fg;
3609 bg = &term->tl_default_color.bg;
3610
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003611 // Vterm uses a default black background. Set it to white when
3612 // 'background' is "light".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003613 if (*p_bg == 'l')
3614 {
3615 fgval = 0;
3616 bgval = 255;
3617 }
3618 else
3619 {
3620 fgval = 255;
3621 bgval = 0;
3622 }
3623 fg->red = fg->green = fg->blue = fgval;
3624 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003625 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003626
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003627 // The "Terminal" highlight group overrules the defaults.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003628 id = syn_name2id((char_u *)"Terminal");
3629
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003630 // Use the actual color for the GUI and when 'termguicolors' is set.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003631#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3632 if (0
3633# ifdef FEAT_GUI
3634 || gui.in_use
3635# endif
3636# ifdef FEAT_TERMGUICOLORS
3637 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003638# ifdef FEAT_VTP
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003639 // Finally get INVALCOLOR on this execution path
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003640 || (!p_tgc && t_colors >= 256)
3641# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003642# endif
3643 )
3644 {
3645 guicolor_T fg_rgb = INVALCOLOR;
3646 guicolor_T bg_rgb = INVALCOLOR;
3647
3648 if (id != 0)
3649 syn_id2colors(id, &fg_rgb, &bg_rgb);
3650
3651# ifdef FEAT_GUI
3652 if (gui.in_use)
3653 {
3654 if (fg_rgb == INVALCOLOR)
3655 fg_rgb = gui.norm_pixel;
3656 if (bg_rgb == INVALCOLOR)
3657 bg_rgb = gui.back_pixel;
3658 }
3659# ifdef FEAT_TERMGUICOLORS
3660 else
3661# endif
3662# endif
3663# ifdef FEAT_TERMGUICOLORS
3664 {
3665 if (fg_rgb == INVALCOLOR)
3666 fg_rgb = cterm_normal_fg_gui_color;
3667 if (bg_rgb == INVALCOLOR)
3668 bg_rgb = cterm_normal_bg_gui_color;
3669 }
3670# endif
3671 if (fg_rgb != INVALCOLOR)
3672 {
3673 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3674
3675 fg->red = (unsigned)(rgb >> 16);
3676 fg->green = (unsigned)(rgb >> 8) & 255;
3677 fg->blue = (unsigned)rgb & 255;
3678 }
3679 if (bg_rgb != INVALCOLOR)
3680 {
3681 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3682
3683 bg->red = (unsigned)(rgb >> 16);
3684 bg->green = (unsigned)(rgb >> 8) & 255;
3685 bg->blue = (unsigned)rgb & 255;
3686 }
3687 }
3688 else
3689#endif
3690 if (id != 0 && t_colors >= 16)
3691 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003692 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003693 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003694 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003695 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003696 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003697 else
3698 {
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003699#if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003700 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003701#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003702
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003703 // In an MS-Windows console we know the normal colors.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003704 if (cterm_normal_fg_color > 0)
3705 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003706 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003707# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3708# ifdef VIMDLL
3709 if (!gui.in_use)
3710# endif
3711 {
3712 tmp = fg->red;
3713 fg->red = fg->blue;
3714 fg->blue = tmp;
3715 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003716# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003717 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003718# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003719 else
3720 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003721# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003722
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003723 if (cterm_normal_bg_color > 0)
3724 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003725 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003726# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3727# ifdef VIMDLL
3728 if (!gui.in_use)
3729# endif
3730 {
3731 tmp = fg->red;
3732 fg->red = fg->blue;
3733 fg->blue = tmp;
3734 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003735# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003736 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003737# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003738 else
3739 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003740# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003741 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003742}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003743
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003744#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3745/*
3746 * Set the 16 ANSI colors from array of RGB values
3747 */
3748 static void
3749set_vterm_palette(VTerm *vterm, long_u *rgb)
3750{
3751 int index = 0;
3752 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003753
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003754 for (; index < 16; index++)
3755 {
3756 VTermColor color;
Bram Moolenaaref8c83c2019-04-11 11:40:13 +02003757
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003758 color.red = (unsigned)(rgb[index] >> 16);
3759 color.green = (unsigned)(rgb[index] >> 8) & 255;
3760 color.blue = (unsigned)rgb[index] & 255;
3761 vterm_state_set_palette_color(state, index, &color);
3762 }
3763}
3764
3765/*
3766 * Set the ANSI color palette from a list of colors
3767 */
3768 static int
3769set_ansi_colors_list(VTerm *vterm, list_T *list)
3770{
3771 int n = 0;
3772 long_u rgb[16];
Bram Moolenaarb0992022020-01-30 14:55:42 +01003773 listitem_T *li;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003774
Bram Moolenaarb0992022020-01-30 14:55:42 +01003775 for (li = list->lv_first; li != NULL && n < 16; li = li->li_next, n++)
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003776 {
3777 char_u *color_name;
3778 guicolor_T guicolor;
3779
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003780 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003781 if (color_name == NULL)
3782 return FAIL;
3783
3784 guicolor = GUI_GET_COLOR(color_name);
3785 if (guicolor == INVALCOLOR)
3786 return FAIL;
3787
3788 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3789 }
3790
3791 if (n != 16 || li != NULL)
3792 return FAIL;
3793
3794 set_vterm_palette(vterm, rgb);
3795
3796 return OK;
3797}
3798
3799/*
3800 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3801 */
3802 static void
3803init_vterm_ansi_colors(VTerm *vterm)
3804{
3805 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3806
3807 if (var != NULL
3808 && (var->di_tv.v_type != VAR_LIST
3809 || var->di_tv.vval.v_list == NULL
Bram Moolenaarb0992022020-01-30 14:55:42 +01003810 || var->di_tv.vval.v_list->lv_first == &range_list_item
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003811 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003812 semsg(_(e_invarg2), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003813}
3814#endif
3815
Bram Moolenaar52acb112018-03-18 19:20:22 +01003816/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003817 * Handles a "drop" command from the job in the terminal.
3818 * "item" is the file name, "item->li_next" may have options.
3819 */
3820 static void
3821handle_drop_command(listitem_T *item)
3822{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003823 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003824 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003825 int bufnr;
3826 win_T *wp;
3827 tabpage_T *tp;
3828 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003829 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003830
3831 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3832 FOR_ALL_TAB_WINDOWS(tp, wp)
3833 {
3834 if (wp->w_buffer->b_fnum == bufnr)
3835 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003836 // buffer is in a window already, go there
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003837 goto_tabpage_win(tp, wp);
3838 return;
3839 }
3840 }
3841
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003842 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003843
3844 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3845 && opt_item->li_tv.vval.v_dict != NULL)
3846 {
3847 dict_T *dict = opt_item->li_tv.vval.v_dict;
3848 char_u *p;
3849
Bram Moolenaar8f667172018-12-14 15:38:31 +01003850 p = dict_get_string(dict, (char_u *)"ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003851 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003852 p = dict_get_string(dict, (char_u *)"fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003853 if (p != NULL)
3854 {
3855 if (check_ff_value(p) == FAIL)
3856 ch_log(NULL, "Invalid ff argument to drop: %s", p);
3857 else
3858 ea.force_ff = *p;
3859 }
Bram Moolenaar8f667172018-12-14 15:38:31 +01003860 p = dict_get_string(dict, (char_u *)"enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003861 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01003862 p = dict_get_string(dict, (char_u *)"encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003863 if (p != NULL)
3864 {
Bram Moolenaar51e14382019-05-25 20:21:28 +02003865 ea.cmd = alloc(STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003866 if (ea.cmd != NULL)
3867 {
3868 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
3869 ea.force_enc = 11;
3870 tofree = ea.cmd;
3871 }
3872 }
3873
Bram Moolenaar8f667172018-12-14 15:38:31 +01003874 p = dict_get_string(dict, (char_u *)"bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003875 if (p != NULL)
3876 get_bad_opt(p, &ea);
3877
3878 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
3879 ea.force_bin = FORCE_BIN;
3880 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
3881 ea.force_bin = FORCE_BIN;
3882 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
3883 ea.force_bin = FORCE_NOBIN;
3884 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
3885 ea.force_bin = FORCE_NOBIN;
3886 }
3887
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003888 // open in new window, like ":split fname"
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003889 if (ea.cmd == NULL)
3890 ea.cmd = (char_u *)"split";
3891 ea.arg = fname;
3892 ea.cmdidx = CMD_split;
3893 ex_splitview(&ea);
3894
3895 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003896}
3897
3898/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003899 * Return TRUE if "func" starts with "pat" and "pat" isn't empty.
3900 */
3901 static int
3902is_permitted_term_api(char_u *func, char_u *pat)
3903{
3904 return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
3905}
3906
3907/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003908 * Handles a function call from the job running in a terminal.
3909 * "item" is the function name, "item->li_next" has the arguments.
3910 */
3911 static void
3912handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
3913{
3914 char_u *func;
3915 typval_T argvars[2];
3916 typval_T rettv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02003917 funcexe_T funcexe;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003918
3919 if (item->li_next == NULL)
3920 {
3921 ch_log(channel, "Missing function arguments for call");
3922 return;
3923 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003924 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003925
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003926 if (!is_permitted_term_api(func, term->tl_api))
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003927 {
Bram Moolenaard2842ea2019-09-26 23:08:54 +02003928 ch_log(channel, "Unpermitted function: %s", func);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003929 return;
3930 }
3931
3932 argvars[0].v_type = VAR_NUMBER;
3933 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
3934 argvars[1] = item->li_next->li_tv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02003935 vim_memset(&funcexe, 0, sizeof(funcexe));
3936 funcexe.firstline = 1L;
3937 funcexe.lastline = 1L;
3938 funcexe.evaluate = TRUE;
3939 if (call_func(func, -1, &rettv, 2, argvars, &funcexe) == OK)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003940 {
3941 clear_tv(&rettv);
3942 ch_log(channel, "Function %s called", func);
3943 }
3944 else
3945 ch_log(channel, "Calling function %s failed", func);
3946}
3947
3948/*
3949 * Called by libvterm when it cannot recognize an OSC sequence.
3950 * We recognize a terminal API command.
3951 */
3952 static int
3953parse_osc(const char *command, size_t cmdlen, void *user)
3954{
3955 term_T *term = (term_T *)user;
3956 js_read_T reader;
3957 typval_T tv;
3958 channel_T *channel = term->tl_job == NULL ? NULL
3959 : term->tl_job->jv_channel;
3960
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003961 // We recognize only OSC 5 1 ; {command}
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003962 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003963 return 0; // not handled
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003964
Bram Moolenaar878c96d2018-04-04 23:00:06 +02003965 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003966 if (reader.js_buf == NULL)
3967 return 1;
3968 reader.js_fill = NULL;
3969 reader.js_used = 0;
3970 if (json_decode(&reader, &tv, 0) == OK
3971 && tv.v_type == VAR_LIST
3972 && tv.vval.v_list != NULL)
3973 {
3974 listitem_T *item = tv.vval.v_list->lv_first;
3975
3976 if (item == NULL)
3977 ch_log(channel, "Missing command");
3978 else
3979 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003980 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003981
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003982 // Make sure an invoked command doesn't delete the buffer (and the
3983 // terminal) under our fingers.
Bram Moolenaara997b452018-04-17 23:24:06 +02003984 ++term->tl_buffer->b_locked;
3985
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003986 item = item->li_next;
3987 if (item == NULL)
3988 ch_log(channel, "Missing argument for %s", cmd);
3989 else if (STRCMP(cmd, "drop") == 0)
3990 handle_drop_command(item);
3991 else if (STRCMP(cmd, "call") == 0)
3992 handle_call_command(term, channel, item);
3993 else
3994 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02003995 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003996 }
3997 }
3998 else
3999 ch_log(channel, "Invalid JSON received");
4000
4001 vim_free(reader.js_buf);
4002 clear_tv(&tv);
4003 return 1;
4004}
4005
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004006/*
4007 * Called by libvterm when it cannot recognize a CSI sequence.
4008 * We recognize the window position report.
4009 */
4010 static int
4011parse_csi(
4012 const char *leader UNUSED,
4013 const long args[],
4014 int argcount,
4015 const char *intermed UNUSED,
4016 char command,
4017 void *user)
4018{
4019 term_T *term = (term_T *)user;
4020 char buf[100];
4021 int len;
4022 int x = 0;
4023 int y = 0;
4024 win_T *wp;
4025
4026 // We recognize only CSI 13 t
4027 if (command != 't' || argcount != 1 || args[0] != 13)
4028 return 0; // not handled
4029
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004030 // When getting the window position is not possible or it fails it results
4031 // in zero/zero.
Bram Moolenaar16c34c32019-04-06 22:01:24 +02004032#if defined(FEAT_GUI) \
4033 || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
4034 || defined(MSWIN)
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004035 (void)ui_get_winpos(&x, &y, (varnumber_T)100);
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004036#endif
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004037
4038 FOR_ALL_WINDOWS(wp)
4039 if (wp->w_buffer == term->tl_buffer)
4040 break;
4041 if (wp != NULL)
4042 {
4043#ifdef FEAT_GUI
4044 if (gui.in_use)
4045 {
4046 x += wp->w_wincol * gui.char_width;
4047 y += W_WINROW(wp) * gui.char_height;
4048 }
4049 else
4050#endif
4051 {
4052 // We roughly estimate the position of the terminal window inside
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004053 // the Vim window by assuming a 10 x 7 character cell.
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004054 x += wp->w_wincol * 7;
4055 y += W_WINROW(wp) * 10;
4056 }
4057 }
4058
4059 len = vim_snprintf(buf, 100, "\x1b[3;%d;%dt", x, y);
4060 channel_send(term->tl_job->jv_channel, get_tty_part(term),
4061 (char_u *)buf, len, NULL);
4062 return 1;
4063}
4064
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004065static VTermParserCallbacks parser_fallbacks = {
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004066 NULL, // text
4067 NULL, // control
4068 NULL, // escape
4069 parse_csi, // csi
4070 parse_osc, // osc
4071 NULL, // dcs
4072 NULL // resize
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004073};
4074
4075/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02004076 * Use Vim's allocation functions for vterm so profiling works.
4077 */
4078 static void *
4079vterm_malloc(size_t size, void *data UNUSED)
4080{
Bram Moolenaar18a4ba22019-05-24 19:39:03 +02004081 return alloc_clear(size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02004082}
4083
4084 static void
4085vterm_memfree(void *ptr, void *data UNUSED)
4086{
4087 vim_free(ptr);
4088}
4089
4090static VTermAllocatorFunctions vterm_allocator = {
4091 &vterm_malloc,
4092 &vterm_memfree
4093};
4094
4095/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004096 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004097 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004098 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004099 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01004100create_vterm(term_T *term, int rows, int cols)
4101{
4102 VTerm *vterm;
4103 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004104 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01004105 VTermValue value;
4106
Bram Moolenaar756ef112018-04-10 12:04:27 +02004107 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004108 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004109 if (vterm == NULL)
4110 return FAIL;
4111
4112 // Allocate screen and state here, so we can bail out if that fails.
4113 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004114 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004115 if (state == NULL || screen == NULL)
4116 {
4117 vterm_free(vterm);
4118 return FAIL;
4119 }
4120
Bram Moolenaar52acb112018-03-18 19:20:22 +01004121 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004122 // TODO: depends on 'encoding'.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004123 vterm_set_utf8(vterm, 1);
4124
4125 init_default_colors(term);
4126
4127 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004128 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01004129 &term->tl_default_color.fg,
4130 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004131
Bram Moolenaar9e587872019-05-13 20:27:23 +02004132 if (t_colors < 16)
4133 // Less than 16 colors: assume that bold means using a bright color for
4134 // the foreground color.
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004135 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
4136
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004137 // Required to initialize most things.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004138 vterm_screen_reset(screen, 1 /* hard */);
4139
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004140 // Allow using alternate screen.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004141 vterm_screen_enable_altscreen(screen, 1);
4142
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004143 // For unix do not use a blinking cursor. In an xterm this causes the
4144 // cursor to blink if it's blinking in the xterm.
4145 // For Windows we respect the system wide setting.
Bram Moolenaar4f974752019-02-17 17:44:42 +01004146#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004147 if (GetCaretBlinkTime() == INFINITE)
4148 value.boolean = 0;
4149 else
4150 value.boolean = 1;
4151#else
4152 value.boolean = 0;
4153#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004154 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
4155 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004156
4157 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004158}
4159
4160/*
4161 * Return the text to show for the buffer name and status.
4162 */
4163 char_u *
4164term_get_status_text(term_T *term)
4165{
4166 if (term->tl_status_text == NULL)
4167 {
4168 char_u *txt;
4169 size_t len;
4170
4171 if (term->tl_normal_mode)
4172 {
4173 if (term_job_running(term))
4174 txt = (char_u *)_("Terminal");
4175 else
4176 txt = (char_u *)_("Terminal-finished");
4177 }
4178 else if (term->tl_title != NULL)
4179 txt = term->tl_title;
4180 else if (term_none_open(term))
4181 txt = (char_u *)_("active");
4182 else if (term_job_running(term))
4183 txt = (char_u *)_("running");
4184 else
4185 txt = (char_u *)_("finished");
4186 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
Bram Moolenaar51e14382019-05-25 20:21:28 +02004187 term->tl_status_text = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004188 if (term->tl_status_text != NULL)
4189 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
4190 term->tl_buffer->b_fname, txt);
4191 }
4192 return term->tl_status_text;
4193}
4194
4195/*
4196 * Mark references in jobs of terminals.
4197 */
4198 int
4199set_ref_in_term(int copyID)
4200{
4201 int abort = FALSE;
4202 term_T *term;
4203 typval_T tv;
4204
Bram Moolenaar75a1a942019-06-20 03:45:36 +02004205 for (term = first_term; !abort && term != NULL; term = term->tl_next)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004206 if (term->tl_job != NULL)
4207 {
4208 tv.v_type = VAR_JOB;
4209 tv.vval.v_job = term->tl_job;
4210 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4211 }
4212 return abort;
4213}
4214
4215/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01004216 * Cache "Terminal" highlight group colors.
4217 */
4218 void
4219set_terminal_default_colors(int cterm_fg, int cterm_bg)
4220{
4221 term_default_cterm_fg = cterm_fg - 1;
4222 term_default_cterm_bg = cterm_bg - 1;
4223}
4224
4225/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004226 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004227 * Returns NULL when the buffer is not for a terminal window and logs a message
4228 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004229 */
4230 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004231term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004232{
4233 buf_T *buf;
4234
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004235 (void)tv_get_number(&argvars[0]); // issue errmsg if type error
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004236 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01004237 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004238 --emsg_off;
4239 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004240 {
4241 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004242 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004243 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004244 return buf;
4245}
4246
Bram Moolenaard96ff162018-02-18 22:13:29 +01004247 static int
4248same_color(VTermColor *a, VTermColor *b)
4249{
4250 return a->red == b->red
4251 && a->green == b->green
4252 && a->blue == b->blue
4253 && a->ansi_index == b->ansi_index;
4254}
4255
4256 static void
4257dump_term_color(FILE *fd, VTermColor *color)
4258{
4259 fprintf(fd, "%02x%02x%02x%d",
4260 (int)color->red, (int)color->green, (int)color->blue,
4261 (int)color->ansi_index);
4262}
4263
4264/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004265 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01004266 *
4267 * Each screen cell in full is:
4268 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
4269 * {characters} is a space for an empty cell
4270 * For a double-width character "+" is changed to "*" and the next cell is
4271 * skipped.
4272 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
4273 * when "&" use the same as the previous cell.
4274 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
4275 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
4276 * {color-idx} is a number from 0 to 255
4277 *
4278 * Screen cell with same width, attributes and color as the previous one:
4279 * |{characters}
4280 *
4281 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
4282 *
4283 * Repeating the previous screen cell:
4284 * @{count}
4285 */
4286 void
4287f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
4288{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004289 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01004290 term_T *term;
4291 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004292 int max_height = 0;
4293 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004294 stat_T st;
4295 FILE *fd;
4296 VTermPos pos;
4297 VTermScreen *screen;
4298 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004299 VTermState *state;
4300 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004301
4302 if (check_restricted() || check_secure())
4303 return;
4304 if (buf == NULL)
4305 return;
4306 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004307 if (term->tl_vterm == NULL)
4308 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004309 emsg(_("E958: Job already finished"));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004310 return;
4311 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004312
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004313 if (argvars[2].v_type != VAR_UNKNOWN)
4314 {
4315 dict_T *d;
4316
4317 if (argvars[2].v_type != VAR_DICT)
4318 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004319 emsg(_(e_dictreq));
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004320 return;
4321 }
4322 d = argvars[2].vval.v_dict;
4323 if (d != NULL)
4324 {
Bram Moolenaar8f667172018-12-14 15:38:31 +01004325 max_height = dict_get_number(d, (char_u *)"rows");
4326 max_width = dict_get_number(d, (char_u *)"columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004327 }
4328 }
4329
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004330 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004331 if (fname == NULL)
4332 return;
4333 if (mch_stat((char *)fname, &st) >= 0)
4334 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004335 semsg(_("E953: File exists: %s"), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004336 return;
4337 }
4338
Bram Moolenaard96ff162018-02-18 22:13:29 +01004339 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
4340 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004341 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004342 return;
4343 }
4344
4345 vim_memset(&prev_cell, 0, sizeof(prev_cell));
4346
4347 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004348 state = vterm_obtain_state(term->tl_vterm);
4349 vterm_state_get_cursorpos(state, &cursor_pos);
4350
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004351 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
4352 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004353 {
4354 int repeat = 0;
4355
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004356 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
4357 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004358 {
4359 VTermScreenCell cell;
4360 int same_attr;
4361 int same_chars = TRUE;
4362 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004363 int is_cursor_pos = (pos.col == cursor_pos.col
4364 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004365
4366 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4367 vim_memset(&cell, 0, sizeof(cell));
4368
4369 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4370 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01004371 int c = cell.chars[i];
4372 int pc = prev_cell.chars[i];
4373
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004374 // For the first character NUL is the same as space.
Bram Moolenaar47015b82018-03-23 22:10:34 +01004375 if (i == 0)
4376 {
4377 c = (c == NUL) ? ' ' : c;
4378 pc = (pc == NUL) ? ' ' : pc;
4379 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004380 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004381 same_chars = FALSE;
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004382 if (c == NUL || pc == NUL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004383 break;
4384 }
4385 same_attr = vtermAttr2hl(cell.attrs)
4386 == vtermAttr2hl(prev_cell.attrs)
4387 && same_color(&cell.fg, &prev_cell.fg)
4388 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004389 if (same_chars && cell.width == prev_cell.width && same_attr
4390 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004391 {
4392 ++repeat;
4393 }
4394 else
4395 {
4396 if (repeat > 0)
4397 {
4398 fprintf(fd, "@%d", repeat);
4399 repeat = 0;
4400 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004401 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004402
4403 if (cell.chars[0] == NUL)
4404 fputs(" ", fd);
4405 else
4406 {
4407 char_u charbuf[10];
4408 int len;
4409
4410 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
4411 && cell.chars[i] != NUL; ++i)
4412 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02004413 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004414 fwrite(charbuf, len, 1, fd);
4415 }
4416 }
4417
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004418 // When only the characters differ we don't write anything, the
4419 // following "|", "@" or NL will indicate using the same
4420 // attributes.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004421 if (cell.width != prev_cell.width || !same_attr)
4422 {
4423 if (cell.width == 2)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004424 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004425 else
4426 fputs("+", fd);
4427
4428 if (same_attr)
4429 {
4430 fputs("&", fd);
4431 }
4432 else
4433 {
4434 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
4435 if (same_color(&cell.fg, &prev_cell.fg))
4436 fputs("&", fd);
4437 else
4438 {
4439 fputs("#", fd);
4440 dump_term_color(fd, &cell.fg);
4441 }
4442 if (same_color(&cell.bg, &prev_cell.bg))
4443 fputs("&", fd);
4444 else
4445 {
4446 fputs("#", fd);
4447 dump_term_color(fd, &cell.bg);
4448 }
4449 }
4450 }
4451
4452 prev_cell = cell;
4453 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004454
4455 if (cell.width == 2)
4456 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004457 }
4458 if (repeat > 0)
4459 fprintf(fd, "@%d", repeat);
4460 fputs("\n", fd);
4461 }
4462
4463 fclose(fd);
4464}
4465
4466/*
4467 * Called when a dump is corrupted. Put a breakpoint here when debugging.
4468 */
4469 static void
4470dump_is_corrupt(garray_T *gap)
4471{
4472 ga_concat(gap, (char_u *)"CORRUPT");
4473}
4474
4475 static void
4476append_cell(garray_T *gap, cellattr_T *cell)
4477{
4478 if (ga_grow(gap, 1) == OK)
4479 {
4480 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4481 ++gap->ga_len;
4482 }
4483}
4484
4485/*
4486 * Read the dump file from "fd" and append lines to the current buffer.
4487 * Return the cell width of the longest line.
4488 */
4489 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01004490read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004491{
4492 int c;
4493 garray_T ga_text;
4494 garray_T ga_cell;
4495 char_u *prev_char = NULL;
4496 int attr = 0;
4497 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004498 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004499 term_T *term = curbuf->b_term;
4500 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004501 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004502
4503 ga_init2(&ga_text, 1, 90);
4504 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4505 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004506 vim_memset(&empty_cell, 0, sizeof(empty_cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01004507 cursor_pos->row = -1;
4508 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004509
4510 c = fgetc(fd);
4511 for (;;)
4512 {
4513 if (c == EOF)
4514 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02004515 if (c == '\r')
4516 {
4517 // DOS line endings? Ignore.
4518 c = fgetc(fd);
4519 }
4520 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004521 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004522 // End of a line: append it to the buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004523 if (ga_text.ga_data == NULL)
4524 dump_is_corrupt(&ga_text);
4525 if (ga_grow(&term->tl_scrollback, 1) == OK)
4526 {
4527 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
4528 + term->tl_scrollback.ga_len;
4529
4530 if (max_cells < ga_cell.ga_len)
4531 max_cells = ga_cell.ga_len;
4532 line->sb_cols = ga_cell.ga_len;
4533 line->sb_cells = ga_cell.ga_data;
4534 line->sb_fill_attr = term->tl_default_color;
4535 ++term->tl_scrollback.ga_len;
4536 ga_init(&ga_cell);
4537
4538 ga_append(&ga_text, NUL);
4539 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4540 ga_text.ga_len, FALSE);
4541 }
4542 else
4543 ga_clear(&ga_cell);
4544 ga_text.ga_len = 0;
4545
4546 c = fgetc(fd);
4547 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004548 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004549 {
4550 int prev_len = ga_text.ga_len;
4551
Bram Moolenaar9271d052018-02-25 21:39:46 +01004552 if (c == '>')
4553 {
4554 if (cursor_pos->row != -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004555 dump_is_corrupt(&ga_text); // duplicate cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01004556 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4557 cursor_pos->col = ga_cell.ga_len;
4558 }
4559
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004560 // normal character(s) followed by "+", "*", "|", "@" or NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01004561 c = fgetc(fd);
4562 if (c != EOF)
4563 ga_append(&ga_text, c);
4564 for (;;)
4565 {
4566 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004567 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004568 || c == EOF || c == '\n')
4569 break;
4570 ga_append(&ga_text, c);
4571 }
4572
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004573 // save the character for repeating it
Bram Moolenaard96ff162018-02-18 22:13:29 +01004574 vim_free(prev_char);
4575 if (ga_text.ga_data != NULL)
4576 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4577 ga_text.ga_len - prev_len);
4578
Bram Moolenaar9271d052018-02-25 21:39:46 +01004579 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004580 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004581 // use all attributes from previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01004582 }
4583 else if (c == '+' || c == '*')
4584 {
4585 int is_bg;
4586
4587 cell.width = c == '+' ? 1 : 2;
4588
4589 c = fgetc(fd);
4590 if (c == '&')
4591 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004592 // use same attr as previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01004593 c = fgetc(fd);
4594 }
4595 else if (isdigit(c))
4596 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004597 // get the decimal attribute
Bram Moolenaard96ff162018-02-18 22:13:29 +01004598 attr = 0;
4599 while (isdigit(c))
4600 {
4601 attr = attr * 10 + (c - '0');
4602 c = fgetc(fd);
4603 }
4604 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004605
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004606 // is_bg == 0: fg, is_bg == 1: bg
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004607 for (is_bg = 0; is_bg <= 1; ++is_bg)
4608 {
4609 if (c == '&')
4610 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004611 // use same color as previous cell
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004612 c = fgetc(fd);
4613 }
4614 else if (c == '#')
4615 {
4616 int red, green, blue, index = 0;
4617
4618 c = fgetc(fd);
4619 red = hex2nr(c);
4620 c = fgetc(fd);
4621 red = (red << 4) + hex2nr(c);
4622 c = fgetc(fd);
4623 green = hex2nr(c);
4624 c = fgetc(fd);
4625 green = (green << 4) + hex2nr(c);
4626 c = fgetc(fd);
4627 blue = hex2nr(c);
4628 c = fgetc(fd);
4629 blue = (blue << 4) + hex2nr(c);
4630 c = fgetc(fd);
4631 if (!isdigit(c))
4632 dump_is_corrupt(&ga_text);
4633 while (isdigit(c))
4634 {
4635 index = index * 10 + (c - '0');
4636 c = fgetc(fd);
4637 }
4638
4639 if (is_bg)
4640 {
4641 cell.bg.red = red;
4642 cell.bg.green = green;
4643 cell.bg.blue = blue;
4644 cell.bg.ansi_index = index;
4645 }
4646 else
4647 {
4648 cell.fg.red = red;
4649 cell.fg.green = green;
4650 cell.fg.blue = blue;
4651 cell.fg.ansi_index = index;
4652 }
4653 }
4654 else
4655 dump_is_corrupt(&ga_text);
4656 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004657 }
4658 else
4659 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004660 }
4661 else
4662 dump_is_corrupt(&ga_text);
4663
4664 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004665 if (cell.width == 2)
4666 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004667 }
4668 else if (c == '@')
4669 {
4670 if (prev_char == NULL)
4671 dump_is_corrupt(&ga_text);
4672 else
4673 {
4674 int count = 0;
4675
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004676 // repeat previous character, get the count
Bram Moolenaard96ff162018-02-18 22:13:29 +01004677 for (;;)
4678 {
4679 c = fgetc(fd);
4680 if (!isdigit(c))
4681 break;
4682 count = count * 10 + (c - '0');
4683 }
4684
4685 while (count-- > 0)
4686 {
4687 ga_concat(&ga_text, prev_char);
4688 append_cell(&ga_cell, &cell);
4689 }
4690 }
4691 }
4692 else
4693 {
4694 dump_is_corrupt(&ga_text);
4695 c = fgetc(fd);
4696 }
4697 }
4698
4699 if (ga_text.ga_len > 0)
4700 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004701 // trailing characters after last NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01004702 dump_is_corrupt(&ga_text);
4703 ga_append(&ga_text, NUL);
4704 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4705 ga_text.ga_len, FALSE);
4706 }
4707
4708 ga_clear(&ga_text);
Bram Moolenaar86173482019-10-01 17:02:16 +02004709 ga_clear(&ga_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004710 vim_free(prev_char);
4711
4712 return max_cells;
4713}
4714
4715/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004716 * Return an allocated string with at least "text_width" "=" characters and
4717 * "fname" inserted in the middle.
4718 */
4719 static char_u *
4720get_separator(int text_width, char_u *fname)
4721{
4722 int width = MAX(text_width, curwin->w_width);
4723 char_u *textline;
4724 int fname_size;
4725 char_u *p = fname;
4726 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004727 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004728
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004729 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004730 if (textline == NULL)
4731 return NULL;
4732
4733 fname_size = vim_strsize(fname);
4734 if (fname_size < width - 8)
4735 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004736 // enough room, don't use the full window width
Bram Moolenaar4a696342018-04-05 18:45:26 +02004737 width = MAX(text_width, fname_size + 8);
4738 }
4739 else if (fname_size > width - 8)
4740 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004741 // full name doesn't fit, use only the tail
Bram Moolenaar4a696342018-04-05 18:45:26 +02004742 p = gettail(fname);
4743 fname_size = vim_strsize(p);
4744 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004745 // skip characters until the name fits
Bram Moolenaar4a696342018-04-05 18:45:26 +02004746 while (fname_size > width - 8)
4747 {
4748 p += (*mb_ptr2len)(p);
4749 fname_size = vim_strsize(p);
4750 }
4751
4752 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4753 textline[i] = '=';
4754 textline[i++] = ' ';
4755
4756 STRCPY(textline + i, p);
4757 off = STRLEN(textline);
4758 textline[off] = ' ';
4759 for (i = 1; i < (width - fname_size) / 2; ++i)
4760 textline[off + i] = '=';
4761 textline[off + i] = NUL;
4762
4763 return textline;
4764}
4765
4766/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004767 * Common for "term_dumpdiff()" and "term_dumpload()".
4768 */
4769 static void
4770term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4771{
4772 jobopt_T opt;
Bram Moolenaar87abab92019-06-03 21:14:59 +02004773 buf_T *buf = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004774 char_u buf1[NUMBUFLEN];
4775 char_u buf2[NUMBUFLEN];
4776 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004777 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004778 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004779 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004780 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004781 char_u *textline = NULL;
4782
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004783 // First open the files. If this fails bail out.
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004784 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004785 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004786 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004787 if (fname1 == NULL || (do_diff && fname2 == NULL))
4788 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004789 emsg(_(e_invarg));
Bram Moolenaard96ff162018-02-18 22:13:29 +01004790 return;
4791 }
4792 fd1 = mch_fopen((char *)fname1, READBIN);
4793 if (fd1 == NULL)
4794 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004795 semsg(_(e_notread), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004796 return;
4797 }
4798 if (do_diff)
4799 {
4800 fd2 = mch_fopen((char *)fname2, READBIN);
4801 if (fd2 == NULL)
4802 {
4803 fclose(fd1);
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004804 semsg(_(e_notread), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004805 return;
4806 }
4807 }
4808
4809 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004810 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4811 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4812 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4813 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4814 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004815
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004816 if (opt.jo_term_name == NULL)
4817 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004818 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004819
Bram Moolenaar51e14382019-05-25 20:21:28 +02004820 fname_tofree = alloc(len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004821 if (fname_tofree != NULL)
4822 {
4823 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4824 opt.jo_term_name = fname_tofree;
4825 }
4826 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004827
Bram Moolenaar87abab92019-06-03 21:14:59 +02004828 if (opt.jo_bufnr_buf != NULL)
4829 {
4830 win_T *wp = buf_jump_open_win(opt.jo_bufnr_buf);
4831
4832 // With "bufnr" argument: enter the window with this buffer and make it
4833 // empty.
4834 if (wp == NULL)
4835 semsg(_(e_invarg2), "bufnr");
4836 else
4837 {
4838 buf = curbuf;
4839 while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
4840 ml_delete((linenr_T)1, FALSE);
Bram Moolenaar86173482019-10-01 17:02:16 +02004841 free_scrollback(curbuf->b_term);
Bram Moolenaar87abab92019-06-03 21:14:59 +02004842 redraw_later(NOT_VALID);
4843 }
4844 }
4845 else
4846 // Create a new terminal window.
4847 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
4848
Bram Moolenaard96ff162018-02-18 22:13:29 +01004849 if (buf != NULL && buf->b_term != NULL)
4850 {
4851 int i;
4852 linenr_T bot_lnum;
4853 linenr_T lnum;
4854 term_T *term = buf->b_term;
4855 int width;
4856 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004857 VTermPos cursor_pos1;
4858 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004859
Bram Moolenaar52acb112018-03-18 19:20:22 +01004860 init_default_colors(term);
4861
Bram Moolenaard96ff162018-02-18 22:13:29 +01004862 rettv->vval.v_number = buf->b_fnum;
4863
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004864 // read the files, fill the buffer with the diff
Bram Moolenaar9271d052018-02-25 21:39:46 +01004865 width = read_dump_file(fd1, &cursor_pos1);
4866
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004867 // position the cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01004868 if (cursor_pos1.row >= 0)
4869 {
4870 curwin->w_cursor.lnum = cursor_pos1.row + 1;
4871 coladvance(cursor_pos1.col);
4872 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004873
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004874 // Delete the empty line that was in the empty buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004875 ml_delete(1, FALSE);
4876
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004877 // For term_dumpload() we are done here.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004878 if (!do_diff)
4879 goto theend;
4880
4881 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
4882
Bram Moolenaar4a696342018-04-05 18:45:26 +02004883 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004884 if (textline == NULL)
4885 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004886 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4887 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
4888 vim_free(textline);
4889
4890 textline = get_separator(width, fname2);
4891 if (textline == NULL)
4892 goto theend;
4893 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
4894 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004895 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004896
4897 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004898 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004899 if (width2 > width)
4900 {
4901 vim_free(textline);
4902 textline = alloc(width2 + 1);
4903 if (textline == NULL)
4904 goto theend;
4905 width = width2;
4906 textline[width] = NUL;
4907 }
4908 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
4909
4910 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
4911 {
4912 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
4913 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004914 // bottom part has fewer rows, fill with "-"
Bram Moolenaard96ff162018-02-18 22:13:29 +01004915 for (i = 0; i < width; ++i)
4916 textline[i] = '-';
4917 }
4918 else
4919 {
4920 char_u *line1;
4921 char_u *line2;
4922 char_u *p1;
4923 char_u *p2;
4924 int col;
4925 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
4926 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
4927 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
4928 ->sb_cells;
4929
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004930 // Make a copy, getting the second line will invalidate it.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004931 line1 = vim_strsave(ml_get(lnum));
4932 if (line1 == NULL)
4933 break;
4934 p1 = line1;
4935
4936 line2 = ml_get(lnum + bot_lnum);
4937 p2 = line2;
4938 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
4939 {
4940 int len1 = utfc_ptr2len(p1);
4941 int len2 = utfc_ptr2len(p2);
4942
4943 textline[col] = ' ';
4944 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004945 // text differs
Bram Moolenaard96ff162018-02-18 22:13:29 +01004946 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01004947 else if (lnum == cursor_pos1.row + 1
4948 && col == cursor_pos1.col
4949 && (cursor_pos1.row != cursor_pos2.row
4950 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004951 // cursor in first but not in second
Bram Moolenaar9271d052018-02-25 21:39:46 +01004952 textline[col] = '>';
4953 else if (lnum == cursor_pos2.row + 1
4954 && col == cursor_pos2.col
4955 && (cursor_pos1.row != cursor_pos2.row
4956 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004957 // cursor in second but not in first
Bram Moolenaar9271d052018-02-25 21:39:46 +01004958 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01004959 else if (cellattr1 != NULL && cellattr2 != NULL)
4960 {
4961 if ((cellattr1 + col)->width
4962 != (cellattr2 + col)->width)
4963 textline[col] = 'w';
4964 else if (!same_color(&(cellattr1 + col)->fg,
4965 &(cellattr2 + col)->fg))
4966 textline[col] = 'f';
4967 else if (!same_color(&(cellattr1 + col)->bg,
4968 &(cellattr2 + col)->bg))
4969 textline[col] = 'b';
4970 else if (vtermAttr2hl((cellattr1 + col)->attrs)
4971 != vtermAttr2hl(((cellattr2 + col)->attrs)))
4972 textline[col] = 'a';
4973 }
4974 p1 += len1;
4975 p2 += len2;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004976 // TODO: handle different width
Bram Moolenaard96ff162018-02-18 22:13:29 +01004977 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004978
4979 while (col < width)
4980 {
4981 if (*p1 == NUL && *p2 == NUL)
4982 textline[col] = '?';
4983 else if (*p1 == NUL)
4984 {
4985 textline[col] = '+';
4986 p2 += utfc_ptr2len(p2);
4987 }
4988 else
4989 {
4990 textline[col] = '-';
4991 p1 += utfc_ptr2len(p1);
4992 }
4993 ++col;
4994 }
Bram Moolenaar81aa0f52019-02-14 23:23:19 +01004995
4996 vim_free(line1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004997 }
4998 if (add_empty_scrollback(term, &term->tl_default_color,
4999 term->tl_top_diff_rows) == OK)
5000 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5001 ++bot_lnum;
5002 }
5003
5004 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
5005 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005006 // bottom part has more rows, fill with "+"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005007 for (i = 0; i < width; ++i)
5008 textline[i] = '+';
5009 if (add_empty_scrollback(term, &term->tl_default_color,
5010 term->tl_top_diff_rows) == OK)
5011 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5012 ++lnum;
5013 ++bot_lnum;
5014 }
5015
5016 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005017
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005018 // looks better without wrapping
Bram Moolenaar4a696342018-04-05 18:45:26 +02005019 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005020 }
5021
5022theend:
5023 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005024 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005025 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005026 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005027 fclose(fd2);
5028}
5029
5030/*
5031 * If the current buffer shows the output of term_dumpdiff(), swap the top and
5032 * bottom files.
5033 * Return FAIL when this is not possible.
5034 */
5035 int
5036term_swap_diff()
5037{
5038 term_T *term = curbuf->b_term;
5039 linenr_T line_count;
5040 linenr_T top_rows;
5041 linenr_T bot_rows;
5042 linenr_T bot_start;
5043 linenr_T lnum;
5044 char_u *p;
5045 sb_line_T *sb_line;
5046
5047 if (term == NULL
5048 || !term_is_finished(curbuf)
5049 || term->tl_top_diff_rows == 0
5050 || term->tl_scrollback.ga_len == 0)
5051 return FAIL;
5052
5053 line_count = curbuf->b_ml.ml_line_count;
5054 top_rows = term->tl_top_diff_rows;
5055 bot_rows = term->tl_bot_diff_rows;
5056 bot_start = line_count - bot_rows;
5057 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5058
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005059 // move lines from top to above the bottom part
Bram Moolenaard96ff162018-02-18 22:13:29 +01005060 for (lnum = 1; lnum <= top_rows; ++lnum)
5061 {
5062 p = vim_strsave(ml_get(1));
5063 if (p == NULL)
5064 return OK;
5065 ml_append(bot_start, p, 0, FALSE);
5066 ml_delete(1, FALSE);
5067 vim_free(p);
5068 }
5069
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005070 // move lines from bottom to the top
Bram Moolenaard96ff162018-02-18 22:13:29 +01005071 for (lnum = 1; lnum <= bot_rows; ++lnum)
5072 {
5073 p = vim_strsave(ml_get(bot_start + lnum));
5074 if (p == NULL)
5075 return OK;
5076 ml_delete(bot_start + lnum, FALSE);
5077 ml_append(lnum - 1, p, 0, FALSE);
5078 vim_free(p);
5079 }
5080
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005081 // move top title to bottom
5082 p = vim_strsave(ml_get(bot_rows + 1));
5083 if (p == NULL)
5084 return OK;
5085 ml_append(line_count - top_rows - 1, p, 0, FALSE);
5086 ml_delete(bot_rows + 1, FALSE);
5087 vim_free(p);
5088
5089 // move bottom title to top
5090 p = vim_strsave(ml_get(line_count - top_rows));
5091 if (p == NULL)
5092 return OK;
5093 ml_delete(line_count - top_rows, FALSE);
5094 ml_append(bot_rows, p, 0, FALSE);
5095 vim_free(p);
5096
Bram Moolenaard96ff162018-02-18 22:13:29 +01005097 if (top_rows == bot_rows)
5098 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005099 // rows counts are equal, can swap cell properties
Bram Moolenaard96ff162018-02-18 22:13:29 +01005100 for (lnum = 0; lnum < top_rows; ++lnum)
5101 {
5102 sb_line_T temp;
5103
5104 temp = *(sb_line + lnum);
5105 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
5106 *(sb_line + bot_start + lnum) = temp;
5107 }
5108 }
5109 else
5110 {
5111 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005112 sb_line_T *temp = alloc(size);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005113
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005114 // need to copy cell properties into temp memory
Bram Moolenaard96ff162018-02-18 22:13:29 +01005115 if (temp != NULL)
5116 {
5117 mch_memmove(temp, term->tl_scrollback.ga_data, size);
5118 mch_memmove(term->tl_scrollback.ga_data,
5119 temp + bot_start,
5120 sizeof(sb_line_T) * bot_rows);
5121 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
5122 temp + top_rows,
5123 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
5124 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
5125 + line_count - top_rows,
5126 temp,
5127 sizeof(sb_line_T) * top_rows);
5128 vim_free(temp);
5129 }
5130 }
5131
5132 term->tl_top_diff_rows = bot_rows;
5133 term->tl_bot_diff_rows = top_rows;
5134
5135 update_screen(NOT_VALID);
5136 return OK;
5137}
5138
5139/*
5140 * "term_dumpdiff(filename, filename, options)" function
5141 */
5142 void
5143f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
5144{
5145 term_load_dump(argvars, rettv, TRUE);
5146}
5147
5148/*
5149 * "term_dumpload(filename, options)" function
5150 */
5151 void
5152f_term_dumpload(typval_T *argvars, typval_T *rettv)
5153{
5154 term_load_dump(argvars, rettv, FALSE);
5155}
5156
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005157/*
5158 * "term_getaltscreen(buf)" function
5159 */
5160 void
5161f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
5162{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005163 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005164
5165 if (buf == NULL)
5166 return;
5167 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
5168}
5169
5170/*
5171 * "term_getattr(attr, name)" function
5172 */
5173 void
5174f_term_getattr(typval_T *argvars, typval_T *rettv)
5175{
5176 int attr;
5177 size_t i;
5178 char_u *name;
5179
5180 static struct {
5181 char *name;
5182 int attr;
5183 } attrs[] = {
5184 {"bold", HL_BOLD},
5185 {"italic", HL_ITALIC},
5186 {"underline", HL_UNDERLINE},
5187 {"strike", HL_STRIKETHROUGH},
5188 {"reverse", HL_INVERSE},
5189 };
5190
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005191 attr = tv_get_number(&argvars[0]);
5192 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005193 if (name == NULL)
5194 return;
5195
Bram Moolenaar7ee80f72019-09-08 20:55:06 +02005196 if (attr > HL_ALL)
5197 attr = syn_attr2attr(attr);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005198 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
5199 if (STRCMP(name, attrs[i].name) == 0)
5200 {
5201 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
5202 break;
5203 }
5204}
5205
5206/*
5207 * "term_getcursor(buf)" function
5208 */
5209 void
5210f_term_getcursor(typval_T *argvars, typval_T *rettv)
5211{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005212 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005213 term_T *term;
5214 list_T *l;
5215 dict_T *d;
5216
5217 if (rettv_list_alloc(rettv) == FAIL)
5218 return;
5219 if (buf == NULL)
5220 return;
5221 term = buf->b_term;
5222
5223 l = rettv->vval.v_list;
5224 list_append_number(l, term->tl_cursor_pos.row + 1);
5225 list_append_number(l, term->tl_cursor_pos.col + 1);
5226
5227 d = dict_alloc();
5228 if (d != NULL)
5229 {
Bram Moolenaare0be1672018-07-08 16:50:37 +02005230 dict_add_number(d, "visible", term->tl_cursor_visible);
5231 dict_add_number(d, "blink", blink_state_is_inverted()
5232 ? !term->tl_cursor_blink : term->tl_cursor_blink);
5233 dict_add_number(d, "shape", term->tl_cursor_shape);
5234 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005235 list_append_dict(l, d);
5236 }
5237}
5238
5239/*
5240 * "term_getjob(buf)" function
5241 */
5242 void
5243f_term_getjob(typval_T *argvars, typval_T *rettv)
5244{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005245 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005246
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005247 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005248 {
5249 rettv->v_type = VAR_SPECIAL;
5250 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005251 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005252 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005253
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005254 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005255 rettv->vval.v_job = buf->b_term->tl_job;
5256 if (rettv->vval.v_job != NULL)
5257 ++rettv->vval.v_job->jv_refcount;
5258}
5259
5260 static int
5261get_row_number(typval_T *tv, term_T *term)
5262{
5263 if (tv->v_type == VAR_STRING
5264 && tv->vval.v_string != NULL
5265 && STRCMP(tv->vval.v_string, ".") == 0)
5266 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005267 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005268}
5269
5270/*
5271 * "term_getline(buf, row)" function
5272 */
5273 void
5274f_term_getline(typval_T *argvars, typval_T *rettv)
5275{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005276 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005277 term_T *term;
5278 int row;
5279
5280 rettv->v_type = VAR_STRING;
5281 if (buf == NULL)
5282 return;
5283 term = buf->b_term;
5284 row = get_row_number(&argvars[1], term);
5285
5286 if (term->tl_vterm == NULL)
5287 {
5288 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
5289
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005290 // vterm is finished, get the text from the buffer
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005291 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
5292 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
5293 }
5294 else
5295 {
5296 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
5297 VTermRect rect;
5298 int len;
5299 char_u *p;
5300
5301 if (row < 0 || row >= term->tl_rows)
5302 return;
5303 len = term->tl_cols * MB_MAXBYTES + 1;
5304 p = alloc(len);
5305 if (p == NULL)
5306 return;
5307 rettv->vval.v_string = p;
5308
5309 rect.start_col = 0;
5310 rect.end_col = term->tl_cols;
5311 rect.start_row = row;
5312 rect.end_row = row + 1;
5313 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
5314 }
5315}
5316
5317/*
5318 * "term_getscrolled(buf)" function
5319 */
5320 void
5321f_term_getscrolled(typval_T *argvars, typval_T *rettv)
5322{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005323 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005324
5325 if (buf == NULL)
5326 return;
5327 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
5328}
5329
5330/*
5331 * "term_getsize(buf)" function
5332 */
5333 void
5334f_term_getsize(typval_T *argvars, typval_T *rettv)
5335{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005336 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005337 list_T *l;
5338
5339 if (rettv_list_alloc(rettv) == FAIL)
5340 return;
5341 if (buf == NULL)
5342 return;
5343
5344 l = rettv->vval.v_list;
5345 list_append_number(l, buf->b_term->tl_rows);
5346 list_append_number(l, buf->b_term->tl_cols);
5347}
5348
5349/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005350 * "term_setsize(buf, rows, cols)" function
5351 */
5352 void
5353f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5354{
5355 buf_T *buf = term_get_buf(argvars, "term_setsize()");
5356 term_T *term;
5357 varnumber_T rows, cols;
5358
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005359 if (buf == NULL)
5360 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005361 emsg(_("E955: Not a terminal buffer"));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005362 return;
5363 }
5364 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02005365 return;
5366 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005367 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005368 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005369 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005370 cols = cols <= 0 ? term->tl_cols : cols;
5371 vterm_set_size(term->tl_vterm, rows, cols);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005372 // handle_resize() will resize the windows
Bram Moolenaara42d3632018-04-14 17:05:38 +02005373
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005374 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaara42d3632018-04-14 17:05:38 +02005375 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
5376 term_report_winsize(term, term->tl_rows, term->tl_cols);
5377}
5378
5379/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005380 * "term_getstatus(buf)" function
5381 */
5382 void
5383f_term_getstatus(typval_T *argvars, typval_T *rettv)
5384{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005385 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005386 term_T *term;
5387 char_u val[100];
5388
5389 rettv->v_type = VAR_STRING;
5390 if (buf == NULL)
5391 return;
5392 term = buf->b_term;
5393
5394 if (term_job_running(term))
5395 STRCPY(val, "running");
5396 else
5397 STRCPY(val, "finished");
5398 if (term->tl_normal_mode)
5399 STRCAT(val, ",normal");
5400 rettv->vval.v_string = vim_strsave(val);
5401}
5402
5403/*
5404 * "term_gettitle(buf)" function
5405 */
5406 void
5407f_term_gettitle(typval_T *argvars, typval_T *rettv)
5408{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005409 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005410
5411 rettv->v_type = VAR_STRING;
5412 if (buf == NULL)
5413 return;
5414
5415 if (buf->b_term->tl_title != NULL)
5416 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
5417}
5418
5419/*
5420 * "term_gettty(buf)" function
5421 */
5422 void
5423f_term_gettty(typval_T *argvars, typval_T *rettv)
5424{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005425 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02005426 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005427 int num = 0;
5428
5429 rettv->v_type = VAR_STRING;
5430 if (buf == NULL)
5431 return;
5432 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005433 num = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005434
5435 switch (num)
5436 {
5437 case 0:
5438 if (buf->b_term->tl_job != NULL)
5439 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005440 break;
5441 case 1:
5442 if (buf->b_term->tl_job != NULL)
5443 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005444 break;
5445 default:
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005446 semsg(_(e_invarg2), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005447 return;
5448 }
5449 if (p != NULL)
5450 rettv->vval.v_string = vim_strsave(p);
5451}
5452
5453/*
5454 * "term_list()" function
5455 */
5456 void
5457f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
5458{
5459 term_T *tp;
5460 list_T *l;
5461
5462 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
5463 return;
5464
5465 l = rettv->vval.v_list;
5466 for (tp = first_term; tp != NULL; tp = tp->tl_next)
5467 if (tp != NULL && tp->tl_buffer != NULL)
5468 if (list_append_number(l,
5469 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
5470 return;
5471}
5472
5473/*
5474 * "term_scrape(buf, row)" function
5475 */
5476 void
5477f_term_scrape(typval_T *argvars, typval_T *rettv)
5478{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005479 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005480 VTermScreen *screen = NULL;
5481 VTermPos pos;
5482 list_T *l;
5483 term_T *term;
5484 char_u *p;
5485 sb_line_T *line;
5486
5487 if (rettv_list_alloc(rettv) == FAIL)
5488 return;
5489 if (buf == NULL)
5490 return;
5491 term = buf->b_term;
5492
5493 l = rettv->vval.v_list;
5494 pos.row = get_row_number(&argvars[1], term);
5495
5496 if (term->tl_vterm != NULL)
5497 {
5498 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01005499 if (screen == NULL) // can't really happen
5500 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005501 p = NULL;
5502 line = NULL;
5503 }
5504 else
5505 {
5506 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
5507
5508 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
5509 return;
5510 p = ml_get_buf(buf, lnum + 1, FALSE);
5511 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
5512 }
5513
5514 for (pos.col = 0; pos.col < term->tl_cols; )
5515 {
5516 dict_T *dcell;
5517 int width;
5518 VTermScreenCellAttrs attrs;
5519 VTermColor fg, bg;
5520 char_u rgb[8];
5521 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5522 int off = 0;
5523 int i;
5524
5525 if (screen == NULL)
5526 {
5527 cellattr_T *cellattr;
5528 int len;
5529
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005530 // vterm has finished, get the cell from scrollback
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005531 if (pos.col >= line->sb_cols)
5532 break;
5533 cellattr = line->sb_cells + pos.col;
5534 width = cellattr->width;
5535 attrs = cellattr->attrs;
5536 fg = cellattr->fg;
5537 bg = cellattr->bg;
Bram Moolenaar1614a142019-10-06 22:00:13 +02005538 len = mb_ptr2len(p);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005539 mch_memmove(mbs, p, len);
5540 mbs[len] = NUL;
5541 p += len;
5542 }
5543 else
5544 {
5545 VTermScreenCell cell;
5546 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5547 break;
5548 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5549 {
5550 if (cell.chars[i] == 0)
5551 break;
5552 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5553 }
5554 mbs[off] = NUL;
5555 width = cell.width;
5556 attrs = cell.attrs;
5557 fg = cell.fg;
5558 bg = cell.bg;
5559 }
5560 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01005561 if (dcell == NULL)
5562 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005563 list_append_dict(l, dcell);
5564
Bram Moolenaare0be1672018-07-08 16:50:37 +02005565 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005566
5567 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5568 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005569 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005570 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5571 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005572 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005573
Bram Moolenaare0be1672018-07-08 16:50:37 +02005574 dict_add_number(dcell, "attr", cell2attr(attrs, fg, bg));
5575 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005576
5577 ++pos.col;
5578 if (width == 2)
5579 ++pos.col;
5580 }
5581}
5582
5583/*
5584 * "term_sendkeys(buf, keys)" function
5585 */
5586 void
5587f_term_sendkeys(typval_T *argvars, typval_T *rettv)
5588{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005589 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005590 char_u *msg;
5591 term_T *term;
5592
5593 rettv->v_type = VAR_UNKNOWN;
5594 if (buf == NULL)
5595 return;
5596
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005597 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005598 if (msg == NULL)
5599 return;
5600 term = buf->b_term;
5601 if (term->tl_vterm == NULL)
5602 return;
5603
5604 while (*msg != NUL)
5605 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02005606 int c;
5607
5608 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
5609 {
5610 c = TO_SPECIAL(msg[1], msg[2]);
5611 msg += 3;
5612 }
5613 else
5614 {
5615 c = PTR2CHAR(msg);
5616 msg += MB_CPTR2LEN(msg);
5617 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01005618 send_keys_to_term(term, c, 0, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005619 }
5620}
5621
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005622#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5623/*
5624 * "term_getansicolors(buf)" function
5625 */
5626 void
5627f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5628{
5629 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5630 term_T *term;
5631 VTermState *state;
5632 VTermColor color;
5633 char_u hexbuf[10];
5634 int index;
5635 list_T *list;
5636
5637 if (rettv_list_alloc(rettv) == FAIL)
5638 return;
5639
5640 if (buf == NULL)
5641 return;
5642 term = buf->b_term;
5643 if (term->tl_vterm == NULL)
5644 return;
5645
5646 list = rettv->vval.v_list;
5647 state = vterm_obtain_state(term->tl_vterm);
5648 for (index = 0; index < 16; index++)
5649 {
5650 vterm_state_get_palette_color(state, index, &color);
5651 sprintf((char *)hexbuf, "#%02x%02x%02x",
5652 color.red, color.green, color.blue);
5653 if (list_append_string(list, hexbuf, 7) == FAIL)
5654 return;
5655 }
5656}
5657
5658/*
5659 * "term_setansicolors(buf, list)" function
5660 */
5661 void
5662f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5663{
5664 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5665 term_T *term;
5666
5667 if (buf == NULL)
5668 return;
5669 term = buf->b_term;
5670 if (term->tl_vterm == NULL)
5671 return;
5672
5673 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5674 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005675 emsg(_(e_listreq));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005676 return;
5677 }
5678
5679 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005680 emsg(_(e_invarg));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005681}
5682#endif
5683
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005684/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005685 * "term_setapi(buf, api)" function
5686 */
5687 void
5688f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
5689{
5690 buf_T *buf = term_get_buf(argvars, "term_setapi()");
5691 term_T *term;
5692 char_u *api;
5693
5694 if (buf == NULL)
5695 return;
5696 term = buf->b_term;
5697 vim_free(term->tl_api);
5698 api = tv_get_string_chk(&argvars[1]);
5699 if (api != NULL)
5700 term->tl_api = vim_strsave(api);
5701 else
5702 term->tl_api = NULL;
5703}
5704
5705/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005706 * "term_setrestore(buf, command)" function
5707 */
5708 void
5709f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5710{
5711#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005712 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005713 term_T *term;
5714 char_u *cmd;
5715
5716 if (buf == NULL)
5717 return;
5718 term = buf->b_term;
5719 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005720 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005721 if (cmd != NULL)
5722 term->tl_command = vim_strsave(cmd);
5723 else
5724 term->tl_command = NULL;
5725#endif
5726}
5727
5728/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005729 * "term_setkill(buf, how)" function
5730 */
5731 void
5732f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5733{
5734 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5735 term_T *term;
5736 char_u *how;
5737
5738 if (buf == NULL)
5739 return;
5740 term = buf->b_term;
5741 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005742 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005743 if (how != NULL)
5744 term->tl_kill = vim_strsave(how);
5745 else
5746 term->tl_kill = NULL;
5747}
5748
5749/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005750 * "term_start(command, options)" function
5751 */
5752 void
5753f_term_start(typval_T *argvars, typval_T *rettv)
5754{
5755 jobopt_T opt;
5756 buf_T *buf;
5757
5758 init_job_options(&opt);
5759 if (argvars[1].v_type != VAR_UNKNOWN
5760 && get_job_options(&argvars[1], &opt,
5761 JO_TIMEOUT_ALL + JO_STOPONEXIT
5762 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5763 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5764 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5765 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005766 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005767 + JO2_NORESTORE + JO2_TERM_KILL
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005768 + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005769 return;
5770
Bram Moolenaar13568252018-03-16 20:46:58 +01005771 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005772
5773 if (buf != NULL && buf->b_term != NULL)
5774 rettv->vval.v_number = buf->b_fnum;
5775}
5776
5777/*
5778 * "term_wait" function
5779 */
5780 void
5781f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5782{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005783 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005784
5785 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005786 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005787 if (buf->b_term->tl_job == NULL)
5788 {
5789 ch_log(NULL, "term_wait(): no job to wait for");
5790 return;
5791 }
5792 if (buf->b_term->tl_job->jv_channel == NULL)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005793 // channel is closed, nothing to do
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005794 return;
5795
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005796 // Get the job status, this will detect a job that finished.
Bram Moolenaara15ef452018-02-09 16:46:00 +01005797 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005798 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5799 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005800 // The job is dead, keep reading channel I/O until the channel is
5801 // closed. buf->b_term may become NULL if the terminal was closed while
5802 // waiting.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005803 ch_log(NULL, "term_wait(): waiting for channel to close");
5804 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5805 {
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005806 term_flush_messages();
5807
Bram Moolenaard45aa552018-05-21 22:50:29 +02005808 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01005809 if (!buf_valid(buf))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005810 // If the terminal is closed when the channel is closed the
5811 // buffer disappears.
Bram Moolenaare5182262017-11-19 15:05:44 +01005812 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005813 }
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005814
5815 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005816 }
5817 else
5818 {
5819 long wait = 10L;
5820
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005821 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005822
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005823 // Wait for some time for any channel I/O.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005824 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005825 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005826 ui_delay(wait, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005827
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005828 // Flushing messages on channels is hopefully sufficient.
5829 // TODO: is there a better way?
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005830 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005831 }
5832}
5833
5834/*
5835 * Called when a channel has sent all the lines to a terminal.
5836 * Send a CTRL-D to mark the end of the text.
5837 */
5838 void
5839term_send_eof(channel_T *ch)
5840{
5841 term_T *term;
5842
5843 for (term = first_term; term != NULL; term = term->tl_next)
5844 if (term->tl_job == ch->ch_job)
5845 {
5846 if (term->tl_eof_chars != NULL)
5847 {
5848 channel_send(ch, PART_IN, term->tl_eof_chars,
5849 (int)STRLEN(term->tl_eof_chars), NULL);
5850 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
5851 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01005852# ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005853 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005854 // Default: CTRL-D
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005855 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
5856# endif
5857 }
5858}
5859
Bram Moolenaar113e1072019-01-20 15:30:40 +01005860#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005861 job_T *
5862term_getjob(term_T *term)
5863{
5864 return term != NULL ? term->tl_job : NULL;
5865}
Bram Moolenaar113e1072019-01-20 15:30:40 +01005866#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02005867
Bram Moolenaar4f974752019-02-17 17:44:42 +01005868# if defined(MSWIN) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005869
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005870///////////////////////////////////////
5871// 2. MS-Windows implementation.
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005872#ifdef PROTO
5873typedef int COORD;
5874typedef int DWORD;
5875typedef int HANDLE;
5876typedef int *DWORD_PTR;
5877typedef int HPCON;
5878typedef int HRESULT;
5879typedef int LPPROC_THREAD_ATTRIBUTE_LIST;
Bram Moolenaarad3ec762019-04-21 00:00:13 +02005880typedef int SIZE_T;
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005881typedef int PSIZE_T;
5882typedef int PVOID;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01005883typedef int BOOL;
5884# define WINAPI
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02005885#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005886
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005887HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
5888HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
5889HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
Bram Moolenaar48773f12019-02-12 21:46:46 +01005890BOOL (WINAPI *pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
5891BOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
5892void (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005893
5894 static int
5895dyn_conpty_init(int verbose)
5896{
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005897 static HMODULE hKerneldll = NULL;
5898 int i;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005899 static struct
5900 {
5901 char *name;
5902 FARPROC *ptr;
5903 } conpty_entry[] =
5904 {
5905 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
5906 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
5907 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
5908 {"InitializeProcThreadAttributeList",
5909 (FARPROC*)&pInitializeProcThreadAttributeList},
5910 {"UpdateProcThreadAttribute",
5911 (FARPROC*)&pUpdateProcThreadAttribute},
5912 {"DeleteProcThreadAttributeList",
5913 (FARPROC*)&pDeleteProcThreadAttributeList},
5914 {NULL, NULL}
5915 };
5916
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01005917 if (!has_conpty_working())
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005918 {
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005919 if (verbose)
5920 emsg(_("E982: ConPTY is not available"));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005921 return FAIL;
5922 }
5923
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005924 // No need to initialize twice.
5925 if (hKerneldll)
5926 return OK;
5927
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005928 hKerneldll = vimLoadLib("kernel32.dll");
5929 for (i = 0; conpty_entry[i].name != NULL
5930 && conpty_entry[i].ptr != NULL; ++i)
5931 {
5932 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
5933 conpty_entry[i].name)) == NULL)
5934 {
5935 if (verbose)
5936 semsg(_(e_loadfunc), conpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01005937 hKerneldll = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005938 return FAIL;
5939 }
5940 }
5941
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005942 return OK;
5943}
5944
5945 static int
5946conpty_term_and_job_init(
5947 term_T *term,
5948 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02005949 char **argv UNUSED,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005950 jobopt_T *opt,
5951 jobopt_T *orig_opt)
5952{
5953 WCHAR *cmd_wchar = NULL;
5954 WCHAR *cmd_wchar_copy = NULL;
5955 WCHAR *cwd_wchar = NULL;
5956 WCHAR *env_wchar = NULL;
5957 channel_T *channel = NULL;
5958 job_T *job = NULL;
5959 HANDLE jo = NULL;
5960 garray_T ga_cmd, ga_env;
5961 char_u *cmd = NULL;
5962 HRESULT hr;
5963 COORD consize;
5964 SIZE_T breq;
5965 PROCESS_INFORMATION proc_info;
5966 HANDLE i_theirs = NULL;
5967 HANDLE o_theirs = NULL;
5968 HANDLE i_ours = NULL;
5969 HANDLE o_ours = NULL;
5970
5971 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
5972 ga_init2(&ga_env, (int)sizeof(char*), 20);
5973
5974 if (argvar->v_type == VAR_STRING)
5975 {
5976 cmd = argvar->vval.v_string;
5977 }
5978 else if (argvar->v_type == VAR_LIST)
5979 {
5980 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
5981 goto failed;
5982 cmd = ga_cmd.ga_data;
5983 }
5984 if (cmd == NULL || *cmd == NUL)
5985 {
5986 emsg(_(e_invarg));
5987 goto failed;
5988 }
5989
5990 term->tl_arg0_cmd = vim_strsave(cmd);
5991
5992 cmd_wchar = enc_to_utf16(cmd, NULL);
5993
5994 if (cmd_wchar != NULL)
5995 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005996 // Request by CreateProcessW
5997 breq = wcslen(cmd_wchar) + 1 + 1; // Addition of NUL by API
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005998 cmd_wchar_copy = ALLOC_MULT(WCHAR, breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01005999 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
6000 }
6001
6002 ga_clear(&ga_cmd);
6003 if (cmd_wchar == NULL)
6004 goto failed;
6005 if (opt->jo_cwd != NULL)
6006 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
6007
6008 win32_build_env(opt->jo_env, &ga_env, TRUE);
6009 env_wchar = ga_env.ga_data;
6010
6011 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
6012 goto failed;
6013 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
6014 goto failed;
6015
6016 consize.X = term->tl_cols;
6017 consize.Y = term->tl_rows;
6018 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
6019 &term->tl_conpty);
6020 if (FAILED(hr))
6021 goto failed;
6022
6023 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
6024
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006025 // Set up pipe inheritance safely: Vista or later.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006026 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006027 term->tl_siex.lpAttributeList = alloc(breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006028 if (!term->tl_siex.lpAttributeList)
6029 goto failed;
6030 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
6031 0, &breq))
6032 goto failed;
6033 if (!pUpdateProcThreadAttribute(
6034 term->tl_siex.lpAttributeList, 0,
6035 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
6036 sizeof(HPCON), NULL, NULL))
6037 goto failed;
6038
6039 channel = add_channel();
6040 if (channel == NULL)
6041 goto failed;
6042
6043 job = job_alloc();
6044 if (job == NULL)
6045 goto failed;
6046 if (argvar->v_type == VAR_STRING)
6047 {
6048 int argc;
6049
6050 build_argv_from_string(cmd, &job->jv_argv, &argc);
6051 }
6052 else
6053 {
6054 int argc;
6055
6056 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6057 }
6058
6059 if (opt->jo_set & JO_IN_BUF)
6060 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6061
6062 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
6063 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
6064 | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP
6065 | CREATE_DEFAULT_ERROR_MODE,
6066 env_wchar, cwd_wchar,
6067 &term->tl_siex.StartupInfo, &proc_info))
6068 goto failed;
6069
6070 CloseHandle(i_theirs);
6071 CloseHandle(o_theirs);
6072
6073 channel_set_pipes(channel,
6074 (sock_T)i_ours,
6075 (sock_T)o_ours,
6076 (sock_T)o_ours);
6077
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006078 // Write lines with CR instead of NL.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006079 channel->ch_write_text_mode = TRUE;
6080
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006081 // Use to explicitly delete anonymous pipe handle.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006082 channel->ch_anonymous_pipe = TRUE;
6083
6084 jo = CreateJobObject(NULL, NULL);
6085 if (jo == NULL)
6086 goto failed;
6087
6088 if (!AssignProcessToJobObject(jo, proc_info.hProcess))
6089 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006090 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006091 CloseHandle(jo);
6092 jo = NULL;
6093 }
6094
6095 ResumeThread(proc_info.hThread);
6096 CloseHandle(proc_info.hThread);
6097
6098 vim_free(cmd_wchar);
6099 vim_free(cmd_wchar_copy);
6100 vim_free(cwd_wchar);
6101 vim_free(env_wchar);
6102
6103 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6104 goto failed;
6105
6106#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6107 if (opt->jo_set2 & JO2_ANSI_COLORS)
6108 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6109 else
6110 init_vterm_ansi_colors(term->tl_vterm);
6111#endif
6112
6113 channel_set_job(channel, job, opt);
6114 job_set_options(job, opt);
6115
6116 job->jv_channel = channel;
6117 job->jv_proc_info = proc_info;
6118 job->jv_job_object = jo;
6119 job->jv_status = JOB_STARTED;
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006120 job->jv_tty_type = vim_strsave((char_u *)"conpty");
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006121 ++job->jv_refcount;
6122 term->tl_job = job;
6123
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006124 // Redirecting stdout and stderr doesn't work at the job level. Instead
6125 // open the file here and handle it in. opt->jo_io was changed in
6126 // setup_job_options(), use the original flags here.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006127 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6128 {
6129 char_u *fname = opt->jo_io_name[PART_OUT];
6130
6131 ch_log(channel, "Opening output file %s", fname);
6132 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6133 if (term->tl_out_fd == NULL)
6134 semsg(_(e_notopen), fname);
6135 }
6136
6137 return OK;
6138
6139failed:
6140 ga_clear(&ga_cmd);
6141 ga_clear(&ga_env);
6142 vim_free(cmd_wchar);
6143 vim_free(cmd_wchar_copy);
6144 vim_free(cwd_wchar);
6145 if (channel != NULL)
6146 channel_clear(channel);
6147 if (job != NULL)
6148 {
6149 job->jv_channel = NULL;
6150 job_cleanup(job);
6151 }
6152 term->tl_job = NULL;
6153 if (jo != NULL)
6154 CloseHandle(jo);
6155
6156 if (term->tl_siex.lpAttributeList != NULL)
6157 {
6158 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6159 vim_free(term->tl_siex.lpAttributeList);
6160 }
6161 term->tl_siex.lpAttributeList = NULL;
6162 if (o_theirs != NULL)
6163 CloseHandle(o_theirs);
6164 if (o_ours != NULL)
6165 CloseHandle(o_ours);
6166 if (i_ours != NULL)
6167 CloseHandle(i_ours);
6168 if (i_theirs != NULL)
6169 CloseHandle(i_theirs);
6170 if (term->tl_conpty != NULL)
6171 pClosePseudoConsole(term->tl_conpty);
6172 term->tl_conpty = NULL;
6173 return FAIL;
6174}
6175
6176 static void
6177conpty_term_report_winsize(term_T *term, int rows, int cols)
6178{
6179 COORD consize;
6180
6181 consize.X = cols;
6182 consize.Y = rows;
6183 pResizePseudoConsole(term->tl_conpty, consize);
6184}
6185
Bram Moolenaar840d16f2019-09-10 21:27:18 +02006186 static void
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006187term_free_conpty(term_T *term)
6188{
6189 if (term->tl_siex.lpAttributeList != NULL)
6190 {
6191 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6192 vim_free(term->tl_siex.lpAttributeList);
6193 }
6194 term->tl_siex.lpAttributeList = NULL;
6195 if (term->tl_conpty != NULL)
6196 pClosePseudoConsole(term->tl_conpty);
6197 term->tl_conpty = NULL;
6198}
6199
6200 int
6201use_conpty(void)
6202{
6203 return has_conpty;
6204}
6205
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006206# ifndef PROTO
6207
6208#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
6209#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01006210#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006211
6212void* (*winpty_config_new)(UINT64, void*);
6213void* (*winpty_open)(void*, void*);
6214void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
6215BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
6216void (*winpty_config_set_mouse_mode)(void*, int);
6217void (*winpty_config_set_initial_size)(void*, int, int);
6218LPCWSTR (*winpty_conin_name)(void*);
6219LPCWSTR (*winpty_conout_name)(void*);
6220LPCWSTR (*winpty_conerr_name)(void*);
6221void (*winpty_free)(void*);
6222void (*winpty_config_free)(void*);
6223void (*winpty_spawn_config_free)(void*);
6224void (*winpty_error_free)(void*);
6225LPCWSTR (*winpty_error_msg)(void*);
6226BOOL (*winpty_set_size)(void*, int, int, void*);
6227HANDLE (*winpty_agent_process)(void*);
6228
6229#define WINPTY_DLL "winpty.dll"
6230
6231static HINSTANCE hWinPtyDLL = NULL;
6232# endif
6233
6234 static int
6235dyn_winpty_init(int verbose)
6236{
6237 int i;
6238 static struct
6239 {
6240 char *name;
6241 FARPROC *ptr;
6242 } winpty_entry[] =
6243 {
6244 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
6245 {"winpty_config_free", (FARPROC*)&winpty_config_free},
6246 {"winpty_config_new", (FARPROC*)&winpty_config_new},
6247 {"winpty_config_set_mouse_mode",
6248 (FARPROC*)&winpty_config_set_mouse_mode},
6249 {"winpty_config_set_initial_size",
6250 (FARPROC*)&winpty_config_set_initial_size},
6251 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
6252 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
6253 {"winpty_error_free", (FARPROC*)&winpty_error_free},
6254 {"winpty_free", (FARPROC*)&winpty_free},
6255 {"winpty_open", (FARPROC*)&winpty_open},
6256 {"winpty_spawn", (FARPROC*)&winpty_spawn},
6257 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
6258 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
6259 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
6260 {"winpty_set_size", (FARPROC*)&winpty_set_size},
6261 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
6262 {NULL, NULL}
6263 };
6264
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006265 // No need to initialize twice.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006266 if (hWinPtyDLL)
6267 return OK;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006268 // Load winpty.dll, prefer using the 'winptydll' option, fall back to just
6269 // winpty.dll.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006270 if (*p_winptydll != NUL)
6271 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
6272 if (!hWinPtyDLL)
6273 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
6274 if (!hWinPtyDLL)
6275 {
6276 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006277 semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006278 : (char_u *)WINPTY_DLL);
6279 return FAIL;
6280 }
6281 for (i = 0; winpty_entry[i].name != NULL
6282 && winpty_entry[i].ptr != NULL; ++i)
6283 {
6284 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
6285 winpty_entry[i].name)) == NULL)
6286 {
6287 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006288 semsg(_(e_loadfunc), winpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006289 hWinPtyDLL = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006290 return FAIL;
6291 }
6292 }
6293
6294 return OK;
6295}
6296
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006297 static int
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006298winpty_term_and_job_init(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006299 term_T *term,
6300 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02006301 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006302 jobopt_T *opt,
6303 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006304{
6305 WCHAR *cmd_wchar = NULL;
6306 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006307 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006308 channel_T *channel = NULL;
6309 job_T *job = NULL;
6310 DWORD error;
6311 HANDLE jo = NULL;
6312 HANDLE child_process_handle;
6313 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01006314 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006315 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006316 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006317 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006318
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006319 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
6320 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006321
6322 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006323 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006324 cmd = argvar->vval.v_string;
6325 }
6326 else if (argvar->v_type == VAR_LIST)
6327 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006328 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006329 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006330 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006331 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006332 if (cmd == NULL || *cmd == NUL)
6333 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006334 emsg(_(e_invarg));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006335 goto failed;
6336 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006337
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006338 term->tl_arg0_cmd = vim_strsave(cmd);
6339
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006340 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006341 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006342 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006343 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006344 if (opt->jo_cwd != NULL)
6345 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006346
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006347 win32_build_env(opt->jo_env, &ga_env, TRUE);
6348 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006349
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006350 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
6351 if (term->tl_winpty_config == NULL)
6352 goto failed;
6353
6354 winpty_config_set_mouse_mode(term->tl_winpty_config,
6355 WINPTY_MOUSE_MODE_FORCE);
6356 winpty_config_set_initial_size(term->tl_winpty_config,
6357 term->tl_cols, term->tl_rows);
6358 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
6359 if (term->tl_winpty == NULL)
6360 goto failed;
6361
6362 spawn_config = winpty_spawn_config_new(
6363 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
6364 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
6365 NULL,
6366 cmd_wchar,
6367 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006368 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006369 &winpty_err);
6370 if (spawn_config == NULL)
6371 goto failed;
6372
6373 channel = add_channel();
6374 if (channel == NULL)
6375 goto failed;
6376
6377 job = job_alloc();
6378 if (job == NULL)
6379 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02006380 if (argvar->v_type == VAR_STRING)
6381 {
6382 int argc;
6383
6384 build_argv_from_string(cmd, &job->jv_argv, &argc);
6385 }
6386 else
6387 {
6388 int argc;
6389
6390 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6391 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006392
6393 if (opt->jo_set & JO_IN_BUF)
6394 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6395
6396 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
6397 &child_thread_handle, &error, &winpty_err))
6398 goto failed;
6399
6400 channel_set_pipes(channel,
6401 (sock_T)CreateFileW(
6402 winpty_conin_name(term->tl_winpty),
6403 GENERIC_WRITE, 0, NULL,
6404 OPEN_EXISTING, 0, NULL),
6405 (sock_T)CreateFileW(
6406 winpty_conout_name(term->tl_winpty),
6407 GENERIC_READ, 0, NULL,
6408 OPEN_EXISTING, 0, NULL),
6409 (sock_T)CreateFileW(
6410 winpty_conerr_name(term->tl_winpty),
6411 GENERIC_READ, 0, NULL,
6412 OPEN_EXISTING, 0, NULL));
6413
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006414 // Write lines with CR instead of NL.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006415 channel->ch_write_text_mode = TRUE;
6416
6417 jo = CreateJobObject(NULL, NULL);
6418 if (jo == NULL)
6419 goto failed;
6420
6421 if (!AssignProcessToJobObject(jo, child_process_handle))
6422 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006423 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006424 CloseHandle(jo);
6425 jo = NULL;
6426 }
6427
6428 winpty_spawn_config_free(spawn_config);
6429 vim_free(cmd_wchar);
6430 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006431 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006432
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006433 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6434 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006435
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006436#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6437 if (opt->jo_set2 & JO2_ANSI_COLORS)
6438 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6439 else
6440 init_vterm_ansi_colors(term->tl_vterm);
6441#endif
6442
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006443 channel_set_job(channel, job, opt);
6444 job_set_options(job, opt);
6445
6446 job->jv_channel = channel;
6447 job->jv_proc_info.hProcess = child_process_handle;
6448 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
6449 job->jv_job_object = jo;
6450 job->jv_status = JOB_STARTED;
6451 job->jv_tty_in = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006452 (short_u *)winpty_conin_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006453 job->jv_tty_out = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006454 (short_u *)winpty_conout_name(term->tl_winpty), NULL);
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006455 job->jv_tty_type = vim_strsave((char_u *)"winpty");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006456 ++job->jv_refcount;
6457 term->tl_job = job;
6458
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006459 // Redirecting stdout and stderr doesn't work at the job level. Instead
6460 // open the file here and handle it in. opt->jo_io was changed in
6461 // setup_job_options(), use the original flags here.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006462 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6463 {
6464 char_u *fname = opt->jo_io_name[PART_OUT];
6465
6466 ch_log(channel, "Opening output file %s", fname);
6467 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6468 if (term->tl_out_fd == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006469 semsg(_(e_notopen), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006470 }
6471
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006472 return OK;
6473
6474failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006475 ga_clear(&ga_cmd);
6476 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006477 vim_free(cmd_wchar);
6478 vim_free(cwd_wchar);
6479 if (spawn_config != NULL)
6480 winpty_spawn_config_free(spawn_config);
6481 if (channel != NULL)
6482 channel_clear(channel);
6483 if (job != NULL)
6484 {
6485 job->jv_channel = NULL;
6486 job_cleanup(job);
6487 }
6488 term->tl_job = NULL;
6489 if (jo != NULL)
6490 CloseHandle(jo);
6491 if (term->tl_winpty != NULL)
6492 winpty_free(term->tl_winpty);
6493 term->tl_winpty = NULL;
6494 if (term->tl_winpty_config != NULL)
6495 winpty_config_free(term->tl_winpty_config);
6496 term->tl_winpty_config = NULL;
6497 if (winpty_err != NULL)
6498 {
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006499 char *msg = (char *)utf16_to_enc(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006500 (short_u *)winpty_error_msg(winpty_err), NULL);
6501
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006502 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006503 winpty_error_free(winpty_err);
6504 }
6505 return FAIL;
6506}
6507
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006508/*
6509 * Create a new terminal of "rows" by "cols" cells.
6510 * Store a reference in "term".
6511 * Return OK or FAIL.
6512 */
6513 static int
6514term_and_job_init(
6515 term_T *term,
6516 typval_T *argvar,
Bram Moolenaar197c6b72019-11-03 23:37:12 +01006517 char **argv,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006518 jobopt_T *opt,
6519 jobopt_T *orig_opt)
6520{
6521 int use_winpty = FALSE;
6522 int use_conpty = FALSE;
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006523 int tty_type = *p_twt;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006524
6525 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
6526 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
6527
6528 if (!has_winpty && !has_conpty)
6529 // If neither is available give the errors for winpty, since when
6530 // conpty is not available it can't be installed either.
6531 return dyn_winpty_init(TRUE);
6532
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006533 if (opt->jo_tty_type != NUL)
6534 tty_type = opt->jo_tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006535
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006536 if (tty_type == NUL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006537 {
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01006538 if (has_conpty && (is_conpty_stable() || !has_winpty))
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006539 use_conpty = TRUE;
6540 else if (has_winpty)
6541 use_winpty = TRUE;
6542 // else: error
6543 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006544 else if (tty_type == 'w') // winpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006545 {
6546 if (has_winpty)
6547 use_winpty = TRUE;
6548 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006549 else if (tty_type == 'c') // conpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006550 {
6551 if (has_conpty)
6552 use_conpty = TRUE;
6553 else
6554 return dyn_conpty_init(TRUE);
6555 }
6556
6557 if (use_conpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006558 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006559
6560 if (use_winpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006561 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006562
6563 // error
6564 return dyn_winpty_init(TRUE);
6565}
6566
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006567 static int
6568create_pty_only(term_T *term, jobopt_T *options)
6569{
6570 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
6571 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
6572 char in_name[80], out_name[80];
6573 channel_T *channel = NULL;
6574
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006575 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6576 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006577
6578 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
6579 GetCurrentProcessId(),
6580 curbuf->b_fnum);
6581 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
6582 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6583 PIPE_UNLIMITED_INSTANCES,
6584 0, 0, NMPWAIT_NOWAIT, NULL);
6585 if (hPipeIn == INVALID_HANDLE_VALUE)
6586 goto failed;
6587
6588 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
6589 GetCurrentProcessId(),
6590 curbuf->b_fnum);
6591 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
6592 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6593 PIPE_UNLIMITED_INSTANCES,
6594 0, 0, 0, NULL);
6595 if (hPipeOut == INVALID_HANDLE_VALUE)
6596 goto failed;
6597
6598 ConnectNamedPipe(hPipeIn, NULL);
6599 ConnectNamedPipe(hPipeOut, NULL);
6600
6601 term->tl_job = job_alloc();
6602 if (term->tl_job == NULL)
6603 goto failed;
6604 ++term->tl_job->jv_refcount;
6605
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006606 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006607 term->tl_job->jv_status = JOB_FINISHED;
6608
6609 channel = add_channel();
6610 if (channel == NULL)
6611 goto failed;
6612 term->tl_job->jv_channel = channel;
6613 channel->ch_keep_open = TRUE;
6614 channel->ch_named_pipe = TRUE;
6615
6616 channel_set_pipes(channel,
6617 (sock_T)hPipeIn,
6618 (sock_T)hPipeOut,
6619 (sock_T)hPipeOut);
6620 channel_set_job(channel, term->tl_job, options);
6621 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
6622 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
6623
6624 return OK;
6625
6626failed:
6627 if (hPipeIn != NULL)
6628 CloseHandle(hPipeIn);
6629 if (hPipeOut != NULL)
6630 CloseHandle(hPipeOut);
6631 return FAIL;
6632}
6633
6634/*
6635 * Free the terminal emulator part of "term".
6636 */
6637 static void
6638term_free_vterm(term_T *term)
6639{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006640 term_free_conpty(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006641 if (term->tl_winpty != NULL)
6642 winpty_free(term->tl_winpty);
6643 term->tl_winpty = NULL;
6644 if (term->tl_winpty_config != NULL)
6645 winpty_config_free(term->tl_winpty_config);
6646 term->tl_winpty_config = NULL;
6647 if (term->tl_vterm != NULL)
6648 vterm_free(term->tl_vterm);
6649 term->tl_vterm = NULL;
6650}
6651
6652/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006653 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006654 */
6655 static void
6656term_report_winsize(term_T *term, int rows, int cols)
6657{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006658 if (term->tl_conpty)
6659 conpty_term_report_winsize(term, rows, cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006660 if (term->tl_winpty)
6661 winpty_set_size(term->tl_winpty, cols, rows, NULL);
6662}
6663
6664 int
6665terminal_enabled(void)
6666{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006667 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006668}
6669
6670# else
6671
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006672///////////////////////////////////////
6673// 3. Unix-like implementation.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006674
6675/*
6676 * Create a new terminal of "rows" by "cols" cells.
6677 * Start job for "cmd".
6678 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01006679 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006680 * Return OK or FAIL.
6681 */
6682 static int
6683term_and_job_init(
6684 term_T *term,
6685 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01006686 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006687 jobopt_T *opt,
6688 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006689{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006690 term->tl_arg0_cmd = NULL;
6691
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006692 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6693 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006694
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006695#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6696 if (opt->jo_set2 & JO2_ANSI_COLORS)
6697 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6698 else
6699 init_vterm_ansi_colors(term->tl_vterm);
6700#endif
6701
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006702 // This may change a string in "argvar".
Bram Moolenaar21109272020-01-30 16:27:20 +01006703 term->tl_job = job_start(argvar, argv, opt, &term->tl_job);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006704 if (term->tl_job != NULL)
6705 ++term->tl_job->jv_refcount;
6706
6707 return term->tl_job != NULL
6708 && term->tl_job->jv_channel != NULL
6709 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
6710}
6711
6712 static int
6713create_pty_only(term_T *term, jobopt_T *opt)
6714{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006715 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6716 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006717
6718 term->tl_job = job_alloc();
6719 if (term->tl_job == NULL)
6720 return FAIL;
6721 ++term->tl_job->jv_refcount;
6722
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006723 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006724 term->tl_job->jv_status = JOB_FINISHED;
6725
6726 return mch_create_pty_channel(term->tl_job, opt);
6727}
6728
6729/*
6730 * Free the terminal emulator part of "term".
6731 */
6732 static void
6733term_free_vterm(term_T *term)
6734{
6735 if (term->tl_vterm != NULL)
6736 vterm_free(term->tl_vterm);
6737 term->tl_vterm = NULL;
6738}
6739
6740/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006741 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006742 */
6743 static void
6744term_report_winsize(term_T *term, int rows, int cols)
6745{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006746 // Use an ioctl() to report the new window size to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006747 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
6748 {
6749 int fd = -1;
6750 int part;
6751
6752 for (part = PART_OUT; part < PART_COUNT; ++part)
6753 {
6754 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01006755 if (mch_isatty(fd))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006756 break;
6757 }
6758 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
6759 mch_signal_job(term->tl_job, (char_u *)"winch");
6760 }
6761}
6762
6763# endif
6764
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006765#endif // FEAT_TERMINAL