blob: 7f440a3bd52623662ca3206d727a63a006f455ee [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 }
Bram Moolenaarcee52202020-03-11 14:19:58 +0100385 CHECK_CURBUF;
Bram Moolenaard96ff162018-02-18 22:13:29 +0100386
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100387 // Wiping out the buffer will also close the window and call
388 // free_terminal().
Bram Moolenaard96ff162018-02-18 22:13:29 +0100389 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
390}
391
392/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200393 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100394 * Use either "argvar" or "argv", the other must be NULL.
395 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
396 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200397 * Returns NULL when failed.
398 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100399 buf_T *
400term_start(
401 typval_T *argvar,
402 char **argv,
403 jobopt_T *opt,
404 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200405{
406 exarg_T split_ea;
407 win_T *old_curwin = curwin;
408 term_T *term;
409 buf_T *old_curbuf = NULL;
410 int res;
411 buf_T *newbuf;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100412 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200413 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200414
415 if (check_restricted() || check_secure())
416 return NULL;
417
418 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
419 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
420 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
Bram Moolenaarb0992022020-01-30 14:55:42 +0100421 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF))
422 || (argvar != NULL
423 && argvar->v_type == VAR_LIST
424 && argvar->vval.v_list != NULL
425 && argvar->vval.v_list->lv_first == &range_list_item))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200426 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100427 emsg(_(e_invarg));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200428 return NULL;
429 }
430
Bram Moolenaarc799fe22019-05-28 23:08:19 +0200431 term = ALLOC_CLEAR_ONE(term_T);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200432 if (term == NULL)
433 return NULL;
434 term->tl_dirty_row_end = MAX_ROW;
435 term->tl_cursor_visible = TRUE;
436 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
437 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100438#ifdef FEAT_GUI
439 term->tl_system = (flags & TERM_START_SYSTEM);
440#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200441 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100442 ga_init2(&term->tl_scrollback_postponed, sizeof(sb_line_T), 300);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200443
444 vim_memset(&split_ea, 0, sizeof(split_ea));
445 if (opt->jo_curwin)
446 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100447 // Create a new buffer in the current window.
Bram Moolenaar13568252018-03-16 20:46:58 +0100448 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200449 {
450 no_write_message();
451 vim_free(term);
452 return NULL;
453 }
454 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaar13568252018-03-16 20:46:58 +0100455 ECMD_HIDE
456 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
457 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200458 {
459 vim_free(term);
460 return NULL;
461 }
462 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100463 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200464 {
465 buf_T *buf;
466
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100467 // Create a new buffer without a window. Make it the current buffer for
468 // a moment to be able to do the initialisations.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200469 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
470 BLN_NEW | BLN_LISTED);
471 if (buf == NULL || ml_open(buf) == FAIL)
472 {
473 vim_free(term);
474 return NULL;
475 }
476 old_curbuf = curbuf;
477 --curbuf->b_nwindows;
478 curbuf = buf;
479 curwin->w_buffer = buf;
480 ++curbuf->b_nwindows;
481 }
482 else
483 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100484 // Open a new window or tab.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200485 split_ea.cmdidx = CMD_new;
486 split_ea.cmd = (char_u *)"new";
487 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100488 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200489 {
490 split_ea.line2 = opt->jo_term_rows;
491 split_ea.addr_count = 1;
492 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100493 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200494 {
495 split_ea.line2 = opt->jo_term_cols;
496 split_ea.addr_count = 1;
497 }
498
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100499 if (vertical)
500 cmdmod.split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200501 ex_splitview(&split_ea);
502 if (curwin == old_curwin)
503 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100504 // split failed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200505 vim_free(term);
506 return NULL;
507 }
508 }
509 term->tl_buffer = curbuf;
510 curbuf->b_term = term;
511
512 if (!opt->jo_hidden)
513 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100514 // Only one size was taken care of with :new, do the other one. With
515 // "curwin" both need to be done.
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100516 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200517 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100518 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200519 win_setwidth(opt->jo_term_cols);
520 }
521
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100522 // Link the new terminal in the list of active terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200523 term->tl_next = first_term;
524 first_term = term;
525
Bram Moolenaar5e94a292020-03-19 18:46:57 +0100526 apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf);
527
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200528 if (opt->jo_term_name != NULL)
529 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaar13568252018-03-16 20:46:58 +0100530 else if (argv != NULL)
531 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200532 else
533 {
534 int i;
535 size_t len;
536 char_u *cmd, *p;
537
538 if (argvar->v_type == VAR_STRING)
539 {
540 cmd = argvar->vval.v_string;
541 if (cmd == NULL)
542 cmd = (char_u *)"";
543 else if (STRCMP(cmd, "NONE") == 0)
544 cmd = (char_u *)"pty";
545 }
546 else if (argvar->v_type != VAR_LIST
547 || argvar->vval.v_list == NULL
Bram Moolenaarb0992022020-01-30 14:55:42 +0100548 || argvar->vval.v_list->lv_len == 0
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100549 || (cmd = tv_get_string_chk(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200550 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
551 cmd = (char_u*)"";
552
553 len = STRLEN(cmd) + 10;
Bram Moolenaar51e14382019-05-25 20:21:28 +0200554 p = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200555
556 for (i = 0; p != NULL; ++i)
557 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100558 // Prepend a ! to the command name to avoid the buffer name equals
559 // the executable, otherwise ":w!" would overwrite it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200560 if (i == 0)
561 vim_snprintf((char *)p, len, "!%s", cmd);
562 else
563 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
564 if (buflist_findname(p) == NULL)
565 {
566 vim_free(curbuf->b_ffname);
567 curbuf->b_ffname = p;
568 break;
569 }
570 }
571 }
Bram Moolenaare010c722020-02-24 21:37:54 +0100572 vim_free(curbuf->b_sfname);
573 curbuf->b_sfname = vim_strsave(curbuf->b_ffname);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200574 curbuf->b_fname = curbuf->b_ffname;
575
Bram Moolenaar5e94a292020-03-19 18:46:57 +0100576 apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf);
577
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200578 if (opt->jo_term_opencmd != NULL)
579 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
580
581 if (opt->jo_eof_chars != NULL)
582 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
583
584 set_string_option_direct((char_u *)"buftype", -1,
585 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar7da1fb52018-08-04 16:54:11 +0200586 // Avoid that 'buftype' is reset when this buffer is entered.
587 curbuf->b_p_initialized = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200588
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100589 // Mark the buffer as not modifiable. It can only be made modifiable after
590 // the job finished.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200591 curbuf->b_p_ma = FALSE;
592
593 set_term_and_win_size(term);
Bram Moolenaar4f974752019-02-17 17:44:42 +0100594#ifdef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200595 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
596#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200597 setup_job_options(opt, term->tl_rows, term->tl_cols);
598
Bram Moolenaar13568252018-03-16 20:46:58 +0100599 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100600 return curbuf;
601
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100602#if defined(FEAT_SESSION)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100603 // Remember the command for the session file.
Bram Moolenaar13568252018-03-16 20:46:58 +0100604 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100605 term->tl_command = vim_strsave((char_u *)"NONE");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100606 else if (argvar->v_type == VAR_STRING)
607 {
608 char_u *cmd = argvar->vval.v_string;
609
610 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
611 term->tl_command = vim_strsave(cmd);
612 }
613 else if (argvar->v_type == VAR_LIST
614 && argvar->vval.v_list != NULL
615 && argvar->vval.v_list->lv_len > 0)
616 {
617 garray_T ga;
618 listitem_T *item;
619
620 ga_init2(&ga, 1, 100);
621 for (item = argvar->vval.v_list->lv_first;
622 item != NULL; item = item->li_next)
623 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100624 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100625 char_u *p;
626
627 if (s == NULL)
628 break;
629 p = vim_strsave_fnameescape(s, FALSE);
630 if (p == NULL)
631 break;
632 ga_concat(&ga, p);
633 vim_free(p);
634 ga_append(&ga, ' ');
635 }
636 if (item == NULL)
637 {
638 ga_append(&ga, NUL);
639 term->tl_command = ga.ga_data;
640 }
641 else
642 ga_clear(&ga);
643 }
644#endif
645
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100646 if (opt->jo_term_kill != NULL)
647 {
648 char_u *p = skiptowhite(opt->jo_term_kill);
649
650 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
651 }
652
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200653 if (opt->jo_term_api != NULL)
Bram Moolenaar21109272020-01-30 16:27:20 +0100654 {
655 char_u *p = skiptowhite(opt->jo_term_api);
656
657 term->tl_api = vim_strnsave(opt->jo_term_api, p - opt->jo_term_api);
658 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200659 else
660 term->tl_api = vim_strsave((char_u *)"Tapi_");
661
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100662 // System dependent: setup the vterm and maybe start the job in it.
Bram Moolenaar13568252018-03-16 20:46:58 +0100663 if (argv == NULL
664 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200665 && argvar->vval.v_string != NULL
666 && STRCMP(argvar->vval.v_string, "NONE") == 0)
667 res = create_pty_only(term, opt);
668 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200669 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200670
671 newbuf = curbuf;
672 if (res == OK)
673 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100674 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200675 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
676 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100677#ifdef FEAT_GUI
678 if (term->tl_system)
679 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100680 // display first line below typed command
Bram Moolenaar13568252018-03-16 20:46:58 +0100681 term->tl_toprow = msg_row + 1;
682 term->tl_dirty_row_end = 0;
683 }
684#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200685
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100686 // Make sure we don't get stuck on sending keys to the job, it leads to
687 // a deadlock if the job is waiting for Vim to read.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200688 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
689
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200690 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200691 {
692 --curbuf->b_nwindows;
693 curbuf = old_curbuf;
694 curwin->w_buffer = curbuf;
695 ++curbuf->b_nwindows;
696 }
697 }
698 else
699 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100700 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200701 return NULL;
702 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100703
Bram Moolenaar13568252018-03-16 20:46:58 +0100704 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar28ed4df2019-10-26 16:21:40 +0200705 if (!opt->jo_hidden && !(flags & TERM_START_SYSTEM))
706 apply_autocmds(EVENT_TERMINALWINOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200707 return newbuf;
708}
709
710/*
711 * ":terminal": open a terminal window and execute a job in it.
712 */
713 void
714ex_terminal(exarg_T *eap)
715{
716 typval_T argvar[2];
717 jobopt_T opt;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100718 int opt_shell = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200719 char_u *cmd;
720 char_u *tofree = NULL;
721
722 init_job_options(&opt);
723
724 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100725 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200726 {
727 char_u *p, *ep;
728
729 cmd += 2;
730 p = skiptowhite(cmd);
731 ep = vim_strchr(cmd, '=');
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200732 if (ep != NULL)
733 {
734 if (ep < p)
735 p = ep;
736 else
737 ep = NULL;
738 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200739
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200740# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
741 && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
742 if (OPTARG_HAS("close"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200743 opt.jo_term_finish = 'c';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200744 else if (OPTARG_HAS("noclose"))
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100745 opt.jo_term_finish = 'n';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200746 else if (OPTARG_HAS("open"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200747 opt.jo_term_finish = 'o';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200748 else if (OPTARG_HAS("curwin"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200749 opt.jo_curwin = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200750 else if (OPTARG_HAS("hidden"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200751 opt.jo_hidden = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200752 else if (OPTARG_HAS("norestore"))
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100753 opt.jo_term_norestore = 1;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100754 else if (OPTARG_HAS("shell"))
755 opt_shell = TRUE;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200756 else if (OPTARG_HAS("kill") && ep != NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100757 {
758 opt.jo_set2 |= JO2_TERM_KILL;
759 opt.jo_term_kill = ep + 1;
760 p = skiptowhite(cmd);
761 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200762 else if (OPTARG_HAS("api"))
763 {
764 opt.jo_set2 |= JO2_TERM_API;
765 if (ep != NULL)
766 {
767 opt.jo_term_api = ep + 1;
768 p = skiptowhite(cmd);
769 }
770 else
771 opt.jo_term_api = NULL;
772 }
773 else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200774 {
775 opt.jo_set2 |= JO2_TERM_ROWS;
776 opt.jo_term_rows = atoi((char *)ep + 1);
777 p = skiptowhite(cmd);
778 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200779 else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200780 {
781 opt.jo_set2 |= JO2_TERM_COLS;
782 opt.jo_term_cols = atoi((char *)ep + 1);
783 p = skiptowhite(cmd);
784 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200785 else if (OPTARG_HAS("eof") && ep != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200786 {
787 char_u *buf = NULL;
788 char_u *keys;
789
Bram Moolenaar21109272020-01-30 16:27:20 +0100790 vim_free(opt.jo_eof_chars);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200791 p = skiptowhite(cmd);
792 *p = NUL;
Bram Moolenaar459fd782019-10-13 16:43:39 +0200793 keys = replace_termcodes(ep + 1, &buf,
794 REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200795 opt.jo_set2 |= JO2_EOF_CHARS;
796 opt.jo_eof_chars = vim_strsave(keys);
797 vim_free(buf);
798 *p = ' ';
799 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100800#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100801 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "type", 4) == 0
802 && ep != NULL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100803 {
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100804 int tty_type = NUL;
805
806 p = skiptowhite(cmd);
807 if (STRNICMP(ep + 1, "winpty", p - (ep + 1)) == 0)
808 tty_type = 'w';
809 else if (STRNICMP(ep + 1, "conpty", p - (ep + 1)) == 0)
810 tty_type = 'c';
811 else
812 {
813 semsg(e_invargval, "type");
814 goto theend;
815 }
816 opt.jo_set2 |= JO2_TTY_TYPE;
817 opt.jo_tty_type = tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100818 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100819#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200820 else
821 {
822 if (*p)
823 *p = NUL;
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100824 semsg(_("E181: Invalid attribute: %s"), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100825 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200826 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200827# undef OPTARG_HAS
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200828 cmd = skipwhite(p);
829 }
830 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100831 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100832 // Make a copy of 'shell', an autocommand may change the option.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200833 tofree = cmd = vim_strsave(p_sh);
834
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100835 // default to close when the shell exits
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100836 if (opt.jo_term_finish == NUL)
837 opt.jo_term_finish = 'c';
838 }
839
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200840 if (eap->addr_count > 0)
841 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100842 // Write lines from current buffer to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200843 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
844 opt.jo_io[PART_IN] = JIO_BUFFER;
845 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
846 opt.jo_in_top = eap->line1;
847 opt.jo_in_bot = eap->line2;
848 }
849
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100850 if (opt_shell && tofree == NULL)
851 {
852#ifdef UNIX
853 char **argv = NULL;
854 char_u *tofree1 = NULL;
855 char_u *tofree2 = NULL;
856
857 // :term ++shell command
858 if (unix_build_argv(cmd, &argv, &tofree1, &tofree2) == OK)
859 term_start(NULL, argv, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaaradf4aa22019-11-10 22:36:44 +0100860 vim_free(argv);
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100861 vim_free(tofree1);
862 vim_free(tofree2);
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100863 goto theend;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100864#else
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100865# ifdef MSWIN
866 long_u cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
867 char_u *newcmd;
868
869 newcmd = alloc(cmdlen);
870 if (newcmd == NULL)
871 goto theend;
872 tofree = newcmd;
873 vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd);
874 cmd = newcmd;
875# else
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100876 emsg(_("E279: Sorry, ++shell is not supported on this system"));
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100877 goto theend;
878# endif
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100879#endif
880 }
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100881 argvar[0].v_type = VAR_STRING;
882 argvar[0].vval.v_string = cmd;
883 argvar[1].v_type = VAR_UNKNOWN;
884 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100885
886theend:
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100887 vim_free(tofree);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200888 vim_free(opt.jo_eof_chars);
889}
890
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100891#if defined(FEAT_SESSION) || defined(PROTO)
892/*
893 * Write a :terminal command to the session file to restore the terminal in
894 * window "wp".
895 * Return FAIL if writing fails.
896 */
897 int
898term_write_session(FILE *fd, win_T *wp)
899{
900 term_T *term = wp->w_buffer->b_term;
901
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100902 // Create the terminal and run the command. This is not without
903 // risk, but let's assume the user only creates a session when this
904 // will be OK.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100905 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
906 term->tl_cols, term->tl_rows) < 0)
907 return FAIL;
Bram Moolenaar4f974752019-02-17 17:44:42 +0100908#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100909 if (fprintf(fd, "++type=%s ", term->tl_job->jv_tty_type) < 0)
910 return FAIL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100911#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100912 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
913 return FAIL;
914
915 return put_eol(fd);
916}
917
918/*
919 * Return TRUE if "buf" has a terminal that should be restored.
920 */
921 int
922term_should_restore(buf_T *buf)
923{
924 term_T *term = buf->b_term;
925
926 return term != NULL && (term->tl_command == NULL
927 || STRCMP(term->tl_command, "NONE") != 0);
928}
929#endif
930
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200931/*
932 * Free the scrollback buffer for "term".
933 */
934 static void
935free_scrollback(term_T *term)
936{
937 int i;
938
939 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
940 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
941 ga_clear(&term->tl_scrollback);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100942 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
943 vim_free(((sb_line_T *)term->tl_scrollback_postponed.ga_data + i)->sb_cells);
944 ga_clear(&term->tl_scrollback_postponed);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200945}
946
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100947
948// Terminals that need to be freed soon.
Bram Moolenaar840d16f2019-09-10 21:27:18 +0200949static term_T *terminals_to_free = NULL;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100950
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200951/*
952 * Free a terminal and everything it refers to.
953 * Kills the job if there is one.
954 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100955 * The actual terminal structure is freed later in free_unused_terminals(),
956 * because callbacks may wipe out a buffer while the terminal is still
957 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200958 */
959 void
960free_terminal(buf_T *buf)
961{
962 term_T *term = buf->b_term;
963 term_T *tp;
964
965 if (term == NULL)
966 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100967
968 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200969 if (first_term == term)
970 first_term = term->tl_next;
971 else
972 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
973 if (tp->tl_next == term)
974 {
975 tp->tl_next = term->tl_next;
976 break;
977 }
978
979 if (term->tl_job != NULL)
980 {
981 if (term->tl_job->jv_status != JOB_ENDED
982 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +0100983 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200984 job_stop(term->tl_job, NULL, "kill");
985 job_unref(term->tl_job);
986 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100987 term->tl_next = terminals_to_free;
988 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200989
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200990 buf->b_term = NULL;
991 if (in_terminal_loop == term)
992 in_terminal_loop = NULL;
993}
994
Bram Moolenaar2a4857a2019-01-29 22:29:07 +0100995 void
996free_unused_terminals()
997{
998 while (terminals_to_free != NULL)
999 {
1000 term_T *term = terminals_to_free;
1001
1002 terminals_to_free = term->tl_next;
1003
1004 free_scrollback(term);
1005
1006 term_free_vterm(term);
Bram Moolenaard2842ea2019-09-26 23:08:54 +02001007 vim_free(term->tl_api);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001008 vim_free(term->tl_title);
1009#ifdef FEAT_SESSION
1010 vim_free(term->tl_command);
1011#endif
1012 vim_free(term->tl_kill);
1013 vim_free(term->tl_status_text);
1014 vim_free(term->tl_opencmd);
1015 vim_free(term->tl_eof_chars);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001016 vim_free(term->tl_arg0_cmd);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001017#ifdef MSWIN
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001018 if (term->tl_out_fd != NULL)
1019 fclose(term->tl_out_fd);
1020#endif
1021 vim_free(term->tl_cursor_color);
1022 vim_free(term);
1023 }
1024}
1025
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001026/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001027 * Get the part that is connected to the tty. Normally this is PART_IN, but
1028 * when writing buffer lines to the job it can be another. This makes it
1029 * possible to do "1,5term vim -".
1030 */
1031 static ch_part_T
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02001032get_tty_part(term_T *term UNUSED)
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001033{
1034#ifdef UNIX
1035 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
1036 int i;
1037
1038 for (i = 0; i < 3; ++i)
1039 {
1040 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
1041
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01001042 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001043 return parts[i];
1044 }
1045#endif
1046 return PART_IN;
1047}
1048
1049/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001050 * Write job output "msg[len]" to the vterm.
1051 */
1052 static void
1053term_write_job_output(term_T *term, char_u *msg, size_t len)
1054{
1055 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001056 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001057
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001058 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001059
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001060 // flush vterm buffer when vterm responded to control sequence
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001061 if (prevlen != vterm_output_get_buffer_current(vterm))
1062 {
1063 char buf[KEY_BUF_LEN];
1064 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
1065
1066 if (curlen > 0)
1067 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1068 (char_u *)buf, (int)curlen, NULL);
1069 }
1070
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001071 // this invokes the damage callbacks
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001072 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
1073}
1074
1075 static void
1076update_cursor(term_T *term, int redraw)
1077{
1078 if (term->tl_normal_mode)
1079 return;
Bram Moolenaar13568252018-03-16 20:46:58 +01001080#ifdef FEAT_GUI
1081 if (term->tl_system)
1082 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
1083 term->tl_cursor_pos.col);
1084 else
1085#endif
1086 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001087 if (redraw)
1088 {
1089 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
1090 cursor_on();
1091 out_flush();
1092#ifdef FEAT_GUI
1093 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001094 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001095 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001096 gui_mch_flush();
1097 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001098#endif
1099 }
1100}
1101
1102/*
1103 * Invoked when "msg" output from a job was received. Write it to the terminal
1104 * of "buffer".
1105 */
1106 void
1107write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1108{
1109 size_t len = STRLEN(msg);
1110 term_T *term = buffer->b_term;
1111
Bram Moolenaar4f974752019-02-17 17:44:42 +01001112#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001113 // Win32: Cannot redirect output of the job, intercept it here and write to
1114 // the file.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02001115 if (term->tl_out_fd != NULL)
1116 {
1117 ch_log(channel, "Writing %d bytes to output file", (int)len);
1118 fwrite(msg, len, 1, term->tl_out_fd);
1119 return;
1120 }
1121#endif
1122
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001123 if (term->tl_vterm == NULL)
1124 {
1125 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1126 return;
1127 }
1128 ch_log(channel, "writing %d bytes to terminal", (int)len);
1129 term_write_job_output(term, msg, len);
1130
Bram Moolenaar13568252018-03-16 20:46:58 +01001131#ifdef FEAT_GUI
1132 if (term->tl_system)
1133 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001134 // show system output, scrolling up the screen as needed
Bram Moolenaar13568252018-03-16 20:46:58 +01001135 update_system_term(term);
1136 update_cursor(term, TRUE);
1137 }
1138 else
1139#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001140 // In Terminal-Normal mode we are displaying the buffer, not the terminal
1141 // contents, thus no screen update is needed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001142 if (!term->tl_normal_mode)
1143 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001144 // Don't use update_screen() when editing the command line, it gets
1145 // cleared.
1146 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001147 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001148 if (buffer == curbuf && (State & CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001149 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001150 update_screen(VALID_NO_UPDATE);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001151 // update_screen() can be slow, check the terminal wasn't closed
1152 // already
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001153 if (buffer == curbuf && curbuf->b_term != NULL)
1154 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001155 }
1156 else
1157 redraw_after_callback(TRUE);
1158 }
1159}
1160
1161/*
1162 * Send a mouse position and click to the vterm
1163 */
1164 static int
1165term_send_mouse(VTerm *vterm, int button, int pressed)
1166{
1167 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01001168 int row = mouse_row - W_WINROW(curwin);
1169 int col = mouse_col - curwin->w_wincol;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001170
Bram Moolenaar219c7d02020-02-01 21:57:29 +01001171#ifdef FEAT_PROP_POPUP
1172 if (popup_is_popup(curwin))
1173 {
1174 row -= popup_top_extra(curwin);
1175 col -= popup_left_extra(curwin);
1176 }
1177#endif
1178 vterm_mouse_move(vterm, row, col, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001179 if (button != 0)
1180 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001181 return TRUE;
1182}
1183
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001184static int enter_mouse_col = -1;
1185static int enter_mouse_row = -1;
1186
1187/*
1188 * Handle a mouse click, drag or release.
1189 * Return TRUE when a mouse event is sent to the terminal.
1190 */
1191 static int
1192term_mouse_click(VTerm *vterm, int key)
1193{
1194#if defined(FEAT_CLIPBOARD)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001195 // For modeless selection mouse drag and release events are ignored, unless
1196 // they are preceded with a mouse down event
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001197 static int ignore_drag_release = TRUE;
1198 VTermMouseState mouse_state;
1199
1200 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1201 if (mouse_state.flags == 0)
1202 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001203 // Terminal is not using the mouse, use modeless selection.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001204 switch (key)
1205 {
1206 case K_LEFTDRAG:
1207 case K_LEFTRELEASE:
1208 case K_RIGHTDRAG:
1209 case K_RIGHTRELEASE:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001210 // Ignore drag and release events when the button-down wasn't
1211 // seen before.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001212 if (ignore_drag_release)
1213 {
1214 int save_mouse_col, save_mouse_row;
1215
1216 if (enter_mouse_col < 0)
1217 break;
1218
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001219 // mouse click in the window gave us focus, handle that
1220 // click now
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001221 save_mouse_col = mouse_col;
1222 save_mouse_row = mouse_row;
1223 mouse_col = enter_mouse_col;
1224 mouse_row = enter_mouse_row;
1225 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1226 mouse_col = save_mouse_col;
1227 mouse_row = save_mouse_row;
1228 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001229 // FALLTHROUGH
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001230 case K_LEFTMOUSE:
1231 case K_RIGHTMOUSE:
1232 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1233 ignore_drag_release = TRUE;
1234 else
1235 ignore_drag_release = FALSE;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001236 // Should we call mouse_has() here?
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001237 if (clip_star.available)
1238 {
1239 int button, is_click, is_drag;
1240
1241 button = get_mouse_button(KEY2TERMCAP1(key),
1242 &is_click, &is_drag);
1243 if (mouse_model_popup() && button == MOUSE_LEFT
1244 && (mod_mask & MOD_MASK_SHIFT))
1245 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001246 // Translate shift-left to right button.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001247 button = MOUSE_RIGHT;
1248 mod_mask &= ~MOD_MASK_SHIFT;
1249 }
1250 clip_modeless(button, is_click, is_drag);
1251 }
1252 break;
1253
1254 case K_MIDDLEMOUSE:
1255 if (clip_star.available)
1256 insert_reg('*', TRUE);
1257 break;
1258 }
1259 enter_mouse_col = -1;
1260 return FALSE;
1261 }
1262#endif
1263 enter_mouse_col = -1;
1264
1265 switch (key)
1266 {
1267 case K_LEFTMOUSE:
1268 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1269 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1270 case K_LEFTRELEASE:
1271 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1272 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1273 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1274 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1275 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1276 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1277 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1278 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1279 }
1280 return TRUE;
1281}
1282
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001283/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001284 * Convert typed key "c" with modifiers "modmask" into bytes to send to the
1285 * job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001286 * Return the number of bytes in "buf".
1287 */
1288 static int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001289term_convert_key(term_T *term, int c, int modmask, char *buf)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001290{
1291 VTerm *vterm = term->tl_vterm;
1292 VTermKey key = VTERM_KEY_NONE;
1293 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001294 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001295
1296 switch (c)
1297 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001298 // don't use VTERM_KEY_ENTER, it may do an unwanted conversion
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001299
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001300 // don't use VTERM_KEY_BACKSPACE, it always
1301 // becomes 0x7f DEL
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001302 case K_BS: c = term_backspace_char; break;
1303
1304 case ESC: key = VTERM_KEY_ESCAPE; break;
1305 case K_DEL: key = VTERM_KEY_DEL; break;
1306 case K_DOWN: key = VTERM_KEY_DOWN; break;
1307 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1308 key = VTERM_KEY_DOWN; break;
1309 case K_END: key = VTERM_KEY_END; break;
1310 case K_S_END: mod = VTERM_MOD_SHIFT;
1311 key = VTERM_KEY_END; break;
1312 case K_C_END: mod = VTERM_MOD_CTRL;
1313 key = VTERM_KEY_END; break;
1314 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1315 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1316 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1317 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1318 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1319 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1320 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1321 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1322 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1323 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1324 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1325 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1326 case K_HOME: key = VTERM_KEY_HOME; break;
1327 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1328 key = VTERM_KEY_HOME; break;
1329 case K_C_HOME: mod = VTERM_MOD_CTRL;
1330 key = VTERM_KEY_HOME; break;
1331 case K_INS: key = VTERM_KEY_INS; break;
1332 case K_K0: key = VTERM_KEY_KP_0; break;
1333 case K_K1: key = VTERM_KEY_KP_1; break;
1334 case K_K2: key = VTERM_KEY_KP_2; break;
1335 case K_K3: key = VTERM_KEY_KP_3; break;
1336 case K_K4: key = VTERM_KEY_KP_4; break;
1337 case K_K5: key = VTERM_KEY_KP_5; break;
1338 case K_K6: key = VTERM_KEY_KP_6; break;
1339 case K_K7: key = VTERM_KEY_KP_7; break;
1340 case K_K8: key = VTERM_KEY_KP_8; break;
1341 case K_K9: key = VTERM_KEY_KP_9; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001342 case K_KDEL: key = VTERM_KEY_DEL; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001343 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001344 case K_KEND: key = VTERM_KEY_KP_1; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001345 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001346 case K_KHOME: key = VTERM_KEY_KP_7; break; // TODO
1347 case K_KINS: key = VTERM_KEY_KP_0; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001348 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1349 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001350 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; // TODO
1351 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001352 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1353 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1354 case K_LEFT: key = VTERM_KEY_LEFT; break;
1355 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1356 key = VTERM_KEY_LEFT; break;
1357 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1358 key = VTERM_KEY_LEFT; break;
1359 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1360 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1361 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1362 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1363 key = VTERM_KEY_RIGHT; break;
1364 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1365 key = VTERM_KEY_RIGHT; break;
1366 case K_UP: key = VTERM_KEY_UP; break;
1367 case K_S_UP: mod = VTERM_MOD_SHIFT;
1368 key = VTERM_KEY_UP; break;
1369 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001370 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1371 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001372
Bram Moolenaara42ad572017-11-16 13:08:04 +01001373 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1374 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001375 case K_MOUSELEFT: /* TODO */ return 0;
1376 case K_MOUSERIGHT: /* TODO */ return 0;
1377
1378 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001379 case K_LEFTMOUSE_NM:
1380 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001381 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001382 case K_LEFTRELEASE_NM:
1383 case K_MOUSEMOVE:
1384 case K_MIDDLEMOUSE:
1385 case K_MIDDLEDRAG:
1386 case K_MIDDLERELEASE:
1387 case K_RIGHTMOUSE:
1388 case K_RIGHTDRAG:
1389 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1390 return 0;
1391 other = TRUE;
1392 break;
1393
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001394 case K_X1MOUSE: /* TODO */ return 0;
1395 case K_X1DRAG: /* TODO */ return 0;
1396 case K_X1RELEASE: /* TODO */ return 0;
1397 case K_X2MOUSE: /* TODO */ return 0;
1398 case K_X2DRAG: /* TODO */ return 0;
1399 case K_X2RELEASE: /* TODO */ return 0;
1400
1401 case K_IGNORE: return 0;
1402 case K_NOP: return 0;
1403 case K_UNDO: return 0;
1404 case K_HELP: return 0;
1405 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1406 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1407 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1408 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1409 case K_SELECT: return 0;
1410#ifdef FEAT_GUI
1411 case K_VER_SCROLLBAR: return 0;
1412 case K_HOR_SCROLLBAR: return 0;
1413#endif
1414#ifdef FEAT_GUI_TABLINE
1415 case K_TABLINE: return 0;
1416 case K_TABMENU: return 0;
1417#endif
1418#ifdef FEAT_NETBEANS_INTG
1419 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1420#endif
1421#ifdef FEAT_DND
1422 case K_DROP: return 0;
1423#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001424 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001425 case K_PS: vterm_keyboard_start_paste(vterm);
1426 other = TRUE;
1427 break;
1428 case K_PE: vterm_keyboard_end_paste(vterm);
1429 other = TRUE;
1430 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001431 }
1432
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001433 // add modifiers for the typed key
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001434 if (modmask & MOD_MASK_SHIFT)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001435 mod |= VTERM_MOD_SHIFT;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001436 if (modmask & MOD_MASK_CTRL)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001437 mod |= VTERM_MOD_CTRL;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001438 if (modmask & (MOD_MASK_ALT | MOD_MASK_META))
Bram Moolenaar459fd782019-10-13 16:43:39 +02001439 mod |= VTERM_MOD_ALT;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001440
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001441 /*
1442 * Convert special keys to vterm keys:
1443 * - Write keys to vterm: vterm_keyboard_key()
1444 * - Write output to channel.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001445 */
1446 if (key != VTERM_KEY_NONE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001447 // Special key, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001448 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001449 else if (!other)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001450 // Normal character, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001451 vterm_keyboard_unichar(vterm, c, mod);
1452
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001453 // Read back the converted escape sequence.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001454 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1455}
1456
1457/*
1458 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001459 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001460 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001461 */
1462 static int
1463term_job_running_check(term_T *term, int check_job_status)
1464{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001465 // Also consider the job finished when the channel is closed, to avoid a
1466 // race condition when updating the title.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001467 if (term != NULL
1468 && term->tl_job != NULL
1469 && channel_is_open(term->tl_job->jv_channel))
1470 {
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001471 job_T *job = term->tl_job;
1472
1473 // Careful: Checking the job status may invoked callbacks, which close
1474 // the buffer and terminate "term". However, "job" will not be freed
1475 // yet.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001476 if (check_job_status)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001477 job_status(job);
1478 return (job->jv_status == JOB_STARTED
1479 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001480 }
1481 return FALSE;
1482}
1483
1484/*
1485 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001486 */
1487 int
1488term_job_running(term_T *term)
1489{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001490 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001491}
1492
1493/*
1494 * Return TRUE if "term" has an active channel and used ":term NONE".
1495 */
1496 int
1497term_none_open(term_T *term)
1498{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001499 // Also consider the job finished when the channel is closed, to avoid a
1500 // race condition when updating the title.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001501 return term != NULL
1502 && term->tl_job != NULL
1503 && channel_is_open(term->tl_job->jv_channel)
1504 && term->tl_job->jv_channel->ch_keep_open;
1505}
1506
1507/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001508 * Used when exiting: kill the job in "buf" if so desired.
1509 * Return OK when the job finished.
1510 * Return FAIL when the job is still running.
1511 */
1512 int
1513term_try_stop_job(buf_T *buf)
1514{
1515 int count;
1516 char *how = (char *)buf->b_term->tl_kill;
1517
1518#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1519 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm))
1520 {
1521 char_u buff[DIALOG_MSG_SIZE];
1522 int ret;
1523
1524 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname);
1525 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
1526 if (ret == VIM_YES)
1527 how = "kill";
1528 else if (ret == VIM_CANCEL)
1529 return FAIL;
1530 }
1531#endif
1532 if (how == NULL || *how == NUL)
1533 return FAIL;
1534
1535 job_stop(buf->b_term->tl_job, NULL, how);
1536
Bram Moolenaar9172d232019-01-29 23:06:54 +01001537 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001538 for (count = 0; count < 100; ++count)
1539 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001540 job_T *job;
1541
1542 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001543 if (!buf_valid(buf)
1544 || buf->b_term == NULL
1545 || buf->b_term->tl_job == NULL)
1546 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001547 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001548
Bram Moolenaar9172d232019-01-29 23:06:54 +01001549 // Call job_status() to update jv_status. It may cause the job to be
1550 // cleaned up but it won't be freed.
1551 job_status(job);
1552 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001553 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001554
Bram Moolenaar8f7ab4b2019-10-23 23:16:45 +02001555 ui_delay(10L, TRUE);
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02001556 term_flush_messages();
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001557 }
1558 return FAIL;
1559}
1560
1561/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001562 * Add the last line of the scrollback buffer to the buffer in the window.
1563 */
1564 static void
1565add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1566{
1567 buf_T *buf = term->tl_buffer;
1568 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1569 linenr_T lnum = buf->b_ml.ml_line_count;
1570
Bram Moolenaar4f974752019-02-17 17:44:42 +01001571#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001572 if (!enc_utf8 && enc_codepage > 0)
1573 {
1574 WCHAR *ret = NULL;
1575 int length = 0;
1576
1577 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1578 &ret, &length);
1579 if (ret != NULL)
1580 {
1581 WideCharToMultiByte_alloc(enc_codepage, 0,
1582 ret, length, (char **)&text, &len, 0, 0);
1583 vim_free(ret);
1584 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1585 vim_free(text);
1586 }
1587 }
1588 else
1589#endif
1590 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1591 if (empty)
1592 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001593 // Delete the empty line that was in the empty buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001594 curbuf = buf;
1595 ml_delete(1, FALSE);
1596 curbuf = curwin->w_buffer;
1597 }
1598}
1599
1600 static void
1601cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1602{
1603 attr->width = cell->width;
1604 attr->attrs = cell->attrs;
1605 attr->fg = cell->fg;
1606 attr->bg = cell->bg;
1607}
1608
1609 static int
1610equal_celattr(cellattr_T *a, cellattr_T *b)
1611{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001612 // Comparing the colors should be sufficient.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001613 return a->fg.red == b->fg.red
1614 && a->fg.green == b->fg.green
1615 && a->fg.blue == b->fg.blue
1616 && a->bg.red == b->bg.red
1617 && a->bg.green == b->bg.green
1618 && a->bg.blue == b->bg.blue;
1619}
1620
Bram Moolenaard96ff162018-02-18 22:13:29 +01001621/*
1622 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1623 * line at this position. Otherwise at the end.
1624 */
1625 static int
1626add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1627{
1628 if (ga_grow(&term->tl_scrollback, 1) == OK)
1629 {
1630 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1631 + term->tl_scrollback.ga_len;
1632
1633 if (lnum > 0)
1634 {
1635 int i;
1636
1637 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1638 {
1639 *line = *(line - 1);
1640 --line;
1641 }
1642 }
1643 line->sb_cols = 0;
1644 line->sb_cells = NULL;
1645 line->sb_fill_attr = *fill_attr;
1646 ++term->tl_scrollback.ga_len;
1647 return OK;
1648 }
1649 return FALSE;
1650}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001651
1652/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001653 * Remove the terminal contents from the scrollback and the buffer.
1654 * Used before adding a new scrollback line or updating the buffer for lines
1655 * displayed in the terminal.
1656 */
1657 static void
1658cleanup_scrollback(term_T *term)
1659{
1660 sb_line_T *line;
1661 garray_T *gap;
1662
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001663 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001664 gap = &term->tl_scrollback;
1665 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1666 && gap->ga_len > 0)
1667 {
1668 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
1669 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1670 vim_free(line->sb_cells);
1671 --gap->ga_len;
1672 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001673 curbuf = curwin->w_buffer;
1674 if (curbuf == term->tl_buffer)
1675 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001676}
1677
1678/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001679 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001680 */
1681 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001682update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001683{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001684 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001685 int len;
1686 int lines_skipped = 0;
1687 VTermPos pos;
1688 VTermScreenCell cell;
1689 cellattr_T fill_attr, new_fill_attr;
1690 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001691
1692 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1693 "Adding terminal window snapshot to buffer");
1694
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001695 // First remove the lines that were appended before, they might be
1696 // outdated.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001697 cleanup_scrollback(term);
1698
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001699 screen = vterm_obtain_screen(term->tl_vterm);
1700 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001701 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1702 {
1703 len = 0;
1704 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1705 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1706 && cell.chars[0] != NUL)
1707 {
1708 len = pos.col + 1;
1709 new_fill_attr = term->tl_default_color;
1710 }
1711 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001712 // Assume the last attr is the filler attr.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001713 cell2cellattr(&cell, &new_fill_attr);
1714
1715 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1716 ++lines_skipped;
1717 else
1718 {
1719 while (lines_skipped > 0)
1720 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001721 // Line was skipped, add an empty line.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001722 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001723 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001724 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001725 }
1726
1727 if (len == 0)
1728 p = NULL;
1729 else
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001730 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001731 if ((p != NULL || len == 0)
1732 && ga_grow(&term->tl_scrollback, 1) == OK)
1733 {
1734 garray_T ga;
1735 int width;
1736 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1737 + term->tl_scrollback.ga_len;
1738
1739 ga_init2(&ga, 1, 100);
1740 for (pos.col = 0; pos.col < len; pos.col += width)
1741 {
1742 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1743 {
1744 width = 1;
1745 vim_memset(p + pos.col, 0, sizeof(cellattr_T));
1746 if (ga_grow(&ga, 1) == OK)
1747 ga.ga_len += utf_char2bytes(' ',
1748 (char_u *)ga.ga_data + ga.ga_len);
1749 }
1750 else
1751 {
1752 width = cell.width;
1753
1754 cell2cellattr(&cell, &p[pos.col]);
1755
Bram Moolenaara79fd562018-12-20 20:47:32 +01001756 // Each character can be up to 6 bytes.
1757 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001758 {
1759 int i;
1760 int c;
1761
1762 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1763 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1764 (char_u *)ga.ga_data + ga.ga_len);
1765 }
1766 }
1767 }
1768 line->sb_cols = len;
1769 line->sb_cells = p;
1770 line->sb_fill_attr = new_fill_attr;
1771 fill_attr = new_fill_attr;
1772 ++term->tl_scrollback.ga_len;
1773
1774 if (ga_grow(&ga, 1) == FAIL)
1775 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1776 else
1777 {
1778 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1779 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1780 }
1781 ga_clear(&ga);
1782 }
1783 else
1784 vim_free(p);
1785 }
1786 }
1787
Bram Moolenaarf3aea592018-11-11 22:18:21 +01001788 // Add trailing empty lines.
1789 for (pos.row = term->tl_scrollback.ga_len;
1790 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1791 ++pos.row)
1792 {
1793 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1794 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1795 }
1796
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001797 term->tl_dirty_snapshot = FALSE;
1798#ifdef FEAT_TIMERS
1799 term->tl_timer_set = FALSE;
1800#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001801}
1802
1803/*
Bram Moolenaare52e0c82020-02-28 22:20:10 +01001804 * Loop over all windows in the current tab, and also curwin, which is not
1805 * encountered when using a terminal in a popup window.
1806 * Return TRUE if "*wp" was set to the next window.
1807 */
1808 static int
1809for_all_windows_and_curwin(win_T **wp, int *did_curwin)
1810{
1811 if (*wp == NULL)
1812 *wp = firstwin;
1813 else if ((*wp)->w_next != NULL)
1814 *wp = (*wp)->w_next;
1815 else if (!*did_curwin)
1816 *wp = curwin;
1817 else
1818 return FALSE;
1819 if (*wp == curwin)
1820 *did_curwin = TRUE;
1821 return TRUE;
1822}
1823
1824/*
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001825 * If needed, add the current lines of the terminal to scrollback and to the
1826 * buffer. Called after the job has ended and when switching to
1827 * Terminal-Normal mode.
1828 * When "redraw" is TRUE redraw the windows that show the terminal.
1829 */
1830 static void
1831may_move_terminal_to_buffer(term_T *term, int redraw)
1832{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001833 if (term->tl_vterm == NULL)
1834 return;
1835
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001836 // Update the snapshot only if something changes or the buffer does not
1837 // have all the lines.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001838 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
1839 <= term->tl_scrollback_scrolled)
1840 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001841
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001842 // Obtain the current background color.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001843 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
1844 &term->tl_default_color.fg, &term->tl_default_color.bg);
1845
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001846 if (redraw)
Bram Moolenaare52e0c82020-02-28 22:20:10 +01001847 {
1848 win_T *wp = NULL;
1849 int did_curwin = FALSE;
1850
1851 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001852 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001853 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001854 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001855 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
1856 wp->w_cursor.col = 0;
1857 wp->w_valid = 0;
1858 if (wp->w_cursor.lnum >= wp->w_height)
1859 {
1860 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001861
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001862 if (wp->w_topline < min_topline)
1863 wp->w_topline = min_topline;
1864 }
1865 redraw_win_later(wp, NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001866 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001867 }
Bram Moolenaare52e0c82020-02-28 22:20:10 +01001868 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001869}
1870
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001871#if defined(FEAT_TIMERS) || defined(PROTO)
1872/*
1873 * Check if any terminal timer expired. If so, copy text from the terminal to
1874 * the buffer.
1875 * Return the time until the next timer will expire.
1876 */
1877 int
1878term_check_timers(int next_due_arg, proftime_T *now)
1879{
1880 term_T *term;
1881 int next_due = next_due_arg;
1882
1883 for (term = first_term; term != NULL; term = term->tl_next)
1884 {
1885 if (term->tl_timer_set && !term->tl_normal_mode)
1886 {
1887 long this_due = proftime_time_left(&term->tl_timer_due, now);
1888
1889 if (this_due <= 1)
1890 {
1891 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001892 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001893 }
1894 else if (next_due == -1 || next_due > this_due)
1895 next_due = this_due;
1896 }
1897 }
1898
1899 return next_due;
1900}
1901#endif
1902
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001903/*
1904 * When "normal_mode" is TRUE set the terminal to Terminal-Normal mode,
1905 * otherwise end it.
1906 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001907 static void
1908set_terminal_mode(term_T *term, int normal_mode)
1909{
1910 term->tl_normal_mode = normal_mode;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001911 if (!normal_mode)
1912 handle_postponed_scrollback(term);
Bram Moolenaard23a8232018-02-10 18:45:26 +01001913 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001914 if (term->tl_buffer == curbuf)
1915 maketitle();
1916}
1917
1918/*
1919 * Called after the job if finished and Terminal mode is not active:
1920 * Move the vterm contents into the scrollback buffer and free the vterm.
1921 */
1922 static void
1923cleanup_vterm(term_T *term)
1924{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001925 set_terminal_mode(term, FALSE);
Bram Moolenaar1dd98332018-03-16 22:54:53 +01001926 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001927 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001928 term_free_vterm(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001929}
1930
1931/*
1932 * Switch from Terminal-Job mode to Terminal-Normal mode.
1933 * Suspends updating the terminal window.
1934 */
1935 static void
1936term_enter_normal_mode(void)
1937{
1938 term_T *term = curbuf->b_term;
1939
Bram Moolenaar2bc79952018-05-12 20:36:24 +02001940 set_terminal_mode(term, TRUE);
1941
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001942 // Append the current terminal contents to the buffer.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001943 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001944
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001945 // Move the window cursor to the position of the cursor in the
1946 // terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001947 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
1948 + term->tl_cursor_pos.row + 1;
1949 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02001950 if (coladvance(term->tl_cursor_pos.col) == FAIL)
1951 coladvance(MAXCOL);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01001952 curwin->w_set_curswant = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001953
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001954 // Display the same lines as in the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001955 curwin->w_topline = term->tl_scrollback_scrolled + 1;
1956}
1957
1958/*
1959 * Returns TRUE if the current window contains a terminal and we are in
1960 * Terminal-Normal mode.
1961 */
1962 int
1963term_in_normal_mode(void)
1964{
1965 term_T *term = curbuf->b_term;
1966
1967 return term != NULL && term->tl_normal_mode;
1968}
1969
1970/*
1971 * Switch from Terminal-Normal mode to Terminal-Job mode.
1972 * Restores updating the terminal window.
1973 */
1974 void
1975term_enter_job_mode()
1976{
1977 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001978
1979 set_terminal_mode(term, FALSE);
1980
1981 if (term->tl_channel_closed)
1982 cleanup_vterm(term);
1983 redraw_buf_and_status_later(curbuf, NOT_VALID);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01001984#ifdef FEAT_PROP_POPUP
1985 if (WIN_IS_POPUP(curwin))
1986 redraw_win_later(curwin, NOT_VALID);
1987#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001988}
1989
1990/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01001991 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001992 * Note: while waiting a terminal may be closed and freed if the channel is
1993 * closed and ++close was used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001994 */
1995 static int
1996term_vgetc()
1997{
1998 int c;
1999 int save_State = State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002000 int modify_other_keys =
2001 vterm_is_modify_other_keys(curbuf->b_term->tl_vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002002
2003 State = TERMINAL;
2004 got_int = FALSE;
Bram Moolenaar4f974752019-02-17 17:44:42 +01002005#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002006 ctrl_break_was_pressed = FALSE;
2007#endif
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002008 if (modify_other_keys)
2009 ++no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002010 c = vgetc();
2011 got_int = FALSE;
2012 State = save_State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002013 if (modify_other_keys)
2014 --no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002015 return c;
2016}
2017
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002018static int mouse_was_outside = FALSE;
2019
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002020/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002021 * Send key "c" with modifiers "modmask" to terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002022 * Return FAIL when the key needs to be handled in Normal mode.
2023 * Return OK when the key was dropped or sent to the terminal.
2024 */
2025 int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002026send_keys_to_term(term_T *term, int c, int modmask, int typed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002027{
2028 char msg[KEY_BUF_LEN];
2029 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002030 int dragging_outside = FALSE;
2031
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002032 // Catch keys that need to be handled as in Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002033 switch (c)
2034 {
2035 case NUL:
2036 case K_ZERO:
2037 if (typed)
2038 stuffcharReadbuff(c);
2039 return FAIL;
2040
Bram Moolenaar231a2db2018-05-06 13:53:50 +02002041 case K_TABLINE:
2042 stuffcharReadbuff(c);
2043 return FAIL;
2044
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002045 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002046 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002047 return FAIL;
2048
2049 case K_LEFTDRAG:
2050 case K_MIDDLEDRAG:
2051 case K_RIGHTDRAG:
2052 case K_X1DRAG:
2053 case K_X2DRAG:
2054 dragging_outside = mouse_was_outside;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002055 // FALLTHROUGH
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002056 case K_LEFTMOUSE:
2057 case K_LEFTMOUSE_NM:
2058 case K_LEFTRELEASE:
2059 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01002060 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002061 case K_MIDDLEMOUSE:
2062 case K_MIDDLERELEASE:
2063 case K_RIGHTMOUSE:
2064 case K_RIGHTRELEASE:
2065 case K_X1MOUSE:
2066 case K_X1RELEASE:
2067 case K_X2MOUSE:
2068 case K_X2RELEASE:
2069
2070 case K_MOUSEUP:
2071 case K_MOUSEDOWN:
2072 case K_MOUSELEFT:
2073 case K_MOUSERIGHT:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002074 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002075 int row = mouse_row;
2076 int col = mouse_col;
2077
2078#ifdef FEAT_PROP_POPUP
2079 if (popup_is_popup(curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002080 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002081 row -= popup_top_extra(curwin);
2082 col -= popup_left_extra(curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002083 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002084#endif
2085 if (row < W_WINROW(curwin)
2086 || row >= (W_WINROW(curwin) + curwin->w_height)
2087 || col < curwin->w_wincol
2088 || col >= W_ENDCOL(curwin)
2089 || dragging_outside)
2090 {
2091 // click or scroll outside the current window or on status
2092 // line or vertical separator
2093 if (typed)
2094 {
2095 stuffcharReadbuff(c);
2096 mouse_was_outside = TRUE;
2097 }
2098 return FAIL;
2099 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002100 }
2101 }
2102 if (typed)
2103 mouse_was_outside = FALSE;
2104
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002105 // Convert the typed key to a sequence of bytes for the job.
2106 len = term_convert_key(term, c, modmask, msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002107 if (len > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002108 // TODO: if FAIL is returned, stop?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002109 channel_send(term->tl_job->jv_channel, get_tty_part(term),
2110 (char_u *)msg, (int)len, NULL);
2111
2112 return OK;
2113}
2114
2115 static void
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002116position_cursor(win_T *wp, VTermPos *pos, int add_off UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002117{
2118 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
2119 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002120#ifdef FEAT_PROP_POPUP
2121 if (add_off && popup_is_popup(curwin))
2122 {
2123 wp->w_wrow += popup_top_extra(curwin);
2124 wp->w_wcol += popup_left_extra(curwin);
2125 }
2126#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002127 wp->w_valid |= (VALID_WCOL|VALID_WROW);
2128}
2129
2130/*
2131 * Handle CTRL-W "": send register contents to the job.
2132 */
2133 static void
2134term_paste_register(int prev_c UNUSED)
2135{
2136 int c;
2137 list_T *l;
2138 listitem_T *item;
2139 long reglen = 0;
2140 int type;
2141
2142#ifdef FEAT_CMDL_INFO
2143 if (add_to_showcmd(prev_c))
2144 if (add_to_showcmd('"'))
2145 out_flush();
2146#endif
2147 c = term_vgetc();
2148#ifdef FEAT_CMDL_INFO
2149 clear_showcmd();
2150#endif
2151 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002152 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002153 return;
2154
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002155 // CTRL-W "= prompt for expression to evaluate.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002156 if (c == '=' && get_expr_register() != '=')
2157 return;
2158 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002159 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002160 return;
2161
2162 l = (list_T *)get_reg_contents(c, GREG_LIST);
2163 if (l != NULL)
2164 {
2165 type = get_reg_type(c, &reglen);
2166 for (item = l->lv_first; item != NULL; item = item->li_next)
2167 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01002168 char_u *s = tv_get_string(&item->li_tv);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002169#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002170 char_u *tmp = s;
2171
2172 if (!enc_utf8 && enc_codepage > 0)
2173 {
2174 WCHAR *ret = NULL;
2175 int length = 0;
2176
2177 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2178 (int)STRLEN(s), &ret, &length);
2179 if (ret != NULL)
2180 {
2181 WideCharToMultiByte_alloc(CP_UTF8, 0,
2182 ret, length, (char **)&s, &length, 0, 0);
2183 vim_free(ret);
2184 }
2185 }
2186#endif
2187 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2188 s, (int)STRLEN(s), NULL);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002189#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002190 if (tmp != s)
2191 vim_free(s);
2192#endif
2193
2194 if (item->li_next != NULL || type == MLINE)
2195 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2196 (char_u *)"\r", 1, NULL);
2197 }
2198 list_free(l);
2199 }
2200}
2201
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002202/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002203 * Return TRUE when waiting for a character in the terminal, the cursor of the
2204 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002205 */
2206 int
2207terminal_is_active()
2208{
2209 return in_terminal_loop != NULL;
2210}
2211
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002212#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002213 cursorentry_T *
2214term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2215{
2216 term_T *term = in_terminal_loop;
2217 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002218 int id;
2219 guicolor_T term_fg, term_bg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002220
2221 vim_memset(&entry, 0, sizeof(entry));
2222 entry.shape = entry.mshape =
2223 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2224 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2225 SHAPE_BLOCK;
2226 entry.percentage = 20;
2227 if (term->tl_cursor_blink)
2228 {
2229 entry.blinkwait = 700;
2230 entry.blinkon = 400;
2231 entry.blinkoff = 250;
2232 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002233
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002234 // The "Terminal" highlight group overrules the defaults.
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002235 id = syn_name2id((char_u *)"Terminal");
2236 if (id != 0)
2237 {
2238 syn_id2colors(id, &term_fg, &term_bg);
2239 *fg = term_bg;
2240 }
2241 else
2242 *fg = gui.back_pixel;
2243
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002244 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002245 {
2246 if (id != 0)
2247 *bg = term_fg;
2248 else
2249 *bg = gui.norm_pixel;
2250 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002251 else
2252 *bg = color_name2handle(term->tl_cursor_color);
2253 entry.name = "n";
2254 entry.used_for = SHAPE_CURSOR;
2255
2256 return &entry;
2257}
2258#endif
2259
Bram Moolenaard317b382018-02-08 22:33:31 +01002260 static void
2261may_output_cursor_props(void)
2262{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002263 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
Bram Moolenaard317b382018-02-08 22:33:31 +01002264 || last_set_cursor_shape != desired_cursor_shape
2265 || last_set_cursor_blink != desired_cursor_blink)
2266 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002267 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002268 last_set_cursor_shape = desired_cursor_shape;
2269 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002270 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002271 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002272 // this will restore the initial cursor style, if possible
Bram Moolenaard317b382018-02-08 22:33:31 +01002273 ui_cursor_shape_forced(TRUE);
2274 else
2275 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2276 }
2277}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002278
Bram Moolenaard317b382018-02-08 22:33:31 +01002279/*
2280 * Set the cursor color and shape, if not last set to these.
2281 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002282 static void
2283may_set_cursor_props(term_T *term)
2284{
2285#ifdef FEAT_GUI
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002286 // For the GUI the cursor properties are obtained with
2287 // term_get_cursor_shape().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002288 if (gui.in_use)
2289 return;
2290#endif
2291 if (in_terminal_loop == term)
2292 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002293 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002294 desired_cursor_shape = term->tl_cursor_shape;
2295 desired_cursor_blink = term->tl_cursor_blink;
2296 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002297 }
2298}
2299
Bram Moolenaard317b382018-02-08 22:33:31 +01002300/*
2301 * Reset the desired cursor properties and restore them when needed.
2302 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002303 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002304prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002305{
2306#ifdef FEAT_GUI
2307 if (gui.in_use)
2308 return;
2309#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002310 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002311 desired_cursor_shape = -1;
2312 desired_cursor_blink = -1;
2313 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002314}
2315
2316/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002317 * Returns TRUE if the current window contains a terminal and we are sending
2318 * keys to the job.
2319 * If "check_job_status" is TRUE update the job status.
2320 */
2321 static int
2322term_use_loop_check(int check_job_status)
2323{
2324 term_T *term = curbuf->b_term;
2325
2326 return term != NULL
2327 && !term->tl_normal_mode
2328 && term->tl_vterm != NULL
2329 && term_job_running_check(term, check_job_status);
2330}
2331
2332/*
2333 * Returns TRUE if the current window contains a terminal and we are sending
2334 * keys to the job.
2335 */
2336 int
2337term_use_loop(void)
2338{
2339 return term_use_loop_check(FALSE);
2340}
2341
2342/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002343 * Called when entering a window with the mouse. If this is a terminal window
2344 * we may want to change state.
2345 */
2346 void
2347term_win_entered()
2348{
2349 term_T *term = curbuf->b_term;
2350
2351 if (term != NULL)
2352 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002353 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002354 {
2355 reset_VIsual_and_resel();
2356 if (State & INSERT)
2357 stop_insert_mode = TRUE;
2358 }
2359 mouse_was_outside = FALSE;
2360 enter_mouse_col = mouse_col;
2361 enter_mouse_row = mouse_row;
2362 }
2363}
2364
2365/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002366 * vgetc() may not include CTRL in the key when modify_other_keys is set.
2367 * Return the Ctrl-key value in that case.
2368 */
2369 static int
2370raw_c_to_ctrl(int c)
2371{
2372 if ((mod_mask & MOD_MASK_CTRL)
2373 && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')))
2374 return c & 0x1f;
2375 return c;
2376}
2377
2378/*
2379 * When modify_other_keys is set then do the reverse of raw_c_to_ctrl().
2380 * May set "mod_mask".
2381 */
2382 static int
2383ctrl_to_raw_c(int c)
2384{
2385 if (c < 0x20 && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
2386 {
2387 mod_mask |= MOD_MASK_CTRL;
2388 return c + '@';
2389 }
2390 return c;
2391}
2392
2393/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002394 * Wait for input and send it to the job.
2395 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2396 * when there is no more typahead.
2397 * Return when the start of a CTRL-W command is typed or anything else that
2398 * should be handled as a Normal mode command.
2399 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2400 * the terminal was closed.
2401 */
2402 int
2403terminal_loop(int blocking)
2404{
2405 int c;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002406 int raw_c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002407 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002408 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002409#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002410 int tty_fd = curbuf->b_term->tl_job->jv_channel
2411 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002412#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002413 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002414
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002415 // Remember the terminal we are sending keys to. However, the terminal
2416 // might be closed while waiting for a character, e.g. typing "exit" in a
2417 // shell and ++close was used. Therefore use curbuf->b_term instead of a
2418 // stored reference.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002419 in_terminal_loop = curbuf->b_term;
2420
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002421 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002422 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002423 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002424 if (termwinkey == Ctrl_W)
2425 termwinkey = 0;
2426 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002427 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002428 may_set_cursor_props(curbuf->b_term);
2429
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002430 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002431 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002432#ifdef FEAT_GUI
2433 if (!curbuf->b_term->tl_system)
2434#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002435 // TODO: skip screen update when handling a sequence of keys.
2436 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002437 while (must_redraw != 0)
2438 if (update_screen(0) == FAIL)
2439 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002440 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002441 // job finished while redrawing
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002442 break;
2443
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002444 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002445 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002446
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002447 raw_c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002448 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002449 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002450 // Job finished while waiting for a character. Push back the
2451 // received character.
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002452 if (raw_c != K_IGNORE)
2453 vungetc(raw_c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002454 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002455 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002456 if (raw_c == K_IGNORE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002457 continue;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002458 c = raw_c_to_ctrl(raw_c);
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002459
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002460#ifdef UNIX
2461 /*
2462 * The shell or another program may change the tty settings. Getting
2463 * them for every typed character is a bit of overhead, but it's needed
2464 * for the first character typed, e.g. when Vim starts in a shell.
2465 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002466 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002467 {
2468 ttyinfo_T info;
2469
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002470 // Get the current backspace character of the pty.
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002471 if (get_tty_info(tty_fd, &info) == OK)
2472 term_backspace_char = info.backspace;
2473 }
2474#endif
2475
Bram Moolenaar4f974752019-02-17 17:44:42 +01002476#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002477 // On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2478 // Use CTRL-BREAK to kill the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002479 if (ctrl_break_was_pressed)
2480 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2481#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002482 // Was either CTRL-W (termwinkey) or CTRL-\ pressed?
2483 // Not in a system terminal.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002484 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002485#ifdef FEAT_GUI
2486 && !curbuf->b_term->tl_system
2487#endif
2488 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002489 {
2490 int prev_c = c;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002491 int prev_raw_c = raw_c;
2492 int prev_mod_mask = mod_mask;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002493
2494#ifdef FEAT_CMDL_INFO
2495 if (add_to_showcmd(c))
2496 out_flush();
2497#endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002498 raw_c = term_vgetc();
2499 c = raw_c_to_ctrl(raw_c);
2500
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002501#ifdef FEAT_CMDL_INFO
2502 clear_showcmd();
2503#endif
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002504 if (!term_use_loop_check(TRUE)
2505 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002506 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002507 break;
2508
2509 if (prev_c == Ctrl_BSL)
2510 {
2511 if (c == Ctrl_N)
2512 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002513 // CTRL-\ CTRL-N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002514 term_enter_normal_mode();
2515 ret = FAIL;
2516 goto theend;
2517 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002518 // Send both keys to the terminal, first one here, second one
2519 // below.
2520 send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask,
2521 TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002522 }
2523 else if (c == Ctrl_C)
2524 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002525 // "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002526 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2527 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002528 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002529 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002530 // "CTRL-W .": send CTRL-W to the job
2531 // "'termwinkey' .": send 'termwinkey' to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002532 raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002533 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002534 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002535 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002536 // "CTRL-W CTRL-\": send CTRL-\ to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002537 raw_c = ctrl_to_raw_c(Ctrl_BSL);
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002538 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002539 else if (c == 'N')
2540 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002541 // CTRL-W N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002542 term_enter_normal_mode();
2543 ret = FAIL;
2544 goto theend;
2545 }
2546 else if (c == '"')
2547 {
2548 term_paste_register(prev_c);
2549 continue;
2550 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002551 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002552 {
Bram Moolenaara4b26992019-08-15 20:58:54 +02002553 char_u buf[MB_MAXBYTES + 2];
2554
2555 // Put the command into the typeahead buffer, when using the
2556 // stuff buffer KeyStuffed is set and 'langmap' won't be used.
2557 buf[0] = Ctrl_W;
2558 buf[(*mb_char2bytes)(c, buf + 1) + 1] = NUL;
2559 ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002560 ret = OK;
2561 goto theend;
2562 }
2563 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01002564# ifdef MSWIN
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002565 if (!enc_utf8 && has_mbyte && raw_c >= 0x80)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002566 {
2567 WCHAR wc;
2568 char_u mb[3];
2569
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002570 mb[0] = (unsigned)raw_c >> 8;
2571 mb[1] = raw_c;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002572 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002573 raw_c = wc;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002574 }
2575# endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002576 if (send_keys_to_term(curbuf->b_term, raw_c, mod_mask, TRUE) != OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002577 {
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002578 if (raw_c == K_MOUSEMOVE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002579 // We are sure to come back here, don't reset the cursor color
2580 // and shape to avoid flickering.
Bram Moolenaard317b382018-02-08 22:33:31 +01002581 restore_cursor = FALSE;
2582
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002583 ret = OK;
2584 goto theend;
2585 }
2586 }
2587 ret = FAIL;
2588
2589theend:
2590 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002591 if (restore_cursor)
2592 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002593
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002594 // Move a snapshot of the screen contents to the buffer, so that completion
2595 // works in other buffers.
Bram Moolenaar620020e2018-05-13 19:06:12 +02002596 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2597 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002598
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002599 return ret;
2600}
2601
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002602 static void
2603may_toggle_cursor(term_T *term)
2604{
2605 if (in_terminal_loop == term)
2606 {
2607 if (term->tl_cursor_visible)
2608 cursor_on();
2609 else
2610 cursor_off();
2611 }
2612}
2613
2614/*
2615 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002616 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002617 */
2618 static int
2619color2index(VTermColor *color, int fg, int *boldp)
2620{
2621 int red = color->red;
2622 int blue = color->blue;
2623 int green = color->green;
2624
Bram Moolenaar46359e12017-11-29 22:33:38 +01002625 if (color->ansi_index != VTERM_ANSI_INDEX_NONE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002626 {
Bram Moolenaar1d79ce82019-04-12 22:27:39 +02002627 // The first 16 colors and default: use the ANSI index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002628 switch (color->ansi_index)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002629 {
Bram Moolenaar46359e12017-11-29 22:33:38 +01002630 case 0: return 0;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002631 case 1: return lookup_color( 0, fg, boldp) + 1; // black
2632 case 2: return lookup_color( 4, fg, boldp) + 1; // dark red
2633 case 3: return lookup_color( 2, fg, boldp) + 1; // dark green
2634 case 4: return lookup_color( 6, fg, boldp) + 1; // brown
2635 case 5: return lookup_color( 1, fg, boldp) + 1; // dark blue
2636 case 6: return lookup_color( 5, fg, boldp) + 1; // dark magenta
2637 case 7: return lookup_color( 3, fg, boldp) + 1; // dark cyan
2638 case 8: return lookup_color( 8, fg, boldp) + 1; // light grey
2639 case 9: return lookup_color(12, fg, boldp) + 1; // dark grey
2640 case 10: return lookup_color(20, fg, boldp) + 1; // red
2641 case 11: return lookup_color(16, fg, boldp) + 1; // green
2642 case 12: return lookup_color(24, fg, boldp) + 1; // yellow
2643 case 13: return lookup_color(14, fg, boldp) + 1; // blue
2644 case 14: return lookup_color(22, fg, boldp) + 1; // magenta
2645 case 15: return lookup_color(18, fg, boldp) + 1; // cyan
2646 case 16: return lookup_color(26, fg, boldp) + 1; // white
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002647 }
2648 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002649
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002650 if (t_colors >= 256)
2651 {
2652 if (red == blue && red == green)
2653 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002654 // 24-color greyscale plus white and black
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002655 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002656 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2657 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2658 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002659 int i;
2660
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002661 if (red < 5)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002662 return 17; // 00/00/00
2663 if (red > 245) // ff/ff/ff
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002664 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002665 for (i = 0; i < 23; ++i)
2666 if (red < cutoff[i])
2667 return i + 233;
2668 return 256;
2669 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002670 {
2671 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2672 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002673
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002674 // 216-color cube
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002675 for (ri = 0; ri < 5; ++ri)
2676 if (red < cutoff[ri])
2677 break;
2678 for (gi = 0; gi < 5; ++gi)
2679 if (green < cutoff[gi])
2680 break;
2681 for (bi = 0; bi < 5; ++bi)
2682 if (blue < cutoff[bi])
2683 break;
2684 return 17 + ri * 36 + gi * 6 + bi;
2685 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002686 }
2687 return 0;
2688}
2689
2690/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002691 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002692 */
2693 static int
Bram Moolenaard96ff162018-02-18 22:13:29 +01002694vtermAttr2hl(VTermScreenCellAttrs cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002695{
2696 int attr = 0;
2697
2698 if (cellattrs.bold)
2699 attr |= HL_BOLD;
2700 if (cellattrs.underline)
2701 attr |= HL_UNDERLINE;
2702 if (cellattrs.italic)
2703 attr |= HL_ITALIC;
2704 if (cellattrs.strike)
2705 attr |= HL_STRIKETHROUGH;
2706 if (cellattrs.reverse)
2707 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002708 return attr;
2709}
2710
2711/*
2712 * Store Vterm attributes in "cell" from highlight flags.
2713 */
2714 static void
2715hl2vtermAttr(int attr, cellattr_T *cell)
2716{
2717 vim_memset(&cell->attrs, 0, sizeof(VTermScreenCellAttrs));
2718 if (attr & HL_BOLD)
2719 cell->attrs.bold = 1;
2720 if (attr & HL_UNDERLINE)
2721 cell->attrs.underline = 1;
2722 if (attr & HL_ITALIC)
2723 cell->attrs.italic = 1;
2724 if (attr & HL_STRIKETHROUGH)
2725 cell->attrs.strike = 1;
2726 if (attr & HL_INVERSE)
2727 cell->attrs.reverse = 1;
2728}
2729
2730/*
2731 * Convert the attributes of a vterm cell into an attribute index.
2732 */
2733 static int
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002734cell2attr(
2735 win_T *wp,
2736 VTermScreenCellAttrs cellattrs,
2737 VTermColor cellfg,
2738 VTermColor cellbg)
Bram Moolenaard96ff162018-02-18 22:13:29 +01002739{
2740 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002741
2742#ifdef FEAT_GUI
2743 if (gui.in_use)
2744 {
2745 guicolor_T fg, bg;
2746
2747 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue);
2748 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue);
2749 return get_gui_attr_idx(attr, fg, bg);
2750 }
2751 else
2752#endif
2753#ifdef FEAT_TERMGUICOLORS
2754 if (p_tgc)
2755 {
2756 guicolor_T fg, bg;
2757
2758 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue);
2759 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue);
2760
2761 return get_tgc_attr_idx(attr, fg, bg);
2762 }
2763 else
2764#endif
2765 {
2766 int bold = MAYBE;
2767 int fg = color2index(&cellfg, TRUE, &bold);
2768 int bg = color2index(&cellbg, FALSE, &bold);
2769
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002770 // Use the 'wincolor' or "Terminal" highlighting for the default
2771 // colors.
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01002772 if ((fg == 0 || bg == 0) && t_colors >= 16)
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002773 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002774 int wincolor_fg = -1;
2775 int wincolor_bg = -1;
2776
2777 if (wp != NULL && *wp->w_p_wcr != NUL)
2778 {
2779 int id = syn_name2id(curwin->w_p_wcr);
2780
2781 // Get the 'wincolor' group colors.
2782 if (id > 0)
2783 syn_id2cterm_bg(id, &wincolor_fg, &wincolor_bg);
2784 }
2785 if (fg == 0)
2786 {
2787 if (wincolor_fg >= 0)
2788 fg = wincolor_fg + 1;
2789 else if (term_default_cterm_fg >= 0)
2790 fg = term_default_cterm_fg + 1;
2791 }
2792 if (bg == 0)
2793 {
2794 if (wincolor_bg >= 0)
2795 bg = wincolor_bg + 1;
2796 else if (term_default_cterm_bg >= 0)
2797 bg = term_default_cterm_bg + 1;
2798 }
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002799 }
2800
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002801 // with 8 colors set the bold attribute to get a bright foreground
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002802 if (bold == TRUE)
2803 attr |= HL_BOLD;
2804 return get_cterm_attr_idx(attr, fg, bg);
2805 }
2806 return 0;
2807}
2808
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002809 static void
2810set_dirty_snapshot(term_T *term)
2811{
2812 term->tl_dirty_snapshot = TRUE;
2813#ifdef FEAT_TIMERS
2814 if (!term->tl_normal_mode)
2815 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002816 // Update the snapshot after 100 msec of not getting updates.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002817 profile_setlimit(100L, &term->tl_timer_due);
2818 term->tl_timer_set = TRUE;
2819 }
2820#endif
2821}
2822
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002823 static int
2824handle_damage(VTermRect rect, void *user)
2825{
2826 term_T *term = (term_T *)user;
2827
2828 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
2829 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002830 set_dirty_snapshot(term);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002831 redraw_buf_later(term->tl_buffer, SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002832 return 1;
2833}
2834
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002835 static void
2836term_scroll_up(term_T *term, int start_row, int count)
2837{
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002838 win_T *wp = NULL;
2839 int did_curwin = FALSE;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002840 VTermColor fg, bg;
2841 VTermScreenCellAttrs attr;
2842 int clear_attr;
2843
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002844 vim_memset(&attr, 0, sizeof(attr));
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002845
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002846 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002847 {
2848 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002849 {
2850 // Set the color to clear lines with.
2851 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2852 &fg, &bg);
2853 clear_attr = cell2attr(wp, attr, fg, bg);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002854 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002855 }
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002856 }
2857}
2858
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002859 static int
2860handle_moverect(VTermRect dest, VTermRect src, void *user)
2861{
2862 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002863 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002864
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002865 // Scrolling up is done much more efficiently by deleting lines instead of
2866 // redrawing the text. But avoid doing this multiple times, postpone until
2867 // the redraw happens.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002868 if (dest.start_col == src.start_col
2869 && dest.end_col == src.end_col
2870 && dest.start_row < src.start_row)
2871 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02002872 if (dest.start_row == 0)
2873 term->tl_postponed_scroll += count;
2874 else
2875 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002876 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002877
2878 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
2879 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002880 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02002881
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002882 // Note sure if the scrolling will work correctly, let's do a complete
2883 // redraw later.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002884 redraw_buf_later(term->tl_buffer, NOT_VALID);
2885 return 1;
2886}
2887
2888 static int
2889handle_movecursor(
2890 VTermPos pos,
2891 VTermPos oldpos UNUSED,
2892 int visible,
2893 void *user)
2894{
2895 term_T *term = (term_T *)user;
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002896 win_T *wp = NULL;
2897 int did_curwin = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002898
2899 term->tl_cursor_pos = pos;
2900 term->tl_cursor_visible = visible;
2901
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002902 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002903 {
2904 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002905 position_cursor(wp, &pos, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002906 }
2907 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
2908 {
2909 may_toggle_cursor(term);
2910 update_cursor(term, term->tl_cursor_visible);
2911 }
2912
2913 return 1;
2914}
2915
2916 static int
2917handle_settermprop(
2918 VTermProp prop,
2919 VTermValue *value,
2920 void *user)
2921{
2922 term_T *term = (term_T *)user;
2923
2924 switch (prop)
2925 {
2926 case VTERM_PROP_TITLE:
2927 vim_free(term->tl_title);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002928 // a blank title isn't useful, make it empty, so that "running" is
2929 // displayed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002930 if (*skipwhite((char_u *)value->string) == NUL)
2931 term->tl_title = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01002932 // Same as blank
2933 else if (term->tl_arg0_cmd != NULL
2934 && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string,
2935 (int)STRLEN(term->tl_arg0_cmd)) == 0)
2936 term->tl_title = NULL;
2937 // Empty corrupted data of winpty
2938 else if (STRNCMP(" - ", (char_u *)value->string, 4) == 0)
2939 term->tl_title = NULL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01002940#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002941 else if (!enc_utf8 && enc_codepage > 0)
2942 {
2943 WCHAR *ret = NULL;
2944 int length = 0;
2945
2946 MultiByteToWideChar_alloc(CP_UTF8, 0,
2947 (char*)value->string, (int)STRLEN(value->string),
2948 &ret, &length);
2949 if (ret != NULL)
2950 {
2951 WideCharToMultiByte_alloc(enc_codepage, 0,
2952 ret, length, (char**)&term->tl_title,
2953 &length, 0, 0);
2954 vim_free(ret);
2955 }
2956 }
2957#endif
2958 else
2959 term->tl_title = vim_strsave((char_u *)value->string);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002960 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002961 if (term == curbuf->b_term)
2962 maketitle();
2963 break;
2964
2965 case VTERM_PROP_CURSORVISIBLE:
2966 term->tl_cursor_visible = value->boolean;
2967 may_toggle_cursor(term);
2968 out_flush();
2969 break;
2970
2971 case VTERM_PROP_CURSORBLINK:
2972 term->tl_cursor_blink = value->boolean;
2973 may_set_cursor_props(term);
2974 break;
2975
2976 case VTERM_PROP_CURSORSHAPE:
2977 term->tl_cursor_shape = value->number;
2978 may_set_cursor_props(term);
2979 break;
2980
2981 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002982 cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002983 may_set_cursor_props(term);
2984 break;
2985
2986 case VTERM_PROP_ALTSCREEN:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002987 // TODO: do anything else?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002988 term->tl_using_altscreen = value->boolean;
2989 break;
2990
2991 default:
2992 break;
2993 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002994 // Always return 1, otherwise vterm doesn't store the value internally.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002995 return 1;
2996}
2997
2998/*
2999 * The job running in the terminal resized the terminal.
3000 */
3001 static int
3002handle_resize(int rows, int cols, void *user)
3003{
3004 term_T *term = (term_T *)user;
3005 win_T *wp;
3006
3007 term->tl_rows = rows;
3008 term->tl_cols = cols;
3009 if (term->tl_vterm_size_changed)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003010 // Size was set by vterm_set_size(), don't set the window size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003011 term->tl_vterm_size_changed = FALSE;
3012 else
3013 {
3014 FOR_ALL_WINDOWS(wp)
3015 {
3016 if (wp->w_buffer == term->tl_buffer)
3017 {
3018 win_setheight_win(rows, wp);
3019 win_setwidth_win(cols, wp);
3020 }
3021 }
3022 redraw_buf_later(term->tl_buffer, NOT_VALID);
3023 }
3024 return 1;
3025}
3026
3027/*
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003028 * If the number of lines that are stored goes over 'termscrollback' then
3029 * delete the first 10%.
3030 * "gap" points to tl_scrollback or tl_scrollback_postponed.
3031 * "update_buffer" is TRUE when the buffer should be updated.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003032 */
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003033 static void
3034limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003035{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003036 if (gap->ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003037 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02003038 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003039 int i;
3040
3041 curbuf = term->tl_buffer;
3042 for (i = 0; i < todo; ++i)
3043 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003044 vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells);
3045 if (update_buffer)
3046 ml_delete(1, FALSE);
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003047 }
3048 curbuf = curwin->w_buffer;
3049
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003050 gap->ga_len -= todo;
3051 mch_memmove(gap->ga_data,
3052 (sb_line_T *)gap->ga_data + todo,
3053 sizeof(sb_line_T) * gap->ga_len);
3054 if (update_buffer)
3055 term->tl_scrollback_scrolled -= todo;
3056 }
3057}
3058
3059/*
3060 * Handle a line that is pushed off the top of the screen.
3061 */
3062 static int
3063handle_pushline(int cols, const VTermScreenCell *cells, void *user)
3064{
3065 term_T *term = (term_T *)user;
3066 garray_T *gap;
3067 int update_buffer;
3068
3069 if (term->tl_normal_mode)
3070 {
3071 // In Terminal-Normal mode the user interacts with the buffer, thus we
3072 // must not change it. Postpone adding the scrollback lines.
3073 gap = &term->tl_scrollback_postponed;
3074 update_buffer = FALSE;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003075 }
3076 else
3077 {
3078 // First remove the lines that were appended before, the pushed line
3079 // goes above it.
3080 cleanup_scrollback(term);
3081 gap = &term->tl_scrollback;
3082 update_buffer = TRUE;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003083 }
3084
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003085 limit_scrollback(term, gap, update_buffer);
3086
3087 if (ga_grow(gap, 1) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003088 {
3089 cellattr_T *p = NULL;
3090 int len = 0;
3091 int i;
3092 int c;
3093 int col;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003094 int text_len;
3095 char_u *text;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003096 sb_line_T *line;
3097 garray_T ga;
3098 cellattr_T fill_attr = term->tl_default_color;
3099
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003100 // do not store empty cells at the end
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003101 for (i = 0; i < cols; ++i)
3102 if (cells[i].chars[0] != 0)
3103 len = i + 1;
3104 else
3105 cell2cellattr(&cells[i], &fill_attr);
3106
3107 ga_init2(&ga, 1, 100);
3108 if (len > 0)
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003109 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003110 if (p != NULL)
3111 {
3112 for (col = 0; col < len; col += cells[col].width)
3113 {
3114 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
3115 {
3116 ga.ga_len = 0;
3117 break;
3118 }
3119 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
3120 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
3121 (char_u *)ga.ga_data + ga.ga_len);
3122 cell2cellattr(&cells[col], &p[col]);
3123 }
3124 }
3125 if (ga_grow(&ga, 1) == FAIL)
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003126 {
3127 if (update_buffer)
3128 text = (char_u *)"";
3129 else
3130 text = vim_strsave((char_u *)"");
3131 text_len = 0;
3132 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003133 else
3134 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003135 text = ga.ga_data;
3136 text_len = ga.ga_len;
3137 *(text + text_len) = NUL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003138 }
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003139 if (update_buffer)
3140 add_scrollback_line_to_buffer(term, text, text_len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003141
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003142 line = (sb_line_T *)gap->ga_data + gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003143 line->sb_cols = len;
3144 line->sb_cells = p;
3145 line->sb_fill_attr = fill_attr;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003146 if (update_buffer)
3147 {
3148 line->sb_text = NULL;
3149 ++term->tl_scrollback_scrolled;
3150 ga_clear(&ga); // free the text
3151 }
3152 else
3153 {
3154 line->sb_text = text;
3155 ga_init(&ga); // text is kept in tl_scrollback_postponed
3156 }
3157 ++gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003158 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003159 return 0; // ignored
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003160}
3161
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003162/*
3163 * Called when leaving Terminal-Normal mode: deal with any scrollback that was
3164 * received and stored in tl_scrollback_postponed.
3165 */
3166 static void
3167handle_postponed_scrollback(term_T *term)
3168{
3169 int i;
3170
Bram Moolenaar8376c3d2019-03-19 20:50:43 +01003171 if (term->tl_scrollback_postponed.ga_len == 0)
3172 return;
3173 ch_log(NULL, "Moving postponed scrollback to scrollback");
3174
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003175 // First remove the lines that were appended before, the pushed lines go
3176 // above it.
3177 cleanup_scrollback(term);
3178
3179 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
3180 {
3181 char_u *text;
3182 sb_line_T *pp_line;
3183 sb_line_T *line;
3184
3185 if (ga_grow(&term->tl_scrollback, 1) == FAIL)
3186 break;
3187 pp_line = (sb_line_T *)term->tl_scrollback_postponed.ga_data + i;
3188
3189 text = pp_line->sb_text;
3190 if (text == NULL)
3191 text = (char_u *)"";
3192 add_scrollback_line_to_buffer(term, text, (int)STRLEN(text));
3193 vim_free(pp_line->sb_text);
3194
3195 line = (sb_line_T *)term->tl_scrollback.ga_data
3196 + term->tl_scrollback.ga_len;
3197 line->sb_cols = pp_line->sb_cols;
3198 line->sb_cells = pp_line->sb_cells;
3199 line->sb_fill_attr = pp_line->sb_fill_attr;
3200 line->sb_text = NULL;
3201 ++term->tl_scrollback_scrolled;
3202 ++term->tl_scrollback.ga_len;
3203 }
3204
3205 ga_clear(&term->tl_scrollback_postponed);
3206 limit_scrollback(term, &term->tl_scrollback, TRUE);
3207}
3208
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003209static VTermScreenCallbacks screen_callbacks = {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003210 handle_damage, // damage
3211 handle_moverect, // moverect
3212 handle_movecursor, // movecursor
3213 handle_settermprop, // settermprop
3214 NULL, // bell
3215 handle_resize, // resize
3216 handle_pushline, // sb_pushline
3217 NULL // sb_popline
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003218};
3219
3220/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003221 * Do the work after the channel of a terminal was closed.
3222 * Must be called only when updating_screen is FALSE.
3223 * Returns TRUE when a buffer was closed (list of terminals may have changed).
3224 */
3225 static int
3226term_after_channel_closed(term_T *term)
3227{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003228 // Unless in Terminal-Normal mode: clear the vterm.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003229 if (!term->tl_normal_mode)
3230 {
3231 int fnum = term->tl_buffer->b_fnum;
3232
3233 cleanup_vterm(term);
3234
3235 if (term->tl_finish == TL_FINISH_CLOSE)
3236 {
3237 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003238 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003239#ifdef FEAT_PROP_POPUP
3240 win_T *pwin = NULL;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003241
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003242 // If this was a terminal in a popup window, go back to the
3243 // previous window.
3244 if (popup_is_popup(curwin) && curbuf == term->tl_buffer)
3245 {
3246 pwin = curwin;
3247 if (win_valid(prevwin))
3248 win_enter(prevwin, FALSE);
3249 }
3250 else
3251#endif
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003252 // If this is the last normal window: exit Vim.
3253 if (term->tl_buffer->b_nwindows > 0 && only_one_window())
3254 {
3255 exarg_T ea;
3256
3257 vim_memset(&ea, 0, sizeof(ea));
3258 ex_quit(&ea);
3259 return TRUE;
3260 }
3261
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003262 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003263 ch_log(NULL, "terminal job finished, closing window");
3264 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003265 // Avoid closing the window if we temporarily use it.
Bram Moolenaar517f71a2019-06-17 22:40:41 +02003266 if (curwin == aucmd_win)
3267 do_set_w_closing = TRUE;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003268 if (do_set_w_closing)
3269 curwin->w_closing = TRUE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003270 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003271 if (do_set_w_closing)
3272 curwin->w_closing = FALSE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003273 aucmd_restbuf(&aco);
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003274#ifdef FEAT_PROP_POPUP
3275 if (pwin != NULL)
3276 popup_close_with_retval(pwin, 0);
3277#endif
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003278 return TRUE;
3279 }
3280 if (term->tl_finish == TL_FINISH_OPEN
3281 && term->tl_buffer->b_nwindows == 0)
3282 {
3283 char buf[50];
3284
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003285 // TODO: use term_opencmd
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003286 ch_log(NULL, "terminal job finished, opening window");
3287 vim_snprintf(buf, sizeof(buf),
3288 term->tl_opencmd == NULL
3289 ? "botright sbuf %d"
3290 : (char *)term->tl_opencmd, fnum);
3291 do_cmdline_cmd((char_u *)buf);
3292 }
3293 else
3294 ch_log(NULL, "terminal job finished");
3295 }
3296
3297 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
3298 return FALSE;
3299}
3300
Bram Moolenaard98c0b62020-02-02 15:25:16 +01003301#if defined(FEAT_PROP_POPUP) || defined(PROTO)
3302/*
3303 * If the current window is a terminal in a popup window and the job has
3304 * finished, close the popup window and to back to the previous window.
3305 * Otherwise return FAIL.
3306 */
3307 int
3308may_close_term_popup(void)
3309{
3310 if (popup_is_popup(curwin) && curbuf->b_term != NULL
3311 && !term_job_running(curbuf->b_term))
3312 {
3313 win_T *pwin = curwin;
3314
3315 if (win_valid(prevwin))
3316 win_enter(prevwin, FALSE);
3317 popup_close_with_retval(pwin, 0);
3318 return OK;
3319 }
3320 return FAIL;
3321}
3322#endif
3323
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003324/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003325 * Called when a channel has been closed.
3326 * If this was a channel for a terminal window then finish it up.
3327 */
3328 void
3329term_channel_closed(channel_T *ch)
3330{
3331 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003332 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003333 int did_one = FALSE;
3334
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003335 for (term = first_term; term != NULL; term = next_term)
3336 {
3337 next_term = term->tl_next;
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02003338 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003339 {
3340 term->tl_channel_closed = TRUE;
3341 did_one = TRUE;
3342
Bram Moolenaard23a8232018-02-10 18:45:26 +01003343 VIM_CLEAR(term->tl_title);
3344 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar4f974752019-02-17 17:44:42 +01003345#ifdef MSWIN
Bram Moolenaar402c8392018-05-06 22:01:42 +02003346 if (term->tl_out_fd != NULL)
3347 {
3348 fclose(term->tl_out_fd);
3349 term->tl_out_fd = NULL;
3350 }
3351#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003352
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003353 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003354 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003355 // Cannot open or close windows now. Can happen when
3356 // 'lazyredraw' is set.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003357 term->tl_channel_recently_closed = TRUE;
3358 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003359 }
3360
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003361 if (term_after_channel_closed(term))
3362 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003363 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003364 }
3365
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003366 if (did_one)
3367 {
3368 redraw_statuslines();
3369
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003370 // Need to break out of vgetc().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003371 ins_char_typebuf(K_IGNORE);
3372 typebuf_was_filled = TRUE;
3373
3374 term = curbuf->b_term;
3375 if (term != NULL)
3376 {
3377 if (term->tl_job == ch->ch_job)
3378 maketitle();
3379 update_cursor(term, term->tl_cursor_visible);
3380 }
3381 }
3382}
3383
3384/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003385 * To be called after resetting updating_screen: handle any terminal where the
3386 * channel was closed.
3387 */
3388 void
3389term_check_channel_closed_recently()
3390{
3391 term_T *term;
3392 term_T *next_term;
3393
3394 for (term = first_term; term != NULL; term = next_term)
3395 {
3396 next_term = term->tl_next;
3397 if (term->tl_channel_recently_closed)
3398 {
3399 term->tl_channel_recently_closed = FALSE;
3400 if (term_after_channel_closed(term))
3401 // start over, the list may have changed
3402 next_term = first_term;
3403 }
3404 }
3405}
3406
3407/*
Bram Moolenaar13568252018-03-16 20:46:58 +01003408 * Fill one screen line from a line of the terminal.
3409 * Advances "pos" to past the last column.
3410 */
3411 static void
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003412term_line2screenline(
3413 win_T *wp,
3414 VTermScreen *screen,
3415 VTermPos *pos,
3416 int max_col)
Bram Moolenaar13568252018-03-16 20:46:58 +01003417{
3418 int off = screen_get_current_line_off();
3419
3420 for (pos->col = 0; pos->col < max_col; )
3421 {
3422 VTermScreenCell cell;
3423 int c;
3424
3425 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
3426 vim_memset(&cell, 0, sizeof(cell));
3427
3428 c = cell.chars[0];
3429 if (c == NUL)
3430 {
3431 ScreenLines[off] = ' ';
3432 if (enc_utf8)
3433 ScreenLinesUC[off] = NUL;
3434 }
3435 else
3436 {
3437 if (enc_utf8)
3438 {
3439 int i;
3440
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003441 // composing chars
Bram Moolenaar13568252018-03-16 20:46:58 +01003442 for (i = 0; i < Screen_mco
3443 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3444 {
3445 ScreenLinesC[i][off] = cell.chars[i + 1];
3446 if (cell.chars[i + 1] == 0)
3447 break;
3448 }
3449 if (c >= 0x80 || (Screen_mco > 0
3450 && ScreenLinesC[0][off] != 0))
3451 {
3452 ScreenLines[off] = ' ';
3453 ScreenLinesUC[off] = c;
3454 }
3455 else
3456 {
3457 ScreenLines[off] = c;
3458 ScreenLinesUC[off] = NUL;
3459 }
3460 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01003461#ifdef MSWIN
Bram Moolenaar13568252018-03-16 20:46:58 +01003462 else if (has_mbyte && c >= 0x80)
3463 {
3464 char_u mb[MB_MAXBYTES+1];
3465 WCHAR wc = c;
3466
3467 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3468 (char*)mb, 2, 0, 0) > 1)
3469 {
3470 ScreenLines[off] = mb[0];
3471 ScreenLines[off + 1] = mb[1];
3472 cell.width = mb_ptr2cells(mb);
3473 }
3474 else
3475 ScreenLines[off] = c;
3476 }
3477#endif
3478 else
3479 ScreenLines[off] = c;
3480 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003481 ScreenAttrs[off] = cell2attr(wp, cell.attrs, cell.fg, cell.bg);
Bram Moolenaar13568252018-03-16 20:46:58 +01003482
3483 ++pos->col;
3484 ++off;
3485 if (cell.width == 2)
3486 {
3487 if (enc_utf8)
3488 ScreenLinesUC[off] = NUL;
3489
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003490 // don't set the second byte to NUL for a DBCS encoding, it
3491 // has been set above
Bram Moolenaar13568252018-03-16 20:46:58 +01003492 if (enc_utf8 || !has_mbyte)
3493 ScreenLines[off] = NUL;
3494
3495 ++pos->col;
3496 ++off;
3497 }
3498 }
3499}
3500
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003501#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003502 static void
3503update_system_term(term_T *term)
3504{
3505 VTermPos pos;
3506 VTermScreen *screen;
3507
3508 if (term->tl_vterm == NULL)
3509 return;
3510 screen = vterm_obtain_screen(term->tl_vterm);
3511
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003512 // Scroll up to make more room for terminal lines if needed.
Bram Moolenaar13568252018-03-16 20:46:58 +01003513 while (term->tl_toprow > 0
3514 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3515 {
3516 int save_p_more = p_more;
3517
3518 p_more = FALSE;
3519 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003520 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003521 p_more = save_p_more;
3522 --term->tl_toprow;
3523 }
3524
3525 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3526 && pos.row < Rows; ++pos.row)
3527 {
3528 if (pos.row < term->tl_rows)
3529 {
3530 int max_col = MIN(Columns, term->tl_cols);
3531
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003532 term_line2screenline(NULL, screen, &pos, max_col);
Bram Moolenaar13568252018-03-16 20:46:58 +01003533 }
3534 else
3535 pos.col = 0;
3536
Bram Moolenaar4d784b22019-05-25 19:51:39 +02003537 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, 0);
Bram Moolenaar13568252018-03-16 20:46:58 +01003538 }
3539
3540 term->tl_dirty_row_start = MAX_ROW;
3541 term->tl_dirty_row_end = 0;
Bram Moolenaar13568252018-03-16 20:46:58 +01003542}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003543#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003544
3545/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003546 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3547 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003548 * Terminal-Normal mode.
3549 */
3550 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003551term_do_update_window(win_T *wp)
3552{
3553 term_T *term = wp->w_buffer->b_term;
3554
3555 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3556}
3557
3558/*
3559 * Called to update a window that contains an active terminal.
3560 */
3561 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003562term_update_window(win_T *wp)
3563{
3564 term_T *term = wp->w_buffer->b_term;
3565 VTerm *vterm;
3566 VTermScreen *screen;
3567 VTermState *state;
3568 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003569 int rows, cols;
3570 int newrows, newcols;
3571 int minsize;
3572 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003573
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003574 vterm = term->tl_vterm;
3575 screen = vterm_obtain_screen(vterm);
3576 state = vterm_obtain_state(vterm);
3577
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003578 // We use NOT_VALID on a resize or scroll, redraw everything then. With
3579 // SOME_VALID only redraw what was marked dirty.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003580 if (wp->w_redr_type > SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003581 {
3582 term->tl_dirty_row_start = 0;
3583 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003584
3585 if (term->tl_postponed_scroll > 0
3586 && term->tl_postponed_scroll < term->tl_rows / 3)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003587 // Scrolling is usually faster than redrawing, when there are only
3588 // a few lines to scroll.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003589 term_scroll_up(term, 0, term->tl_postponed_scroll);
3590 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003591 }
3592
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003593 /*
3594 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003595 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003596 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003597 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003598
Bram Moolenaar498c2562018-04-15 23:45:15 +02003599 newrows = 99999;
3600 newcols = 99999;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003601 for (twp = firstwin; ; twp = twp->w_next)
Bram Moolenaar498c2562018-04-15 23:45:15 +02003602 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003603 // Always use curwin, it may be a popup window.
3604 win_T *wwp = twp == NULL ? curwin : twp;
3605
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003606 // When more than one window shows the same terminal, use the
3607 // smallest size.
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003608 if (wwp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003609 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003610 newrows = MIN(newrows, wwp->w_height);
3611 newcols = MIN(newcols, wwp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003612 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003613 if (twp == NULL)
3614 break;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003615 }
Bram Moolenaare0d749a2019-09-25 22:14:48 +02003616 if (newrows == 99999 || newcols == 99999)
3617 return; // safety exit
Bram Moolenaar498c2562018-04-15 23:45:15 +02003618 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3619 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3620
3621 if (term->tl_rows != newrows || term->tl_cols != newcols)
3622 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003623 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003624 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003625 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003626 newrows);
3627 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003628
3629 // Updating the terminal size will cause the snapshot to be cleared.
3630 // When not in terminal_loop() we need to restore it.
3631 if (term != in_terminal_loop)
3632 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003633 }
3634
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003635 // The cursor may have been moved when resizing.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003636 vterm_state_get_cursorpos(state, &pos);
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003637 position_cursor(wp, &pos, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003638
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003639 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3640 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003641 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003642 if (pos.row < term->tl_rows)
3643 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003644 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003645
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003646 term_line2screenline(wp, screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003647 }
3648 else
3649 pos.col = 0;
3650
Bram Moolenaarf118d482018-03-13 13:14:00 +01003651 screen_line(wp->w_winrow + pos.row
3652#ifdef FEAT_MENU
3653 + winbar_height(wp)
3654#endif
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003655 , wp->w_wincol, pos.col, wp->w_width,
3656#ifdef FEAT_PROP_POPUP
3657 popup_is_popup(wp) ? SLF_POPUP :
3658#endif
3659 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003660 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003661 term->tl_dirty_row_start = MAX_ROW;
3662 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003663}
3664
3665/*
3666 * Return TRUE if "wp" is a terminal window where the job has finished.
3667 */
3668 int
3669term_is_finished(buf_T *buf)
3670{
3671 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3672}
3673
3674/*
3675 * Return TRUE if "wp" is a terminal window where the job has finished or we
3676 * are in Terminal-Normal mode, thus we show the buffer contents.
3677 */
3678 int
3679term_show_buffer(buf_T *buf)
3680{
3681 term_T *term = buf->b_term;
3682
3683 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3684}
3685
3686/*
3687 * The current buffer is going to be changed. If there is terminal
3688 * highlighting remove it now.
3689 */
3690 void
3691term_change_in_curbuf(void)
3692{
3693 term_T *term = curbuf->b_term;
3694
3695 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3696 {
3697 free_scrollback(term);
3698 redraw_buf_later(term->tl_buffer, NOT_VALID);
3699
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003700 // The buffer is now like a normal buffer, it cannot be easily
3701 // abandoned when changed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003702 set_string_option_direct((char_u *)"buftype", -1,
3703 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3704 }
3705}
3706
3707/*
3708 * Get the screen attribute for a position in the buffer.
3709 * Use a negative "col" to get the filler background color.
3710 */
3711 int
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003712term_get_attr(win_T *wp, linenr_T lnum, int col)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003713{
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003714 buf_T *buf = wp->w_buffer;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003715 term_T *term = buf->b_term;
3716 sb_line_T *line;
3717 cellattr_T *cellattr;
3718
3719 if (lnum > term->tl_scrollback.ga_len)
3720 cellattr = &term->tl_default_color;
3721 else
3722 {
3723 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3724 if (col < 0 || col >= line->sb_cols)
3725 cellattr = &line->sb_fill_attr;
3726 else
3727 cellattr = line->sb_cells + col;
3728 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003729 return cell2attr(wp, cellattr->attrs, cellattr->fg, cellattr->bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003730}
3731
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003732/*
3733 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02003734 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003735 */
3736 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003737cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003738{
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003739 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003740}
3741
3742/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01003743 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003744 */
3745 static void
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003746init_default_colors(term_T *term, win_T *wp)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003747{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003748 VTermColor *fg, *bg;
3749 int fgval, bgval;
3750 int id;
3751
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003752 vim_memset(&term->tl_default_color.attrs, 0, sizeof(VTermScreenCellAttrs));
3753 term->tl_default_color.width = 1;
3754 fg = &term->tl_default_color.fg;
3755 bg = &term->tl_default_color.bg;
3756
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003757 // Vterm uses a default black background. Set it to white when
3758 // 'background' is "light".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003759 if (*p_bg == 'l')
3760 {
3761 fgval = 0;
3762 bgval = 255;
3763 }
3764 else
3765 {
3766 fgval = 255;
3767 bgval = 0;
3768 }
3769 fg->red = fg->green = fg->blue = fgval;
3770 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003771 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003772
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003773 // The 'wincolor' or "Terminal" highlight group overrules the defaults.
3774 if (wp != NULL && *wp->w_p_wcr != NUL)
3775 id = syn_name2id(wp->w_p_wcr);
3776 else
3777 id = syn_name2id((char_u *)"Terminal");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003778
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003779 // Use the actual color for the GUI and when 'termguicolors' is set.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003780#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3781 if (0
3782# ifdef FEAT_GUI
3783 || gui.in_use
3784# endif
3785# ifdef FEAT_TERMGUICOLORS
3786 || p_tgc
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003787# ifdef FEAT_VTP
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003788 // Finally get INVALCOLOR on this execution path
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003789 || (!p_tgc && t_colors >= 256)
3790# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003791# endif
3792 )
3793 {
3794 guicolor_T fg_rgb = INVALCOLOR;
3795 guicolor_T bg_rgb = INVALCOLOR;
3796
3797 if (id != 0)
3798 syn_id2colors(id, &fg_rgb, &bg_rgb);
3799
3800# ifdef FEAT_GUI
3801 if (gui.in_use)
3802 {
3803 if (fg_rgb == INVALCOLOR)
3804 fg_rgb = gui.norm_pixel;
3805 if (bg_rgb == INVALCOLOR)
3806 bg_rgb = gui.back_pixel;
3807 }
3808# ifdef FEAT_TERMGUICOLORS
3809 else
3810# endif
3811# endif
3812# ifdef FEAT_TERMGUICOLORS
3813 {
3814 if (fg_rgb == INVALCOLOR)
3815 fg_rgb = cterm_normal_fg_gui_color;
3816 if (bg_rgb == INVALCOLOR)
3817 bg_rgb = cterm_normal_bg_gui_color;
3818 }
3819# endif
3820 if (fg_rgb != INVALCOLOR)
3821 {
3822 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
3823
3824 fg->red = (unsigned)(rgb >> 16);
3825 fg->green = (unsigned)(rgb >> 8) & 255;
3826 fg->blue = (unsigned)rgb & 255;
3827 }
3828 if (bg_rgb != INVALCOLOR)
3829 {
3830 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
3831
3832 bg->red = (unsigned)(rgb >> 16);
3833 bg->green = (unsigned)(rgb >> 8) & 255;
3834 bg->blue = (unsigned)rgb & 255;
3835 }
3836 }
3837 else
3838#endif
3839 if (id != 0 && t_colors >= 16)
3840 {
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003841 if (term_default_cterm_fg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003842 cterm_color2vterm(term_default_cterm_fg, fg);
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01003843 if (term_default_cterm_bg >= 0)
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003844 cterm_color2vterm(term_default_cterm_bg, bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003845 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003846 else
3847 {
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003848#if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003849 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003850#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003851
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003852 // In an MS-Windows console we know the normal colors.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003853 if (cterm_normal_fg_color > 0)
3854 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003855 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003856# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3857# ifdef VIMDLL
3858 if (!gui.in_use)
3859# endif
3860 {
3861 tmp = fg->red;
3862 fg->red = fg->blue;
3863 fg->blue = tmp;
3864 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003865# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003866 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003867# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003868 else
3869 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003870# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003871
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003872 if (cterm_normal_bg_color > 0)
3873 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02003874 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02003875# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
3876# ifdef VIMDLL
3877 if (!gui.in_use)
3878# endif
3879 {
3880 tmp = fg->red;
3881 fg->red = fg->blue;
3882 fg->blue = tmp;
3883 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003884# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003885 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02003886# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02003887 else
3888 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02003889# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003890 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01003891}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003892
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003893#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
3894/*
3895 * Set the 16 ANSI colors from array of RGB values
3896 */
3897 static void
3898set_vterm_palette(VTerm *vterm, long_u *rgb)
3899{
3900 int index = 0;
3901 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01003902
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003903 for (; index < 16; index++)
3904 {
3905 VTermColor color;
Bram Moolenaaref8c83c2019-04-11 11:40:13 +02003906
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003907 color.red = (unsigned)(rgb[index] >> 16);
3908 color.green = (unsigned)(rgb[index] >> 8) & 255;
3909 color.blue = (unsigned)rgb[index] & 255;
3910 vterm_state_set_palette_color(state, index, &color);
3911 }
3912}
3913
3914/*
3915 * Set the ANSI color palette from a list of colors
3916 */
3917 static int
3918set_ansi_colors_list(VTerm *vterm, list_T *list)
3919{
3920 int n = 0;
3921 long_u rgb[16];
Bram Moolenaarb0992022020-01-30 14:55:42 +01003922 listitem_T *li;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003923
Bram Moolenaarb0992022020-01-30 14:55:42 +01003924 for (li = list->lv_first; li != NULL && n < 16; li = li->li_next, n++)
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003925 {
3926 char_u *color_name;
3927 guicolor_T guicolor;
3928
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003929 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003930 if (color_name == NULL)
3931 return FAIL;
3932
3933 guicolor = GUI_GET_COLOR(color_name);
3934 if (guicolor == INVALCOLOR)
3935 return FAIL;
3936
3937 rgb[n] = GUI_MCH_GET_RGB(guicolor);
3938 }
3939
3940 if (n != 16 || li != NULL)
3941 return FAIL;
3942
3943 set_vterm_palette(vterm, rgb);
3944
3945 return OK;
3946}
3947
3948/*
3949 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
3950 */
3951 static void
3952init_vterm_ansi_colors(VTerm *vterm)
3953{
3954 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
3955
3956 if (var != NULL
3957 && (var->di_tv.v_type != VAR_LIST
3958 || var->di_tv.vval.v_list == NULL
Bram Moolenaarb0992022020-01-30 14:55:42 +01003959 || var->di_tv.vval.v_list->lv_first == &range_list_item
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003960 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL))
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01003961 semsg(_(e_invarg2), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02003962}
3963#endif
3964
Bram Moolenaar52acb112018-03-18 19:20:22 +01003965/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003966 * Handles a "drop" command from the job in the terminal.
3967 * "item" is the file name, "item->li_next" may have options.
3968 */
3969 static void
3970handle_drop_command(listitem_T *item)
3971{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01003972 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003973 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003974 int bufnr;
3975 win_T *wp;
3976 tabpage_T *tp;
3977 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003978 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003979
3980 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
3981 FOR_ALL_TAB_WINDOWS(tp, wp)
3982 {
3983 if (wp->w_buffer->b_fnum == bufnr)
3984 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003985 // buffer is in a window already, go there
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003986 goto_tabpage_win(tp, wp);
3987 return;
3988 }
3989 }
3990
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02003991 vim_memset(&ea, 0, sizeof(ea));
Bram Moolenaar333b80a2018-04-04 22:57:29 +02003992
3993 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
3994 && opt_item->li_tv.vval.v_dict != NULL)
3995 {
3996 dict_T *dict = opt_item->li_tv.vval.v_dict;
3997 char_u *p;
3998
Bram Moolenaar8f667172018-12-14 15:38:31 +01003999 p = dict_get_string(dict, (char_u *)"ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004000 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01004001 p = dict_get_string(dict, (char_u *)"fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004002 if (p != NULL)
4003 {
4004 if (check_ff_value(p) == FAIL)
4005 ch_log(NULL, "Invalid ff argument to drop: %s", p);
4006 else
4007 ea.force_ff = *p;
4008 }
Bram Moolenaar8f667172018-12-14 15:38:31 +01004009 p = dict_get_string(dict, (char_u *)"enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004010 if (p == NULL)
Bram Moolenaar8f667172018-12-14 15:38:31 +01004011 p = dict_get_string(dict, (char_u *)"encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004012 if (p != NULL)
4013 {
Bram Moolenaar51e14382019-05-25 20:21:28 +02004014 ea.cmd = alloc(STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004015 if (ea.cmd != NULL)
4016 {
4017 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
4018 ea.force_enc = 11;
4019 tofree = ea.cmd;
4020 }
4021 }
4022
Bram Moolenaar8f667172018-12-14 15:38:31 +01004023 p = dict_get_string(dict, (char_u *)"bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004024 if (p != NULL)
4025 get_bad_opt(p, &ea);
4026
4027 if (dict_find(dict, (char_u *)"bin", -1) != NULL)
4028 ea.force_bin = FORCE_BIN;
4029 if (dict_find(dict, (char_u *)"binary", -1) != NULL)
4030 ea.force_bin = FORCE_BIN;
4031 if (dict_find(dict, (char_u *)"nobin", -1) != NULL)
4032 ea.force_bin = FORCE_NOBIN;
4033 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL)
4034 ea.force_bin = FORCE_NOBIN;
4035 }
4036
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004037 // open in new window, like ":split fname"
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004038 if (ea.cmd == NULL)
4039 ea.cmd = (char_u *)"split";
4040 ea.arg = fname;
4041 ea.cmdidx = CMD_split;
4042 ex_splitview(&ea);
4043
4044 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004045}
4046
4047/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004048 * Return TRUE if "func" starts with "pat" and "pat" isn't empty.
4049 */
4050 static int
4051is_permitted_term_api(char_u *func, char_u *pat)
4052{
4053 return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
4054}
4055
4056/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004057 * Handles a function call from the job running in a terminal.
4058 * "item" is the function name, "item->li_next" has the arguments.
4059 */
4060 static void
4061handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
4062{
4063 char_u *func;
4064 typval_T argvars[2];
4065 typval_T rettv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02004066 funcexe_T funcexe;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004067
4068 if (item->li_next == NULL)
4069 {
4070 ch_log(channel, "Missing function arguments for call");
4071 return;
4072 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004073 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004074
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004075 if (!is_permitted_term_api(func, term->tl_api))
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004076 {
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004077 ch_log(channel, "Unpermitted function: %s", func);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004078 return;
4079 }
4080
4081 argvars[0].v_type = VAR_NUMBER;
4082 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
4083 argvars[1] = item->li_next->li_tv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02004084 vim_memset(&funcexe, 0, sizeof(funcexe));
4085 funcexe.firstline = 1L;
4086 funcexe.lastline = 1L;
4087 funcexe.evaluate = TRUE;
4088 if (call_func(func, -1, &rettv, 2, argvars, &funcexe) == OK)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004089 {
4090 clear_tv(&rettv);
4091 ch_log(channel, "Function %s called", func);
4092 }
4093 else
4094 ch_log(channel, "Calling function %s failed", func);
4095}
4096
4097/*
4098 * Called by libvterm when it cannot recognize an OSC sequence.
4099 * We recognize a terminal API command.
4100 */
4101 static int
4102parse_osc(const char *command, size_t cmdlen, void *user)
4103{
4104 term_T *term = (term_T *)user;
4105 js_read_T reader;
4106 typval_T tv;
4107 channel_T *channel = term->tl_job == NULL ? NULL
4108 : term->tl_job->jv_channel;
4109
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004110 // We recognize only OSC 5 1 ; {command}
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004111 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004112 return 0; // not handled
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004113
Bram Moolenaar878c96d2018-04-04 23:00:06 +02004114 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3));
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004115 if (reader.js_buf == NULL)
4116 return 1;
4117 reader.js_fill = NULL;
4118 reader.js_used = 0;
4119 if (json_decode(&reader, &tv, 0) == OK
4120 && tv.v_type == VAR_LIST
4121 && tv.vval.v_list != NULL)
4122 {
4123 listitem_T *item = tv.vval.v_list->lv_first;
4124
4125 if (item == NULL)
4126 ch_log(channel, "Missing command");
4127 else
4128 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004129 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004130
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004131 // Make sure an invoked command doesn't delete the buffer (and the
4132 // terminal) under our fingers.
Bram Moolenaara997b452018-04-17 23:24:06 +02004133 ++term->tl_buffer->b_locked;
4134
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004135 item = item->li_next;
4136 if (item == NULL)
4137 ch_log(channel, "Missing argument for %s", cmd);
4138 else if (STRCMP(cmd, "drop") == 0)
4139 handle_drop_command(item);
4140 else if (STRCMP(cmd, "call") == 0)
4141 handle_call_command(term, channel, item);
4142 else
4143 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02004144 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004145 }
4146 }
4147 else
4148 ch_log(channel, "Invalid JSON received");
4149
4150 vim_free(reader.js_buf);
4151 clear_tv(&tv);
4152 return 1;
4153}
4154
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004155/*
4156 * Called by libvterm when it cannot recognize a CSI sequence.
4157 * We recognize the window position report.
4158 */
4159 static int
4160parse_csi(
4161 const char *leader UNUSED,
4162 const long args[],
4163 int argcount,
4164 const char *intermed UNUSED,
4165 char command,
4166 void *user)
4167{
4168 term_T *term = (term_T *)user;
4169 char buf[100];
4170 int len;
4171 int x = 0;
4172 int y = 0;
4173 win_T *wp;
4174
4175 // We recognize only CSI 13 t
4176 if (command != 't' || argcount != 1 || args[0] != 13)
4177 return 0; // not handled
4178
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004179 // When getting the window position is not possible or it fails it results
4180 // in zero/zero.
Bram Moolenaar16c34c32019-04-06 22:01:24 +02004181#if defined(FEAT_GUI) \
4182 || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
4183 || defined(MSWIN)
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004184 (void)ui_get_winpos(&x, &y, (varnumber_T)100);
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004185#endif
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004186
4187 FOR_ALL_WINDOWS(wp)
4188 if (wp->w_buffer == term->tl_buffer)
4189 break;
4190 if (wp != NULL)
4191 {
4192#ifdef FEAT_GUI
4193 if (gui.in_use)
4194 {
4195 x += wp->w_wincol * gui.char_width;
4196 y += W_WINROW(wp) * gui.char_height;
4197 }
4198 else
4199#endif
4200 {
4201 // We roughly estimate the position of the terminal window inside
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004202 // the Vim window by assuming a 10 x 7 character cell.
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004203 x += wp->w_wincol * 7;
4204 y += W_WINROW(wp) * 10;
4205 }
4206 }
4207
4208 len = vim_snprintf(buf, 100, "\x1b[3;%d;%dt", x, y);
4209 channel_send(term->tl_job->jv_channel, get_tty_part(term),
4210 (char_u *)buf, len, NULL);
4211 return 1;
4212}
4213
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004214static VTermParserCallbacks parser_fallbacks = {
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004215 NULL, // text
4216 NULL, // control
4217 NULL, // escape
4218 parse_csi, // csi
4219 parse_osc, // osc
4220 NULL, // dcs
4221 NULL // resize
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004222};
4223
4224/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02004225 * Use Vim's allocation functions for vterm so profiling works.
4226 */
4227 static void *
4228vterm_malloc(size_t size, void *data UNUSED)
4229{
Bram Moolenaar18a4ba22019-05-24 19:39:03 +02004230 return alloc_clear(size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02004231}
4232
4233 static void
4234vterm_memfree(void *ptr, void *data UNUSED)
4235{
4236 vim_free(ptr);
4237}
4238
4239static VTermAllocatorFunctions vterm_allocator = {
4240 &vterm_malloc,
4241 &vterm_memfree
4242};
4243
4244/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004245 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004246 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004247 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004248 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01004249create_vterm(term_T *term, int rows, int cols)
4250{
4251 VTerm *vterm;
4252 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004253 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01004254 VTermValue value;
4255
Bram Moolenaar756ef112018-04-10 12:04:27 +02004256 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004257 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004258 if (vterm == NULL)
4259 return FAIL;
4260
4261 // Allocate screen and state here, so we can bail out if that fails.
4262 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004263 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004264 if (state == NULL || screen == NULL)
4265 {
4266 vterm_free(vterm);
4267 return FAIL;
4268 }
4269
Bram Moolenaar52acb112018-03-18 19:20:22 +01004270 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004271 // TODO: depends on 'encoding'.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004272 vterm_set_utf8(vterm, 1);
4273
Bram Moolenaar219c7d02020-02-01 21:57:29 +01004274 init_default_colors(term, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004275
4276 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004277 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01004278 &term->tl_default_color.fg,
4279 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004280
Bram Moolenaar9e587872019-05-13 20:27:23 +02004281 if (t_colors < 16)
4282 // Less than 16 colors: assume that bold means using a bright color for
4283 // the foreground color.
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004284 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
4285
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004286 // Required to initialize most things.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004287 vterm_screen_reset(screen, 1 /* hard */);
4288
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004289 // Allow using alternate screen.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004290 vterm_screen_enable_altscreen(screen, 1);
4291
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004292 // For unix do not use a blinking cursor. In an xterm this causes the
4293 // cursor to blink if it's blinking in the xterm.
4294 // For Windows we respect the system wide setting.
Bram Moolenaar4f974752019-02-17 17:44:42 +01004295#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004296 if (GetCaretBlinkTime() == INFINITE)
4297 value.boolean = 0;
4298 else
4299 value.boolean = 1;
4300#else
4301 value.boolean = 0;
4302#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004303 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
4304 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004305
4306 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004307}
4308
4309/*
Bram Moolenaar219c7d02020-02-01 21:57:29 +01004310 * Called when 'wincolor' was set.
4311 */
4312 void
4313term_update_colors(void)
4314{
4315 term_T *term = curwin->w_buffer->b_term;
4316
Bram Moolenaar7ba3b912020-02-10 20:34:04 +01004317 if (term->tl_vterm == NULL)
4318 return;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01004319 init_default_colors(term, curwin);
4320 vterm_state_set_default_colors(
4321 vterm_obtain_state(term->tl_vterm),
4322 &term->tl_default_color.fg,
4323 &term->tl_default_color.bg);
4324}
4325
4326/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004327 * Return the text to show for the buffer name and status.
4328 */
4329 char_u *
4330term_get_status_text(term_T *term)
4331{
4332 if (term->tl_status_text == NULL)
4333 {
4334 char_u *txt;
4335 size_t len;
4336
4337 if (term->tl_normal_mode)
4338 {
4339 if (term_job_running(term))
4340 txt = (char_u *)_("Terminal");
4341 else
4342 txt = (char_u *)_("Terminal-finished");
4343 }
4344 else if (term->tl_title != NULL)
4345 txt = term->tl_title;
4346 else if (term_none_open(term))
4347 txt = (char_u *)_("active");
4348 else if (term_job_running(term))
4349 txt = (char_u *)_("running");
4350 else
4351 txt = (char_u *)_("finished");
4352 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
Bram Moolenaar51e14382019-05-25 20:21:28 +02004353 term->tl_status_text = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004354 if (term->tl_status_text != NULL)
4355 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
4356 term->tl_buffer->b_fname, txt);
4357 }
4358 return term->tl_status_text;
4359}
4360
4361/*
4362 * Mark references in jobs of terminals.
4363 */
4364 int
4365set_ref_in_term(int copyID)
4366{
4367 int abort = FALSE;
4368 term_T *term;
4369 typval_T tv;
4370
Bram Moolenaar75a1a942019-06-20 03:45:36 +02004371 for (term = first_term; !abort && term != NULL; term = term->tl_next)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004372 if (term->tl_job != NULL)
4373 {
4374 tv.v_type = VAR_JOB;
4375 tv.vval.v_job = term->tl_job;
4376 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4377 }
4378 return abort;
4379}
4380
4381/*
Bram Moolenaara7c54cf2017-12-01 21:07:20 +01004382 * Cache "Terminal" highlight group colors.
4383 */
4384 void
4385set_terminal_default_colors(int cterm_fg, int cterm_bg)
4386{
4387 term_default_cterm_fg = cterm_fg - 1;
4388 term_default_cterm_bg = cterm_bg - 1;
4389}
4390
4391/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004392 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004393 * Returns NULL when the buffer is not for a terminal window and logs a message
4394 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004395 */
4396 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004397term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004398{
4399 buf_T *buf;
4400
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004401 (void)tv_get_number(&argvars[0]); // issue errmsg if type error
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004402 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01004403 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004404 --emsg_off;
4405 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004406 {
4407 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004408 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004409 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004410 return buf;
4411}
4412
Bram Moolenaard96ff162018-02-18 22:13:29 +01004413 static int
4414same_color(VTermColor *a, VTermColor *b)
4415{
4416 return a->red == b->red
4417 && a->green == b->green
4418 && a->blue == b->blue
4419 && a->ansi_index == b->ansi_index;
4420}
4421
4422 static void
4423dump_term_color(FILE *fd, VTermColor *color)
4424{
4425 fprintf(fd, "%02x%02x%02x%d",
4426 (int)color->red, (int)color->green, (int)color->blue,
4427 (int)color->ansi_index);
4428}
4429
4430/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004431 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01004432 *
4433 * Each screen cell in full is:
4434 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
4435 * {characters} is a space for an empty cell
4436 * For a double-width character "+" is changed to "*" and the next cell is
4437 * skipped.
4438 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
4439 * when "&" use the same as the previous cell.
4440 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
4441 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
4442 * {color-idx} is a number from 0 to 255
4443 *
4444 * Screen cell with same width, attributes and color as the previous one:
4445 * |{characters}
4446 *
4447 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
4448 *
4449 * Repeating the previous screen cell:
4450 * @{count}
4451 */
4452 void
4453f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
4454{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004455 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01004456 term_T *term;
4457 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004458 int max_height = 0;
4459 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004460 stat_T st;
4461 FILE *fd;
4462 VTermPos pos;
4463 VTermScreen *screen;
4464 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004465 VTermState *state;
4466 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004467
4468 if (check_restricted() || check_secure())
4469 return;
4470 if (buf == NULL)
4471 return;
4472 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004473 if (term->tl_vterm == NULL)
4474 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004475 emsg(_("E958: Job already finished"));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004476 return;
4477 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004478
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004479 if (argvars[2].v_type != VAR_UNKNOWN)
4480 {
4481 dict_T *d;
4482
4483 if (argvars[2].v_type != VAR_DICT)
4484 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004485 emsg(_(e_dictreq));
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004486 return;
4487 }
4488 d = argvars[2].vval.v_dict;
4489 if (d != NULL)
4490 {
Bram Moolenaar8f667172018-12-14 15:38:31 +01004491 max_height = dict_get_number(d, (char_u *)"rows");
4492 max_width = dict_get_number(d, (char_u *)"columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004493 }
4494 }
4495
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004496 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004497 if (fname == NULL)
4498 return;
4499 if (mch_stat((char *)fname, &st) >= 0)
4500 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004501 semsg(_("E953: File exists: %s"), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004502 return;
4503 }
4504
Bram Moolenaard96ff162018-02-18 22:13:29 +01004505 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
4506 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004507 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004508 return;
4509 }
4510
4511 vim_memset(&prev_cell, 0, sizeof(prev_cell));
4512
4513 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004514 state = vterm_obtain_state(term->tl_vterm);
4515 vterm_state_get_cursorpos(state, &cursor_pos);
4516
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004517 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
4518 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004519 {
4520 int repeat = 0;
4521
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004522 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
4523 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004524 {
4525 VTermScreenCell cell;
4526 int same_attr;
4527 int same_chars = TRUE;
4528 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004529 int is_cursor_pos = (pos.col == cursor_pos.col
4530 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004531
4532 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
4533 vim_memset(&cell, 0, sizeof(cell));
4534
4535 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
4536 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01004537 int c = cell.chars[i];
4538 int pc = prev_cell.chars[i];
4539
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004540 // For the first character NUL is the same as space.
Bram Moolenaar47015b82018-03-23 22:10:34 +01004541 if (i == 0)
4542 {
4543 c = (c == NUL) ? ' ' : c;
4544 pc = (pc == NUL) ? ' ' : pc;
4545 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004546 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004547 same_chars = FALSE;
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02004548 if (c == NUL || pc == NUL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004549 break;
4550 }
4551 same_attr = vtermAttr2hl(cell.attrs)
4552 == vtermAttr2hl(prev_cell.attrs)
4553 && same_color(&cell.fg, &prev_cell.fg)
4554 && same_color(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004555 if (same_chars && cell.width == prev_cell.width && same_attr
4556 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004557 {
4558 ++repeat;
4559 }
4560 else
4561 {
4562 if (repeat > 0)
4563 {
4564 fprintf(fd, "@%d", repeat);
4565 repeat = 0;
4566 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004567 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004568
4569 if (cell.chars[0] == NUL)
4570 fputs(" ", fd);
4571 else
4572 {
4573 char_u charbuf[10];
4574 int len;
4575
4576 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
4577 && cell.chars[i] != NUL; ++i)
4578 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02004579 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004580 fwrite(charbuf, len, 1, fd);
4581 }
4582 }
4583
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004584 // When only the characters differ we don't write anything, the
4585 // following "|", "@" or NL will indicate using the same
4586 // attributes.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004587 if (cell.width != prev_cell.width || !same_attr)
4588 {
4589 if (cell.width == 2)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004590 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004591 else
4592 fputs("+", fd);
4593
4594 if (same_attr)
4595 {
4596 fputs("&", fd);
4597 }
4598 else
4599 {
4600 fprintf(fd, "%d", vtermAttr2hl(cell.attrs));
4601 if (same_color(&cell.fg, &prev_cell.fg))
4602 fputs("&", fd);
4603 else
4604 {
4605 fputs("#", fd);
4606 dump_term_color(fd, &cell.fg);
4607 }
4608 if (same_color(&cell.bg, &prev_cell.bg))
4609 fputs("&", fd);
4610 else
4611 {
4612 fputs("#", fd);
4613 dump_term_color(fd, &cell.bg);
4614 }
4615 }
4616 }
4617
4618 prev_cell = cell;
4619 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004620
4621 if (cell.width == 2)
4622 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004623 }
4624 if (repeat > 0)
4625 fprintf(fd, "@%d", repeat);
4626 fputs("\n", fd);
4627 }
4628
4629 fclose(fd);
4630}
4631
4632/*
4633 * Called when a dump is corrupted. Put a breakpoint here when debugging.
4634 */
4635 static void
4636dump_is_corrupt(garray_T *gap)
4637{
4638 ga_concat(gap, (char_u *)"CORRUPT");
4639}
4640
4641 static void
4642append_cell(garray_T *gap, cellattr_T *cell)
4643{
4644 if (ga_grow(gap, 1) == OK)
4645 {
4646 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
4647 ++gap->ga_len;
4648 }
4649}
4650
4651/*
4652 * Read the dump file from "fd" and append lines to the current buffer.
4653 * Return the cell width of the longest line.
4654 */
4655 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01004656read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004657{
4658 int c;
4659 garray_T ga_text;
4660 garray_T ga_cell;
4661 char_u *prev_char = NULL;
4662 int attr = 0;
4663 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004664 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004665 term_T *term = curbuf->b_term;
4666 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004667 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004668
4669 ga_init2(&ga_text, 1, 90);
4670 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
4671 vim_memset(&cell, 0, sizeof(cell));
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004672 vim_memset(&empty_cell, 0, sizeof(empty_cell));
Bram Moolenaar9271d052018-02-25 21:39:46 +01004673 cursor_pos->row = -1;
4674 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004675
4676 c = fgetc(fd);
4677 for (;;)
4678 {
4679 if (c == EOF)
4680 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02004681 if (c == '\r')
4682 {
4683 // DOS line endings? Ignore.
4684 c = fgetc(fd);
4685 }
4686 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004687 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004688 // End of a line: append it to the buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01004689 if (ga_text.ga_data == NULL)
4690 dump_is_corrupt(&ga_text);
4691 if (ga_grow(&term->tl_scrollback, 1) == OK)
4692 {
4693 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
4694 + term->tl_scrollback.ga_len;
4695
4696 if (max_cells < ga_cell.ga_len)
4697 max_cells = ga_cell.ga_len;
4698 line->sb_cols = ga_cell.ga_len;
4699 line->sb_cells = ga_cell.ga_data;
4700 line->sb_fill_attr = term->tl_default_color;
4701 ++term->tl_scrollback.ga_len;
4702 ga_init(&ga_cell);
4703
4704 ga_append(&ga_text, NUL);
4705 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4706 ga_text.ga_len, FALSE);
4707 }
4708 else
4709 ga_clear(&ga_cell);
4710 ga_text.ga_len = 0;
4711
4712 c = fgetc(fd);
4713 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01004714 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004715 {
4716 int prev_len = ga_text.ga_len;
4717
Bram Moolenaar9271d052018-02-25 21:39:46 +01004718 if (c == '>')
4719 {
4720 if (cursor_pos->row != -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004721 dump_is_corrupt(&ga_text); // duplicate cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01004722 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
4723 cursor_pos->col = ga_cell.ga_len;
4724 }
4725
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004726 // normal character(s) followed by "+", "*", "|", "@" or NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01004727 c = fgetc(fd);
4728 if (c != EOF)
4729 ga_append(&ga_text, c);
4730 for (;;)
4731 {
4732 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01004733 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01004734 || c == EOF || c == '\n')
4735 break;
4736 ga_append(&ga_text, c);
4737 }
4738
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004739 // save the character for repeating it
Bram Moolenaard96ff162018-02-18 22:13:29 +01004740 vim_free(prev_char);
4741 if (ga_text.ga_data != NULL)
4742 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
4743 ga_text.ga_len - prev_len);
4744
Bram Moolenaar9271d052018-02-25 21:39:46 +01004745 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01004746 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004747 // use all attributes from previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01004748 }
4749 else if (c == '+' || c == '*')
4750 {
4751 int is_bg;
4752
4753 cell.width = c == '+' ? 1 : 2;
4754
4755 c = fgetc(fd);
4756 if (c == '&')
4757 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004758 // use same attr as previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01004759 c = fgetc(fd);
4760 }
4761 else if (isdigit(c))
4762 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004763 // get the decimal attribute
Bram Moolenaard96ff162018-02-18 22:13:29 +01004764 attr = 0;
4765 while (isdigit(c))
4766 {
4767 attr = attr * 10 + (c - '0');
4768 c = fgetc(fd);
4769 }
4770 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004771
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004772 // is_bg == 0: fg, is_bg == 1: bg
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004773 for (is_bg = 0; is_bg <= 1; ++is_bg)
4774 {
4775 if (c == '&')
4776 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004777 // use same color as previous cell
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004778 c = fgetc(fd);
4779 }
4780 else if (c == '#')
4781 {
4782 int red, green, blue, index = 0;
4783
4784 c = fgetc(fd);
4785 red = hex2nr(c);
4786 c = fgetc(fd);
4787 red = (red << 4) + hex2nr(c);
4788 c = fgetc(fd);
4789 green = hex2nr(c);
4790 c = fgetc(fd);
4791 green = (green << 4) + hex2nr(c);
4792 c = fgetc(fd);
4793 blue = hex2nr(c);
4794 c = fgetc(fd);
4795 blue = (blue << 4) + hex2nr(c);
4796 c = fgetc(fd);
4797 if (!isdigit(c))
4798 dump_is_corrupt(&ga_text);
4799 while (isdigit(c))
4800 {
4801 index = index * 10 + (c - '0');
4802 c = fgetc(fd);
4803 }
4804
4805 if (is_bg)
4806 {
4807 cell.bg.red = red;
4808 cell.bg.green = green;
4809 cell.bg.blue = blue;
4810 cell.bg.ansi_index = index;
4811 }
4812 else
4813 {
4814 cell.fg.red = red;
4815 cell.fg.green = green;
4816 cell.fg.blue = blue;
4817 cell.fg.ansi_index = index;
4818 }
4819 }
4820 else
4821 dump_is_corrupt(&ga_text);
4822 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004823 }
4824 else
4825 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004826 }
4827 else
4828 dump_is_corrupt(&ga_text);
4829
4830 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01004831 if (cell.width == 2)
4832 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004833 }
4834 else if (c == '@')
4835 {
4836 if (prev_char == NULL)
4837 dump_is_corrupt(&ga_text);
4838 else
4839 {
4840 int count = 0;
4841
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004842 // repeat previous character, get the count
Bram Moolenaard96ff162018-02-18 22:13:29 +01004843 for (;;)
4844 {
4845 c = fgetc(fd);
4846 if (!isdigit(c))
4847 break;
4848 count = count * 10 + (c - '0');
4849 }
4850
4851 while (count-- > 0)
4852 {
4853 ga_concat(&ga_text, prev_char);
4854 append_cell(&ga_cell, &cell);
4855 }
4856 }
4857 }
4858 else
4859 {
4860 dump_is_corrupt(&ga_text);
4861 c = fgetc(fd);
4862 }
4863 }
4864
4865 if (ga_text.ga_len > 0)
4866 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004867 // trailing characters after last NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01004868 dump_is_corrupt(&ga_text);
4869 ga_append(&ga_text, NUL);
4870 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
4871 ga_text.ga_len, FALSE);
4872 }
4873
4874 ga_clear(&ga_text);
Bram Moolenaar86173482019-10-01 17:02:16 +02004875 ga_clear(&ga_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004876 vim_free(prev_char);
4877
4878 return max_cells;
4879}
4880
4881/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02004882 * Return an allocated string with at least "text_width" "=" characters and
4883 * "fname" inserted in the middle.
4884 */
4885 static char_u *
4886get_separator(int text_width, char_u *fname)
4887{
4888 int width = MAX(text_width, curwin->w_width);
4889 char_u *textline;
4890 int fname_size;
4891 char_u *p = fname;
4892 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004893 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02004894
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02004895 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02004896 if (textline == NULL)
4897 return NULL;
4898
4899 fname_size = vim_strsize(fname);
4900 if (fname_size < width - 8)
4901 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004902 // enough room, don't use the full window width
Bram Moolenaar4a696342018-04-05 18:45:26 +02004903 width = MAX(text_width, fname_size + 8);
4904 }
4905 else if (fname_size > width - 8)
4906 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004907 // full name doesn't fit, use only the tail
Bram Moolenaar4a696342018-04-05 18:45:26 +02004908 p = gettail(fname);
4909 fname_size = vim_strsize(p);
4910 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004911 // skip characters until the name fits
Bram Moolenaar4a696342018-04-05 18:45:26 +02004912 while (fname_size > width - 8)
4913 {
4914 p += (*mb_ptr2len)(p);
4915 fname_size = vim_strsize(p);
4916 }
4917
4918 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
4919 textline[i] = '=';
4920 textline[i++] = ' ';
4921
4922 STRCPY(textline + i, p);
4923 off = STRLEN(textline);
4924 textline[off] = ' ';
4925 for (i = 1; i < (width - fname_size) / 2; ++i)
4926 textline[off + i] = '=';
4927 textline[off + i] = NUL;
4928
4929 return textline;
4930}
4931
4932/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01004933 * Common for "term_dumpdiff()" and "term_dumpload()".
4934 */
4935 static void
4936term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
4937{
4938 jobopt_T opt;
Bram Moolenaar87abab92019-06-03 21:14:59 +02004939 buf_T *buf = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004940 char_u buf1[NUMBUFLEN];
4941 char_u buf2[NUMBUFLEN];
4942 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004943 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004944 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004945 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01004946 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004947 char_u *textline = NULL;
4948
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004949 // First open the files. If this fails bail out.
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004950 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004951 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004952 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004953 if (fname1 == NULL || (do_diff && fname2 == NULL))
4954 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004955 emsg(_(e_invarg));
Bram Moolenaard96ff162018-02-18 22:13:29 +01004956 return;
4957 }
4958 fd1 = mch_fopen((char *)fname1, READBIN);
4959 if (fd1 == NULL)
4960 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004961 semsg(_(e_notread), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004962 return;
4963 }
4964 if (do_diff)
4965 {
4966 fd2 = mch_fopen((char *)fname2, READBIN);
4967 if (fd2 == NULL)
4968 {
4969 fclose(fd1);
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01004970 semsg(_(e_notread), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004971 return;
4972 }
4973 }
4974
4975 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004976 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
4977 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
4978 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
4979 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
4980 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004981
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004982 if (opt.jo_term_name == NULL)
4983 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01004984 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004985
Bram Moolenaar51e14382019-05-25 20:21:28 +02004986 fname_tofree = alloc(len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01004987 if (fname_tofree != NULL)
4988 {
4989 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
4990 opt.jo_term_name = fname_tofree;
4991 }
4992 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004993
Bram Moolenaar87abab92019-06-03 21:14:59 +02004994 if (opt.jo_bufnr_buf != NULL)
4995 {
4996 win_T *wp = buf_jump_open_win(opt.jo_bufnr_buf);
4997
4998 // With "bufnr" argument: enter the window with this buffer and make it
4999 // empty.
5000 if (wp == NULL)
5001 semsg(_(e_invarg2), "bufnr");
5002 else
5003 {
5004 buf = curbuf;
5005 while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
5006 ml_delete((linenr_T)1, FALSE);
Bram Moolenaar86173482019-10-01 17:02:16 +02005007 free_scrollback(curbuf->b_term);
Bram Moolenaar87abab92019-06-03 21:14:59 +02005008 redraw_later(NOT_VALID);
5009 }
5010 }
5011 else
5012 // Create a new terminal window.
5013 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
5014
Bram Moolenaard96ff162018-02-18 22:13:29 +01005015 if (buf != NULL && buf->b_term != NULL)
5016 {
5017 int i;
5018 linenr_T bot_lnum;
5019 linenr_T lnum;
5020 term_T *term = buf->b_term;
5021 int width;
5022 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005023 VTermPos cursor_pos1;
5024 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005025
Bram Moolenaar219c7d02020-02-01 21:57:29 +01005026 init_default_colors(term, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01005027
Bram Moolenaard96ff162018-02-18 22:13:29 +01005028 rettv->vval.v_number = buf->b_fnum;
5029
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005030 // read the files, fill the buffer with the diff
Bram Moolenaar9271d052018-02-25 21:39:46 +01005031 width = read_dump_file(fd1, &cursor_pos1);
5032
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005033 // position the cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01005034 if (cursor_pos1.row >= 0)
5035 {
5036 curwin->w_cursor.lnum = cursor_pos1.row + 1;
5037 coladvance(cursor_pos1.col);
5038 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005039
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005040 // Delete the empty line that was in the empty buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005041 ml_delete(1, FALSE);
5042
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005043 // For term_dumpload() we are done here.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005044 if (!do_diff)
5045 goto theend;
5046
5047 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
5048
Bram Moolenaar4a696342018-04-05 18:45:26 +02005049 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005050 if (textline == NULL)
5051 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005052 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
5053 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
5054 vim_free(textline);
5055
5056 textline = get_separator(width, fname2);
5057 if (textline == NULL)
5058 goto theend;
5059 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
5060 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005061 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005062
5063 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005064 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005065 if (width2 > width)
5066 {
5067 vim_free(textline);
5068 textline = alloc(width2 + 1);
5069 if (textline == NULL)
5070 goto theend;
5071 width = width2;
5072 textline[width] = NUL;
5073 }
5074 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
5075
5076 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
5077 {
5078 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
5079 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005080 // bottom part has fewer rows, fill with "-"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005081 for (i = 0; i < width; ++i)
5082 textline[i] = '-';
5083 }
5084 else
5085 {
5086 char_u *line1;
5087 char_u *line2;
5088 char_u *p1;
5089 char_u *p2;
5090 int col;
5091 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5092 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
5093 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
5094 ->sb_cells;
5095
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005096 // Make a copy, getting the second line will invalidate it.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005097 line1 = vim_strsave(ml_get(lnum));
5098 if (line1 == NULL)
5099 break;
5100 p1 = line1;
5101
5102 line2 = ml_get(lnum + bot_lnum);
5103 p2 = line2;
5104 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
5105 {
5106 int len1 = utfc_ptr2len(p1);
5107 int len2 = utfc_ptr2len(p2);
5108
5109 textline[col] = ' ';
5110 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005111 // text differs
Bram Moolenaard96ff162018-02-18 22:13:29 +01005112 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01005113 else if (lnum == cursor_pos1.row + 1
5114 && col == cursor_pos1.col
5115 && (cursor_pos1.row != cursor_pos2.row
5116 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005117 // cursor in first but not in second
Bram Moolenaar9271d052018-02-25 21:39:46 +01005118 textline[col] = '>';
5119 else if (lnum == cursor_pos2.row + 1
5120 && col == cursor_pos2.col
5121 && (cursor_pos1.row != cursor_pos2.row
5122 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005123 // cursor in second but not in first
Bram Moolenaar9271d052018-02-25 21:39:46 +01005124 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01005125 else if (cellattr1 != NULL && cellattr2 != NULL)
5126 {
5127 if ((cellattr1 + col)->width
5128 != (cellattr2 + col)->width)
5129 textline[col] = 'w';
5130 else if (!same_color(&(cellattr1 + col)->fg,
5131 &(cellattr2 + col)->fg))
5132 textline[col] = 'f';
5133 else if (!same_color(&(cellattr1 + col)->bg,
5134 &(cellattr2 + col)->bg))
5135 textline[col] = 'b';
5136 else if (vtermAttr2hl((cellattr1 + col)->attrs)
5137 != vtermAttr2hl(((cellattr2 + col)->attrs)))
5138 textline[col] = 'a';
5139 }
5140 p1 += len1;
5141 p2 += len2;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005142 // TODO: handle different width
Bram Moolenaard96ff162018-02-18 22:13:29 +01005143 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005144
5145 while (col < width)
5146 {
5147 if (*p1 == NUL && *p2 == NUL)
5148 textline[col] = '?';
5149 else if (*p1 == NUL)
5150 {
5151 textline[col] = '+';
5152 p2 += utfc_ptr2len(p2);
5153 }
5154 else
5155 {
5156 textline[col] = '-';
5157 p1 += utfc_ptr2len(p1);
5158 }
5159 ++col;
5160 }
Bram Moolenaar81aa0f52019-02-14 23:23:19 +01005161
5162 vim_free(line1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005163 }
5164 if (add_empty_scrollback(term, &term->tl_default_color,
5165 term->tl_top_diff_rows) == OK)
5166 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5167 ++bot_lnum;
5168 }
5169
5170 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
5171 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005172 // bottom part has more rows, fill with "+"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005173 for (i = 0; i < width; ++i)
5174 textline[i] = '+';
5175 if (add_empty_scrollback(term, &term->tl_default_color,
5176 term->tl_top_diff_rows) == OK)
5177 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5178 ++lnum;
5179 ++bot_lnum;
5180 }
5181
5182 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005183
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005184 // looks better without wrapping
Bram Moolenaar4a696342018-04-05 18:45:26 +02005185 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005186 }
5187
5188theend:
5189 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005190 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005191 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005192 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005193 fclose(fd2);
5194}
5195
5196/*
5197 * If the current buffer shows the output of term_dumpdiff(), swap the top and
5198 * bottom files.
5199 * Return FAIL when this is not possible.
5200 */
5201 int
5202term_swap_diff()
5203{
5204 term_T *term = curbuf->b_term;
5205 linenr_T line_count;
5206 linenr_T top_rows;
5207 linenr_T bot_rows;
5208 linenr_T bot_start;
5209 linenr_T lnum;
5210 char_u *p;
5211 sb_line_T *sb_line;
5212
5213 if (term == NULL
5214 || !term_is_finished(curbuf)
5215 || term->tl_top_diff_rows == 0
5216 || term->tl_scrollback.ga_len == 0)
5217 return FAIL;
5218
5219 line_count = curbuf->b_ml.ml_line_count;
5220 top_rows = term->tl_top_diff_rows;
5221 bot_rows = term->tl_bot_diff_rows;
5222 bot_start = line_count - bot_rows;
5223 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5224
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005225 // move lines from top to above the bottom part
Bram Moolenaard96ff162018-02-18 22:13:29 +01005226 for (lnum = 1; lnum <= top_rows; ++lnum)
5227 {
5228 p = vim_strsave(ml_get(1));
5229 if (p == NULL)
5230 return OK;
5231 ml_append(bot_start, p, 0, FALSE);
5232 ml_delete(1, FALSE);
5233 vim_free(p);
5234 }
5235
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005236 // move lines from bottom to the top
Bram Moolenaard96ff162018-02-18 22:13:29 +01005237 for (lnum = 1; lnum <= bot_rows; ++lnum)
5238 {
5239 p = vim_strsave(ml_get(bot_start + lnum));
5240 if (p == NULL)
5241 return OK;
5242 ml_delete(bot_start + lnum, FALSE);
5243 ml_append(lnum - 1, p, 0, FALSE);
5244 vim_free(p);
5245 }
5246
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005247 // move top title to bottom
5248 p = vim_strsave(ml_get(bot_rows + 1));
5249 if (p == NULL)
5250 return OK;
5251 ml_append(line_count - top_rows - 1, p, 0, FALSE);
5252 ml_delete(bot_rows + 1, FALSE);
5253 vim_free(p);
5254
5255 // move bottom title to top
5256 p = vim_strsave(ml_get(line_count - top_rows));
5257 if (p == NULL)
5258 return OK;
5259 ml_delete(line_count - top_rows, FALSE);
5260 ml_append(bot_rows, p, 0, FALSE);
5261 vim_free(p);
5262
Bram Moolenaard96ff162018-02-18 22:13:29 +01005263 if (top_rows == bot_rows)
5264 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005265 // rows counts are equal, can swap cell properties
Bram Moolenaard96ff162018-02-18 22:13:29 +01005266 for (lnum = 0; lnum < top_rows; ++lnum)
5267 {
5268 sb_line_T temp;
5269
5270 temp = *(sb_line + lnum);
5271 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
5272 *(sb_line + bot_start + lnum) = temp;
5273 }
5274 }
5275 else
5276 {
5277 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005278 sb_line_T *temp = alloc(size);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005279
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005280 // need to copy cell properties into temp memory
Bram Moolenaard96ff162018-02-18 22:13:29 +01005281 if (temp != NULL)
5282 {
5283 mch_memmove(temp, term->tl_scrollback.ga_data, size);
5284 mch_memmove(term->tl_scrollback.ga_data,
5285 temp + bot_start,
5286 sizeof(sb_line_T) * bot_rows);
5287 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
5288 temp + top_rows,
5289 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
5290 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
5291 + line_count - top_rows,
5292 temp,
5293 sizeof(sb_line_T) * top_rows);
5294 vim_free(temp);
5295 }
5296 }
5297
5298 term->tl_top_diff_rows = bot_rows;
5299 term->tl_bot_diff_rows = top_rows;
5300
5301 update_screen(NOT_VALID);
5302 return OK;
5303}
5304
5305/*
5306 * "term_dumpdiff(filename, filename, options)" function
5307 */
5308 void
5309f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
5310{
5311 term_load_dump(argvars, rettv, TRUE);
5312}
5313
5314/*
5315 * "term_dumpload(filename, options)" function
5316 */
5317 void
5318f_term_dumpload(typval_T *argvars, typval_T *rettv)
5319{
5320 term_load_dump(argvars, rettv, FALSE);
5321}
5322
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005323/*
5324 * "term_getaltscreen(buf)" function
5325 */
5326 void
5327f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
5328{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005329 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005330
5331 if (buf == NULL)
5332 return;
5333 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
5334}
5335
5336/*
5337 * "term_getattr(attr, name)" function
5338 */
5339 void
5340f_term_getattr(typval_T *argvars, typval_T *rettv)
5341{
5342 int attr;
5343 size_t i;
5344 char_u *name;
5345
5346 static struct {
5347 char *name;
5348 int attr;
5349 } attrs[] = {
5350 {"bold", HL_BOLD},
5351 {"italic", HL_ITALIC},
5352 {"underline", HL_UNDERLINE},
5353 {"strike", HL_STRIKETHROUGH},
5354 {"reverse", HL_INVERSE},
5355 };
5356
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005357 attr = tv_get_number(&argvars[0]);
5358 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005359 if (name == NULL)
5360 return;
5361
Bram Moolenaar7ee80f72019-09-08 20:55:06 +02005362 if (attr > HL_ALL)
5363 attr = syn_attr2attr(attr);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005364 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
5365 if (STRCMP(name, attrs[i].name) == 0)
5366 {
5367 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
5368 break;
5369 }
5370}
5371
5372/*
5373 * "term_getcursor(buf)" function
5374 */
5375 void
5376f_term_getcursor(typval_T *argvars, typval_T *rettv)
5377{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005378 buf_T *buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005379 term_T *term;
5380 list_T *l;
5381 dict_T *d;
5382
5383 if (rettv_list_alloc(rettv) == FAIL)
5384 return;
5385 if (buf == NULL)
5386 return;
5387 term = buf->b_term;
5388
5389 l = rettv->vval.v_list;
5390 list_append_number(l, term->tl_cursor_pos.row + 1);
5391 list_append_number(l, term->tl_cursor_pos.col + 1);
5392
5393 d = dict_alloc();
5394 if (d != NULL)
5395 {
Bram Moolenaare0be1672018-07-08 16:50:37 +02005396 dict_add_number(d, "visible", term->tl_cursor_visible);
5397 dict_add_number(d, "blink", blink_state_is_inverted()
5398 ? !term->tl_cursor_blink : term->tl_cursor_blink);
5399 dict_add_number(d, "shape", term->tl_cursor_shape);
5400 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005401 list_append_dict(l, d);
5402 }
5403}
5404
5405/*
5406 * "term_getjob(buf)" function
5407 */
5408 void
5409f_term_getjob(typval_T *argvars, typval_T *rettv)
5410{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005411 buf_T *buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005412
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005413 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005414 {
5415 rettv->v_type = VAR_SPECIAL;
5416 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005417 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005418 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005419
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005420 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005421 rettv->vval.v_job = buf->b_term->tl_job;
5422 if (rettv->vval.v_job != NULL)
5423 ++rettv->vval.v_job->jv_refcount;
5424}
5425
5426 static int
5427get_row_number(typval_T *tv, term_T *term)
5428{
5429 if (tv->v_type == VAR_STRING
5430 && tv->vval.v_string != NULL
5431 && STRCMP(tv->vval.v_string, ".") == 0)
5432 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005433 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005434}
5435
5436/*
5437 * "term_getline(buf, row)" function
5438 */
5439 void
5440f_term_getline(typval_T *argvars, typval_T *rettv)
5441{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005442 buf_T *buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005443 term_T *term;
5444 int row;
5445
5446 rettv->v_type = VAR_STRING;
5447 if (buf == NULL)
5448 return;
5449 term = buf->b_term;
5450 row = get_row_number(&argvars[1], term);
5451
5452 if (term->tl_vterm == NULL)
5453 {
5454 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
5455
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005456 // vterm is finished, get the text from the buffer
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005457 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
5458 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
5459 }
5460 else
5461 {
5462 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
5463 VTermRect rect;
5464 int len;
5465 char_u *p;
5466
5467 if (row < 0 || row >= term->tl_rows)
5468 return;
5469 len = term->tl_cols * MB_MAXBYTES + 1;
5470 p = alloc(len);
5471 if (p == NULL)
5472 return;
5473 rettv->vval.v_string = p;
5474
5475 rect.start_col = 0;
5476 rect.end_col = term->tl_cols;
5477 rect.start_row = row;
5478 rect.end_row = row + 1;
5479 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
5480 }
5481}
5482
5483/*
5484 * "term_getscrolled(buf)" function
5485 */
5486 void
5487f_term_getscrolled(typval_T *argvars, typval_T *rettv)
5488{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005489 buf_T *buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005490
5491 if (buf == NULL)
5492 return;
5493 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
5494}
5495
5496/*
5497 * "term_getsize(buf)" function
5498 */
5499 void
5500f_term_getsize(typval_T *argvars, typval_T *rettv)
5501{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005502 buf_T *buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005503 list_T *l;
5504
5505 if (rettv_list_alloc(rettv) == FAIL)
5506 return;
5507 if (buf == NULL)
5508 return;
5509
5510 l = rettv->vval.v_list;
5511 list_append_number(l, buf->b_term->tl_rows);
5512 list_append_number(l, buf->b_term->tl_cols);
5513}
5514
5515/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02005516 * "term_setsize(buf, rows, cols)" function
5517 */
5518 void
5519f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5520{
5521 buf_T *buf = term_get_buf(argvars, "term_setsize()");
5522 term_T *term;
5523 varnumber_T rows, cols;
5524
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005525 if (buf == NULL)
5526 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005527 emsg(_("E955: Not a terminal buffer"));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02005528 return;
5529 }
5530 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02005531 return;
5532 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005533 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005534 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005535 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02005536 cols = cols <= 0 ? term->tl_cols : cols;
5537 vterm_set_size(term->tl_vterm, rows, cols);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005538 // handle_resize() will resize the windows
Bram Moolenaara42d3632018-04-14 17:05:38 +02005539
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005540 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaara42d3632018-04-14 17:05:38 +02005541 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
5542 term_report_winsize(term, term->tl_rows, term->tl_cols);
5543}
5544
5545/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005546 * "term_getstatus(buf)" function
5547 */
5548 void
5549f_term_getstatus(typval_T *argvars, typval_T *rettv)
5550{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005551 buf_T *buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005552 term_T *term;
5553 char_u val[100];
5554
5555 rettv->v_type = VAR_STRING;
5556 if (buf == NULL)
5557 return;
5558 term = buf->b_term;
5559
5560 if (term_job_running(term))
5561 STRCPY(val, "running");
5562 else
5563 STRCPY(val, "finished");
5564 if (term->tl_normal_mode)
5565 STRCAT(val, ",normal");
5566 rettv->vval.v_string = vim_strsave(val);
5567}
5568
5569/*
5570 * "term_gettitle(buf)" function
5571 */
5572 void
5573f_term_gettitle(typval_T *argvars, typval_T *rettv)
5574{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005575 buf_T *buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005576
5577 rettv->v_type = VAR_STRING;
5578 if (buf == NULL)
5579 return;
5580
5581 if (buf->b_term->tl_title != NULL)
5582 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
5583}
5584
5585/*
5586 * "term_gettty(buf)" function
5587 */
5588 void
5589f_term_gettty(typval_T *argvars, typval_T *rettv)
5590{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005591 buf_T *buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar9b50f362018-05-07 20:10:17 +02005592 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005593 int num = 0;
5594
5595 rettv->v_type = VAR_STRING;
5596 if (buf == NULL)
5597 return;
5598 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005599 num = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005600
5601 switch (num)
5602 {
5603 case 0:
5604 if (buf->b_term->tl_job != NULL)
5605 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005606 break;
5607 case 1:
5608 if (buf->b_term->tl_job != NULL)
5609 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005610 break;
5611 default:
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005612 semsg(_(e_invarg2), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005613 return;
5614 }
5615 if (p != NULL)
5616 rettv->vval.v_string = vim_strsave(p);
5617}
5618
5619/*
5620 * "term_list()" function
5621 */
5622 void
5623f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
5624{
5625 term_T *tp;
5626 list_T *l;
5627
5628 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
5629 return;
5630
5631 l = rettv->vval.v_list;
5632 for (tp = first_term; tp != NULL; tp = tp->tl_next)
5633 if (tp != NULL && tp->tl_buffer != NULL)
5634 if (list_append_number(l,
5635 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
5636 return;
5637}
5638
5639/*
5640 * "term_scrape(buf, row)" function
5641 */
5642 void
5643f_term_scrape(typval_T *argvars, typval_T *rettv)
5644{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005645 buf_T *buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005646 VTermScreen *screen = NULL;
5647 VTermPos pos;
5648 list_T *l;
5649 term_T *term;
5650 char_u *p;
5651 sb_line_T *line;
5652
5653 if (rettv_list_alloc(rettv) == FAIL)
5654 return;
5655 if (buf == NULL)
5656 return;
5657 term = buf->b_term;
5658
5659 l = rettv->vval.v_list;
5660 pos.row = get_row_number(&argvars[1], term);
5661
5662 if (term->tl_vterm != NULL)
5663 {
5664 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01005665 if (screen == NULL) // can't really happen
5666 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005667 p = NULL;
5668 line = NULL;
5669 }
5670 else
5671 {
5672 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
5673
5674 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
5675 return;
5676 p = ml_get_buf(buf, lnum + 1, FALSE);
5677 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
5678 }
5679
5680 for (pos.col = 0; pos.col < term->tl_cols; )
5681 {
5682 dict_T *dcell;
5683 int width;
5684 VTermScreenCellAttrs attrs;
5685 VTermColor fg, bg;
5686 char_u rgb[8];
5687 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
5688 int off = 0;
5689 int i;
5690
5691 if (screen == NULL)
5692 {
5693 cellattr_T *cellattr;
5694 int len;
5695
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005696 // vterm has finished, get the cell from scrollback
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005697 if (pos.col >= line->sb_cols)
5698 break;
5699 cellattr = line->sb_cells + pos.col;
5700 width = cellattr->width;
5701 attrs = cellattr->attrs;
5702 fg = cellattr->fg;
5703 bg = cellattr->bg;
Bram Moolenaar1614a142019-10-06 22:00:13 +02005704 len = mb_ptr2len(p);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005705 mch_memmove(mbs, p, len);
5706 mbs[len] = NUL;
5707 p += len;
5708 }
5709 else
5710 {
5711 VTermScreenCell cell;
5712 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
5713 break;
5714 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5715 {
5716 if (cell.chars[i] == 0)
5717 break;
5718 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
5719 }
5720 mbs[off] = NUL;
5721 width = cell.width;
5722 attrs = cell.attrs;
5723 fg = cell.fg;
5724 bg = cell.bg;
5725 }
5726 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01005727 if (dcell == NULL)
5728 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005729 list_append_dict(l, dcell);
5730
Bram Moolenaare0be1672018-07-08 16:50:37 +02005731 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005732
5733 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5734 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005735 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005736 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
5737 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02005738 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005739
Bram Moolenaar219c7d02020-02-01 21:57:29 +01005740 dict_add_number(dcell, "attr", cell2attr(NULL, attrs, fg, bg));
Bram Moolenaare0be1672018-07-08 16:50:37 +02005741 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005742
5743 ++pos.col;
5744 if (width == 2)
5745 ++pos.col;
5746 }
5747}
5748
5749/*
5750 * "term_sendkeys(buf, keys)" function
5751 */
5752 void
Bram Moolenaar3a05ce62020-03-11 19:30:01 +01005753f_term_sendkeys(typval_T *argvars, typval_T *rettv UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005754{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005755 buf_T *buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005756 char_u *msg;
5757 term_T *term;
5758
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005759 if (buf == NULL)
5760 return;
5761
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005762 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005763 if (msg == NULL)
5764 return;
5765 term = buf->b_term;
5766 if (term->tl_vterm == NULL)
5767 return;
5768
5769 while (*msg != NUL)
5770 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02005771 int c;
5772
5773 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
5774 {
5775 c = TO_SPECIAL(msg[1], msg[2]);
5776 msg += 3;
5777 }
5778 else
5779 {
5780 c = PTR2CHAR(msg);
5781 msg += MB_CPTR2LEN(msg);
5782 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01005783 send_keys_to_term(term, c, 0, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005784 }
5785}
5786
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005787#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
5788/*
5789 * "term_getansicolors(buf)" function
5790 */
5791 void
5792f_term_getansicolors(typval_T *argvars, typval_T *rettv)
5793{
5794 buf_T *buf = term_get_buf(argvars, "term_getansicolors()");
5795 term_T *term;
5796 VTermState *state;
5797 VTermColor color;
5798 char_u hexbuf[10];
5799 int index;
5800 list_T *list;
5801
5802 if (rettv_list_alloc(rettv) == FAIL)
5803 return;
5804
5805 if (buf == NULL)
5806 return;
5807 term = buf->b_term;
5808 if (term->tl_vterm == NULL)
5809 return;
5810
5811 list = rettv->vval.v_list;
5812 state = vterm_obtain_state(term->tl_vterm);
5813 for (index = 0; index < 16; index++)
5814 {
5815 vterm_state_get_palette_color(state, index, &color);
5816 sprintf((char *)hexbuf, "#%02x%02x%02x",
5817 color.red, color.green, color.blue);
5818 if (list_append_string(list, hexbuf, 7) == FAIL)
5819 return;
5820 }
5821}
5822
5823/*
5824 * "term_setansicolors(buf, list)" function
5825 */
5826 void
5827f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
5828{
5829 buf_T *buf = term_get_buf(argvars, "term_setansicolors()");
5830 term_T *term;
5831
5832 if (buf == NULL)
5833 return;
5834 term = buf->b_term;
5835 if (term->tl_vterm == NULL)
5836 return;
5837
5838 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL)
5839 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005840 emsg(_(e_listreq));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005841 return;
5842 }
5843
5844 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01005845 emsg(_(e_invarg));
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005846}
5847#endif
5848
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005849/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005850 * "term_setapi(buf, api)" function
5851 */
5852 void
5853f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
5854{
5855 buf_T *buf = term_get_buf(argvars, "term_setapi()");
5856 term_T *term;
5857 char_u *api;
5858
5859 if (buf == NULL)
5860 return;
5861 term = buf->b_term;
5862 vim_free(term->tl_api);
5863 api = tv_get_string_chk(&argvars[1]);
5864 if (api != NULL)
5865 term->tl_api = vim_strsave(api);
5866 else
5867 term->tl_api = NULL;
5868}
5869
5870/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005871 * "term_setrestore(buf, command)" function
5872 */
5873 void
5874f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5875{
5876#if defined(FEAT_SESSION)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005877 buf_T *buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005878 term_T *term;
5879 char_u *cmd;
5880
5881 if (buf == NULL)
5882 return;
5883 term = buf->b_term;
5884 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005885 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005886 if (cmd != NULL)
5887 term->tl_command = vim_strsave(cmd);
5888 else
5889 term->tl_command = NULL;
5890#endif
5891}
5892
5893/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005894 * "term_setkill(buf, how)" function
5895 */
5896 void
5897f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
5898{
5899 buf_T *buf = term_get_buf(argvars, "term_setkill()");
5900 term_T *term;
5901 char_u *how;
5902
5903 if (buf == NULL)
5904 return;
5905 term = buf->b_term;
5906 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005907 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005908 if (how != NULL)
5909 term->tl_kill = vim_strsave(how);
5910 else
5911 term->tl_kill = NULL;
5912}
5913
5914/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005915 * "term_start(command, options)" function
5916 */
5917 void
5918f_term_start(typval_T *argvars, typval_T *rettv)
5919{
5920 jobopt_T opt;
5921 buf_T *buf;
5922
5923 init_job_options(&opt);
5924 if (argvars[1].v_type != VAR_UNKNOWN
5925 && get_job_options(&argvars[1], &opt,
5926 JO_TIMEOUT_ALL + JO_STOPONEXIT
5927 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
5928 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
5929 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
5930 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01005931 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02005932 + JO2_NORESTORE + JO2_TERM_KILL
Bram Moolenaard2842ea2019-09-26 23:08:54 +02005933 + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005934 return;
5935
Bram Moolenaar13568252018-03-16 20:46:58 +01005936 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005937
5938 if (buf != NULL && buf->b_term != NULL)
5939 rettv->vval.v_number = buf->b_fnum;
5940}
5941
5942/*
5943 * "term_wait" function
5944 */
5945 void
5946f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
5947{
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01005948 buf_T *buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005949
5950 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005951 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005952 if (buf->b_term->tl_job == NULL)
5953 {
5954 ch_log(NULL, "term_wait(): no job to wait for");
5955 return;
5956 }
5957 if (buf->b_term->tl_job->jv_channel == NULL)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005958 // channel is closed, nothing to do
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005959 return;
5960
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005961 // Get the job status, this will detect a job that finished.
Bram Moolenaara15ef452018-02-09 16:46:00 +01005962 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005963 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
5964 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005965 // The job is dead, keep reading channel I/O until the channel is
5966 // closed. buf->b_term may become NULL if the terminal was closed while
5967 // waiting.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005968 ch_log(NULL, "term_wait(): waiting for channel to close");
5969 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
5970 {
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005971 term_flush_messages();
5972
Bram Moolenaard45aa552018-05-21 22:50:29 +02005973 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01005974 if (!buf_valid(buf))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005975 // If the terminal is closed when the channel is closed the
5976 // buffer disappears.
Bram Moolenaare5182262017-11-19 15:05:44 +01005977 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005978 }
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005979
5980 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005981 }
5982 else
5983 {
5984 long wait = 10L;
5985
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005986 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005987
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005988 // Wait for some time for any channel I/O.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005989 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005990 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005991 ui_delay(wait, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005992
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005993 // Flushing messages on channels is hopefully sufficient.
5994 // TODO: is there a better way?
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02005995 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005996 }
5997}
5998
5999/*
6000 * Called when a channel has sent all the lines to a terminal.
6001 * Send a CTRL-D to mark the end of the text.
6002 */
6003 void
6004term_send_eof(channel_T *ch)
6005{
6006 term_T *term;
6007
6008 for (term = first_term; term != NULL; term = term->tl_next)
6009 if (term->tl_job == ch->ch_job)
6010 {
6011 if (term->tl_eof_chars != NULL)
6012 {
6013 channel_send(ch, PART_IN, term->tl_eof_chars,
6014 (int)STRLEN(term->tl_eof_chars), NULL);
6015 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
6016 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01006017# ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006018 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006019 // Default: CTRL-D
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006020 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
6021# endif
6022 }
6023}
6024
Bram Moolenaar113e1072019-01-20 15:30:40 +01006025#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02006026 job_T *
6027term_getjob(term_T *term)
6028{
6029 return term != NULL ? term->tl_job : NULL;
6030}
Bram Moolenaar113e1072019-01-20 15:30:40 +01006031#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02006032
Bram Moolenaar4f974752019-02-17 17:44:42 +01006033# if defined(MSWIN) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006034
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006035///////////////////////////////////////
6036// 2. MS-Windows implementation.
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006037#ifdef PROTO
6038typedef int COORD;
6039typedef int DWORD;
6040typedef int HANDLE;
6041typedef int *DWORD_PTR;
6042typedef int HPCON;
6043typedef int HRESULT;
6044typedef int LPPROC_THREAD_ATTRIBUTE_LIST;
Bram Moolenaarad3ec762019-04-21 00:00:13 +02006045typedef int SIZE_T;
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006046typedef int PSIZE_T;
6047typedef int PVOID;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01006048typedef int BOOL;
6049# define WINAPI
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006050#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006051
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006052HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
6053HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
6054HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
Bram Moolenaar48773f12019-02-12 21:46:46 +01006055BOOL (WINAPI *pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
6056BOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
6057void (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006058
6059 static int
6060dyn_conpty_init(int verbose)
6061{
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006062 static HMODULE hKerneldll = NULL;
6063 int i;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006064 static struct
6065 {
6066 char *name;
6067 FARPROC *ptr;
6068 } conpty_entry[] =
6069 {
6070 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
6071 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
6072 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
6073 {"InitializeProcThreadAttributeList",
6074 (FARPROC*)&pInitializeProcThreadAttributeList},
6075 {"UpdateProcThreadAttribute",
6076 (FARPROC*)&pUpdateProcThreadAttribute},
6077 {"DeleteProcThreadAttributeList",
6078 (FARPROC*)&pDeleteProcThreadAttributeList},
6079 {NULL, NULL}
6080 };
6081
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01006082 if (!has_conpty_working())
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006083 {
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006084 if (verbose)
6085 emsg(_("E982: ConPTY is not available"));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006086 return FAIL;
6087 }
6088
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006089 // No need to initialize twice.
6090 if (hKerneldll)
6091 return OK;
6092
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006093 hKerneldll = vimLoadLib("kernel32.dll");
6094 for (i = 0; conpty_entry[i].name != NULL
6095 && conpty_entry[i].ptr != NULL; ++i)
6096 {
6097 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
6098 conpty_entry[i].name)) == NULL)
6099 {
6100 if (verbose)
6101 semsg(_(e_loadfunc), conpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006102 hKerneldll = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006103 return FAIL;
6104 }
6105 }
6106
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006107 return OK;
6108}
6109
6110 static int
6111conpty_term_and_job_init(
6112 term_T *term,
6113 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02006114 char **argv UNUSED,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006115 jobopt_T *opt,
6116 jobopt_T *orig_opt)
6117{
6118 WCHAR *cmd_wchar = NULL;
6119 WCHAR *cmd_wchar_copy = NULL;
6120 WCHAR *cwd_wchar = NULL;
6121 WCHAR *env_wchar = NULL;
6122 channel_T *channel = NULL;
6123 job_T *job = NULL;
6124 HANDLE jo = NULL;
6125 garray_T ga_cmd, ga_env;
6126 char_u *cmd = NULL;
6127 HRESULT hr;
6128 COORD consize;
6129 SIZE_T breq;
6130 PROCESS_INFORMATION proc_info;
6131 HANDLE i_theirs = NULL;
6132 HANDLE o_theirs = NULL;
6133 HANDLE i_ours = NULL;
6134 HANDLE o_ours = NULL;
6135
6136 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
6137 ga_init2(&ga_env, (int)sizeof(char*), 20);
6138
6139 if (argvar->v_type == VAR_STRING)
6140 {
6141 cmd = argvar->vval.v_string;
6142 }
6143 else if (argvar->v_type == VAR_LIST)
6144 {
6145 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
6146 goto failed;
6147 cmd = ga_cmd.ga_data;
6148 }
6149 if (cmd == NULL || *cmd == NUL)
6150 {
6151 emsg(_(e_invarg));
6152 goto failed;
6153 }
6154
6155 term->tl_arg0_cmd = vim_strsave(cmd);
6156
6157 cmd_wchar = enc_to_utf16(cmd, NULL);
6158
6159 if (cmd_wchar != NULL)
6160 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006161 // Request by CreateProcessW
6162 breq = wcslen(cmd_wchar) + 1 + 1; // Addition of NUL by API
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006163 cmd_wchar_copy = ALLOC_MULT(WCHAR, breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006164 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
6165 }
6166
6167 ga_clear(&ga_cmd);
6168 if (cmd_wchar == NULL)
6169 goto failed;
6170 if (opt->jo_cwd != NULL)
6171 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
6172
6173 win32_build_env(opt->jo_env, &ga_env, TRUE);
6174 env_wchar = ga_env.ga_data;
6175
6176 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
6177 goto failed;
6178 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
6179 goto failed;
6180
6181 consize.X = term->tl_cols;
6182 consize.Y = term->tl_rows;
6183 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
6184 &term->tl_conpty);
6185 if (FAILED(hr))
6186 goto failed;
6187
6188 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
6189
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006190 // Set up pipe inheritance safely: Vista or later.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006191 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006192 term->tl_siex.lpAttributeList = alloc(breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006193 if (!term->tl_siex.lpAttributeList)
6194 goto failed;
6195 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
6196 0, &breq))
6197 goto failed;
6198 if (!pUpdateProcThreadAttribute(
6199 term->tl_siex.lpAttributeList, 0,
6200 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
6201 sizeof(HPCON), NULL, NULL))
6202 goto failed;
6203
6204 channel = add_channel();
6205 if (channel == NULL)
6206 goto failed;
6207
6208 job = job_alloc();
6209 if (job == NULL)
6210 goto failed;
6211 if (argvar->v_type == VAR_STRING)
6212 {
6213 int argc;
6214
6215 build_argv_from_string(cmd, &job->jv_argv, &argc);
6216 }
6217 else
6218 {
6219 int argc;
6220
6221 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6222 }
6223
6224 if (opt->jo_set & JO_IN_BUF)
6225 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6226
6227 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
6228 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
6229 | CREATE_SUSPENDED | CREATE_NEW_PROCESS_GROUP
6230 | CREATE_DEFAULT_ERROR_MODE,
6231 env_wchar, cwd_wchar,
6232 &term->tl_siex.StartupInfo, &proc_info))
6233 goto failed;
6234
6235 CloseHandle(i_theirs);
6236 CloseHandle(o_theirs);
6237
6238 channel_set_pipes(channel,
6239 (sock_T)i_ours,
6240 (sock_T)o_ours,
6241 (sock_T)o_ours);
6242
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006243 // Write lines with CR instead of NL.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006244 channel->ch_write_text_mode = TRUE;
6245
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006246 // Use to explicitly delete anonymous pipe handle.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006247 channel->ch_anonymous_pipe = TRUE;
6248
6249 jo = CreateJobObject(NULL, NULL);
6250 if (jo == NULL)
6251 goto failed;
6252
6253 if (!AssignProcessToJobObject(jo, proc_info.hProcess))
6254 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006255 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006256 CloseHandle(jo);
6257 jo = NULL;
6258 }
6259
6260 ResumeThread(proc_info.hThread);
6261 CloseHandle(proc_info.hThread);
6262
6263 vim_free(cmd_wchar);
6264 vim_free(cmd_wchar_copy);
6265 vim_free(cwd_wchar);
6266 vim_free(env_wchar);
6267
6268 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6269 goto failed;
6270
6271#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6272 if (opt->jo_set2 & JO2_ANSI_COLORS)
6273 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6274 else
6275 init_vterm_ansi_colors(term->tl_vterm);
6276#endif
6277
6278 channel_set_job(channel, job, opt);
6279 job_set_options(job, opt);
6280
6281 job->jv_channel = channel;
6282 job->jv_proc_info = proc_info;
6283 job->jv_job_object = jo;
6284 job->jv_status = JOB_STARTED;
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006285 job->jv_tty_type = vim_strsave((char_u *)"conpty");
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006286 ++job->jv_refcount;
6287 term->tl_job = job;
6288
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006289 // Redirecting stdout and stderr doesn't work at the job level. Instead
6290 // open the file here and handle it in. opt->jo_io was changed in
6291 // setup_job_options(), use the original flags here.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006292 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6293 {
6294 char_u *fname = opt->jo_io_name[PART_OUT];
6295
6296 ch_log(channel, "Opening output file %s", fname);
6297 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6298 if (term->tl_out_fd == NULL)
6299 semsg(_(e_notopen), fname);
6300 }
6301
6302 return OK;
6303
6304failed:
6305 ga_clear(&ga_cmd);
6306 ga_clear(&ga_env);
6307 vim_free(cmd_wchar);
6308 vim_free(cmd_wchar_copy);
6309 vim_free(cwd_wchar);
6310 if (channel != NULL)
6311 channel_clear(channel);
6312 if (job != NULL)
6313 {
6314 job->jv_channel = NULL;
6315 job_cleanup(job);
6316 }
6317 term->tl_job = NULL;
6318 if (jo != NULL)
6319 CloseHandle(jo);
6320
6321 if (term->tl_siex.lpAttributeList != NULL)
6322 {
6323 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6324 vim_free(term->tl_siex.lpAttributeList);
6325 }
6326 term->tl_siex.lpAttributeList = NULL;
6327 if (o_theirs != NULL)
6328 CloseHandle(o_theirs);
6329 if (o_ours != NULL)
6330 CloseHandle(o_ours);
6331 if (i_ours != NULL)
6332 CloseHandle(i_ours);
6333 if (i_theirs != NULL)
6334 CloseHandle(i_theirs);
6335 if (term->tl_conpty != NULL)
6336 pClosePseudoConsole(term->tl_conpty);
6337 term->tl_conpty = NULL;
6338 return FAIL;
6339}
6340
6341 static void
6342conpty_term_report_winsize(term_T *term, int rows, int cols)
6343{
6344 COORD consize;
6345
6346 consize.X = cols;
6347 consize.Y = rows;
6348 pResizePseudoConsole(term->tl_conpty, consize);
6349}
6350
Bram Moolenaar840d16f2019-09-10 21:27:18 +02006351 static void
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006352term_free_conpty(term_T *term)
6353{
6354 if (term->tl_siex.lpAttributeList != NULL)
6355 {
6356 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
6357 vim_free(term->tl_siex.lpAttributeList);
6358 }
6359 term->tl_siex.lpAttributeList = NULL;
6360 if (term->tl_conpty != NULL)
6361 pClosePseudoConsole(term->tl_conpty);
6362 term->tl_conpty = NULL;
6363}
6364
6365 int
6366use_conpty(void)
6367{
6368 return has_conpty;
6369}
6370
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006371# ifndef PROTO
6372
6373#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
6374#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01006375#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006376
6377void* (*winpty_config_new)(UINT64, void*);
6378void* (*winpty_open)(void*, void*);
6379void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
6380BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
6381void (*winpty_config_set_mouse_mode)(void*, int);
6382void (*winpty_config_set_initial_size)(void*, int, int);
6383LPCWSTR (*winpty_conin_name)(void*);
6384LPCWSTR (*winpty_conout_name)(void*);
6385LPCWSTR (*winpty_conerr_name)(void*);
6386void (*winpty_free)(void*);
6387void (*winpty_config_free)(void*);
6388void (*winpty_spawn_config_free)(void*);
6389void (*winpty_error_free)(void*);
6390LPCWSTR (*winpty_error_msg)(void*);
6391BOOL (*winpty_set_size)(void*, int, int, void*);
6392HANDLE (*winpty_agent_process)(void*);
6393
6394#define WINPTY_DLL "winpty.dll"
6395
6396static HINSTANCE hWinPtyDLL = NULL;
6397# endif
6398
6399 static int
6400dyn_winpty_init(int verbose)
6401{
6402 int i;
6403 static struct
6404 {
6405 char *name;
6406 FARPROC *ptr;
6407 } winpty_entry[] =
6408 {
6409 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
6410 {"winpty_config_free", (FARPROC*)&winpty_config_free},
6411 {"winpty_config_new", (FARPROC*)&winpty_config_new},
6412 {"winpty_config_set_mouse_mode",
6413 (FARPROC*)&winpty_config_set_mouse_mode},
6414 {"winpty_config_set_initial_size",
6415 (FARPROC*)&winpty_config_set_initial_size},
6416 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
6417 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
6418 {"winpty_error_free", (FARPROC*)&winpty_error_free},
6419 {"winpty_free", (FARPROC*)&winpty_free},
6420 {"winpty_open", (FARPROC*)&winpty_open},
6421 {"winpty_spawn", (FARPROC*)&winpty_spawn},
6422 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
6423 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
6424 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
6425 {"winpty_set_size", (FARPROC*)&winpty_set_size},
6426 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
6427 {NULL, NULL}
6428 };
6429
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006430 // No need to initialize twice.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006431 if (hWinPtyDLL)
6432 return OK;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006433 // Load winpty.dll, prefer using the 'winptydll' option, fall back to just
6434 // winpty.dll.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006435 if (*p_winptydll != NUL)
6436 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
6437 if (!hWinPtyDLL)
6438 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
6439 if (!hWinPtyDLL)
6440 {
6441 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006442 semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006443 : (char_u *)WINPTY_DLL);
6444 return FAIL;
6445 }
6446 for (i = 0; winpty_entry[i].name != NULL
6447 && winpty_entry[i].ptr != NULL; ++i)
6448 {
6449 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
6450 winpty_entry[i].name)) == NULL)
6451 {
6452 if (verbose)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006453 semsg(_(e_loadfunc), winpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006454 hWinPtyDLL = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006455 return FAIL;
6456 }
6457 }
6458
6459 return OK;
6460}
6461
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006462 static int
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006463winpty_term_and_job_init(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006464 term_T *term,
6465 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02006466 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006467 jobopt_T *opt,
6468 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006469{
6470 WCHAR *cmd_wchar = NULL;
6471 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006472 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006473 channel_T *channel = NULL;
6474 job_T *job = NULL;
6475 DWORD error;
6476 HANDLE jo = NULL;
6477 HANDLE child_process_handle;
6478 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01006479 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006480 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006481 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006482 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006483
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006484 ga_init2(&ga_cmd, (int)sizeof(char*), 20);
6485 ga_init2(&ga_env, (int)sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006486
6487 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006488 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006489 cmd = argvar->vval.v_string;
6490 }
6491 else if (argvar->v_type == VAR_LIST)
6492 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006493 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006494 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006495 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006496 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006497 if (cmd == NULL || *cmd == NUL)
6498 {
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006499 emsg(_(e_invarg));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006500 goto failed;
6501 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006502
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006503 term->tl_arg0_cmd = vim_strsave(cmd);
6504
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006505 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006506 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006507 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006508 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006509 if (opt->jo_cwd != NULL)
6510 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006511
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01006512 win32_build_env(opt->jo_env, &ga_env, TRUE);
6513 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006514
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006515 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
6516 if (term->tl_winpty_config == NULL)
6517 goto failed;
6518
6519 winpty_config_set_mouse_mode(term->tl_winpty_config,
6520 WINPTY_MOUSE_MODE_FORCE);
6521 winpty_config_set_initial_size(term->tl_winpty_config,
6522 term->tl_cols, term->tl_rows);
6523 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
6524 if (term->tl_winpty == NULL)
6525 goto failed;
6526
6527 spawn_config = winpty_spawn_config_new(
6528 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
6529 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
6530 NULL,
6531 cmd_wchar,
6532 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01006533 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006534 &winpty_err);
6535 if (spawn_config == NULL)
6536 goto failed;
6537
6538 channel = add_channel();
6539 if (channel == NULL)
6540 goto failed;
6541
6542 job = job_alloc();
6543 if (job == NULL)
6544 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02006545 if (argvar->v_type == VAR_STRING)
6546 {
6547 int argc;
6548
6549 build_argv_from_string(cmd, &job->jv_argv, &argc);
6550 }
6551 else
6552 {
6553 int argc;
6554
6555 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6556 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006557
6558 if (opt->jo_set & JO_IN_BUF)
6559 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6560
6561 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
6562 &child_thread_handle, &error, &winpty_err))
6563 goto failed;
6564
6565 channel_set_pipes(channel,
6566 (sock_T)CreateFileW(
6567 winpty_conin_name(term->tl_winpty),
6568 GENERIC_WRITE, 0, NULL,
6569 OPEN_EXISTING, 0, NULL),
6570 (sock_T)CreateFileW(
6571 winpty_conout_name(term->tl_winpty),
6572 GENERIC_READ, 0, NULL,
6573 OPEN_EXISTING, 0, NULL),
6574 (sock_T)CreateFileW(
6575 winpty_conerr_name(term->tl_winpty),
6576 GENERIC_READ, 0, NULL,
6577 OPEN_EXISTING, 0, NULL));
6578
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006579 // Write lines with CR instead of NL.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006580 channel->ch_write_text_mode = TRUE;
6581
6582 jo = CreateJobObject(NULL, NULL);
6583 if (jo == NULL)
6584 goto failed;
6585
6586 if (!AssignProcessToJobObject(jo, child_process_handle))
6587 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006588 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006589 CloseHandle(jo);
6590 jo = NULL;
6591 }
6592
6593 winpty_spawn_config_free(spawn_config);
6594 vim_free(cmd_wchar);
6595 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006596 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006597
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006598 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6599 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006600
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006601#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6602 if (opt->jo_set2 & JO2_ANSI_COLORS)
6603 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6604 else
6605 init_vterm_ansi_colors(term->tl_vterm);
6606#endif
6607
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006608 channel_set_job(channel, job, opt);
6609 job_set_options(job, opt);
6610
6611 job->jv_channel = channel;
6612 job->jv_proc_info.hProcess = child_process_handle;
6613 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
6614 job->jv_job_object = jo;
6615 job->jv_status = JOB_STARTED;
6616 job->jv_tty_in = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006617 (short_u *)winpty_conin_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006618 job->jv_tty_out = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006619 (short_u *)winpty_conout_name(term->tl_winpty), NULL);
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006620 job->jv_tty_type = vim_strsave((char_u *)"winpty");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006621 ++job->jv_refcount;
6622 term->tl_job = job;
6623
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006624 // Redirecting stdout and stderr doesn't work at the job level. Instead
6625 // open the file here and handle it in. opt->jo_io was changed in
6626 // setup_job_options(), use the original flags here.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006627 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6628 {
6629 char_u *fname = opt->jo_io_name[PART_OUT];
6630
6631 ch_log(channel, "Opening output file %s", fname);
6632 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6633 if (term->tl_out_fd == NULL)
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006634 semsg(_(e_notopen), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006635 }
6636
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006637 return OK;
6638
6639failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01006640 ga_clear(&ga_cmd);
6641 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006642 vim_free(cmd_wchar);
6643 vim_free(cwd_wchar);
6644 if (spawn_config != NULL)
6645 winpty_spawn_config_free(spawn_config);
6646 if (channel != NULL)
6647 channel_clear(channel);
6648 if (job != NULL)
6649 {
6650 job->jv_channel = NULL;
6651 job_cleanup(job);
6652 }
6653 term->tl_job = NULL;
6654 if (jo != NULL)
6655 CloseHandle(jo);
6656 if (term->tl_winpty != NULL)
6657 winpty_free(term->tl_winpty);
6658 term->tl_winpty = NULL;
6659 if (term->tl_winpty_config != NULL)
6660 winpty_config_free(term->tl_winpty_config);
6661 term->tl_winpty_config = NULL;
6662 if (winpty_err != NULL)
6663 {
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006664 char *msg = (char *)utf16_to_enc(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006665 (short_u *)winpty_error_msg(winpty_err), NULL);
6666
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01006667 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006668 winpty_error_free(winpty_err);
6669 }
6670 return FAIL;
6671}
6672
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006673/*
6674 * Create a new terminal of "rows" by "cols" cells.
6675 * Store a reference in "term".
6676 * Return OK or FAIL.
6677 */
6678 static int
6679term_and_job_init(
6680 term_T *term,
6681 typval_T *argvar,
Bram Moolenaar197c6b72019-11-03 23:37:12 +01006682 char **argv,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006683 jobopt_T *opt,
6684 jobopt_T *orig_opt)
6685{
6686 int use_winpty = FALSE;
6687 int use_conpty = FALSE;
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006688 int tty_type = *p_twt;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006689
6690 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
6691 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
6692
6693 if (!has_winpty && !has_conpty)
6694 // If neither is available give the errors for winpty, since when
6695 // conpty is not available it can't be installed either.
6696 return dyn_winpty_init(TRUE);
6697
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006698 if (opt->jo_tty_type != NUL)
6699 tty_type = opt->jo_tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006700
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006701 if (tty_type == NUL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006702 {
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01006703 if (has_conpty && (is_conpty_stable() || !has_winpty))
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006704 use_conpty = TRUE;
6705 else if (has_winpty)
6706 use_winpty = TRUE;
6707 // else: error
6708 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006709 else if (tty_type == 'w') // winpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006710 {
6711 if (has_winpty)
6712 use_winpty = TRUE;
6713 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01006714 else if (tty_type == 'c') // conpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006715 {
6716 if (has_conpty)
6717 use_conpty = TRUE;
6718 else
6719 return dyn_conpty_init(TRUE);
6720 }
6721
6722 if (use_conpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006723 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006724
6725 if (use_winpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006726 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006727
6728 // error
6729 return dyn_winpty_init(TRUE);
6730}
6731
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006732 static int
6733create_pty_only(term_T *term, jobopt_T *options)
6734{
6735 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
6736 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
6737 char in_name[80], out_name[80];
6738 channel_T *channel = NULL;
6739
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006740 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6741 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006742
6743 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
6744 GetCurrentProcessId(),
6745 curbuf->b_fnum);
6746 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
6747 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6748 PIPE_UNLIMITED_INSTANCES,
6749 0, 0, NMPWAIT_NOWAIT, NULL);
6750 if (hPipeIn == INVALID_HANDLE_VALUE)
6751 goto failed;
6752
6753 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
6754 GetCurrentProcessId(),
6755 curbuf->b_fnum);
6756 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
6757 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
6758 PIPE_UNLIMITED_INSTANCES,
6759 0, 0, 0, NULL);
6760 if (hPipeOut == INVALID_HANDLE_VALUE)
6761 goto failed;
6762
6763 ConnectNamedPipe(hPipeIn, NULL);
6764 ConnectNamedPipe(hPipeOut, NULL);
6765
6766 term->tl_job = job_alloc();
6767 if (term->tl_job == NULL)
6768 goto failed;
6769 ++term->tl_job->jv_refcount;
6770
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006771 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006772 term->tl_job->jv_status = JOB_FINISHED;
6773
6774 channel = add_channel();
6775 if (channel == NULL)
6776 goto failed;
6777 term->tl_job->jv_channel = channel;
6778 channel->ch_keep_open = TRUE;
6779 channel->ch_named_pipe = TRUE;
6780
6781 channel_set_pipes(channel,
6782 (sock_T)hPipeIn,
6783 (sock_T)hPipeOut,
6784 (sock_T)hPipeOut);
6785 channel_set_job(channel, term->tl_job, options);
6786 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
6787 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
6788
6789 return OK;
6790
6791failed:
6792 if (hPipeIn != NULL)
6793 CloseHandle(hPipeIn);
6794 if (hPipeOut != NULL)
6795 CloseHandle(hPipeOut);
6796 return FAIL;
6797}
6798
6799/*
6800 * Free the terminal emulator part of "term".
6801 */
6802 static void
6803term_free_vterm(term_T *term)
6804{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006805 term_free_conpty(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006806 if (term->tl_winpty != NULL)
6807 winpty_free(term->tl_winpty);
6808 term->tl_winpty = NULL;
6809 if (term->tl_winpty_config != NULL)
6810 winpty_config_free(term->tl_winpty_config);
6811 term->tl_winpty_config = NULL;
6812 if (term->tl_vterm != NULL)
6813 vterm_free(term->tl_vterm);
6814 term->tl_vterm = NULL;
6815}
6816
6817/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006818 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006819 */
6820 static void
6821term_report_winsize(term_T *term, int rows, int cols)
6822{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006823 if (term->tl_conpty)
6824 conpty_term_report_winsize(term, rows, cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006825 if (term->tl_winpty)
6826 winpty_set_size(term->tl_winpty, cols, rows, NULL);
6827}
6828
6829 int
6830terminal_enabled(void)
6831{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006832 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006833}
6834
6835# else
6836
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006837///////////////////////////////////////
6838// 3. Unix-like implementation.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006839
6840/*
6841 * Create a new terminal of "rows" by "cols" cells.
6842 * Start job for "cmd".
6843 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01006844 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006845 * Return OK or FAIL.
6846 */
6847 static int
6848term_and_job_init(
6849 term_T *term,
6850 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01006851 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02006852 jobopt_T *opt,
6853 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006854{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006855 term->tl_arg0_cmd = NULL;
6856
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006857 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6858 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006859
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006860#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
6861 if (opt->jo_set2 & JO2_ANSI_COLORS)
6862 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors);
6863 else
6864 init_vterm_ansi_colors(term->tl_vterm);
6865#endif
6866
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006867 // This may change a string in "argvar".
Bram Moolenaar21109272020-01-30 16:27:20 +01006868 term->tl_job = job_start(argvar, argv, opt, &term->tl_job);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006869 if (term->tl_job != NULL)
6870 ++term->tl_job->jv_refcount;
6871
6872 return term->tl_job != NULL
6873 && term->tl_job->jv_channel != NULL
6874 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
6875}
6876
6877 static int
6878create_pty_only(term_T *term, jobopt_T *opt)
6879{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01006880 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6881 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006882
6883 term->tl_job = job_alloc();
6884 if (term->tl_job == NULL)
6885 return FAIL;
6886 ++term->tl_job->jv_refcount;
6887
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006888 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006889 term->tl_job->jv_status = JOB_FINISHED;
6890
6891 return mch_create_pty_channel(term->tl_job, opt);
6892}
6893
6894/*
6895 * Free the terminal emulator part of "term".
6896 */
6897 static void
6898term_free_vterm(term_T *term)
6899{
6900 if (term->tl_vterm != NULL)
6901 vterm_free(term->tl_vterm);
6902 term->tl_vterm = NULL;
6903}
6904
6905/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006906 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006907 */
6908 static void
6909term_report_winsize(term_T *term, int rows, int cols)
6910{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006911 // Use an ioctl() to report the new window size to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006912 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
6913 {
6914 int fd = -1;
6915 int part;
6916
6917 for (part = PART_OUT; part < PART_COUNT; ++part)
6918 {
6919 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01006920 if (mch_isatty(fd))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006921 break;
6922 }
6923 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
6924 mch_signal_job(term->tl_job, (char_u *)"winch");
6925 }
6926}
6927
6928# endif
6929
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006930#endif // FEAT_TERMINAL