blob: ae532dc7219f9c3436c3b8a59d065a789bceefb9 [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 Moolenaareea32af2021-11-21 14:51:13 +0000102 int tl_channel_closing;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200103 int tl_channel_closed;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +0200104 int tl_channel_recently_closed; // still need to handle tl_finish
105
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100106 int tl_finish;
107#define TL_FINISH_UNSET NUL
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100108#define TL_FINISH_CLOSE 'c' // ++close or :terminal without argument
109#define TL_FINISH_NOCLOSE 'n' // ++noclose
110#define TL_FINISH_OPEN 'o' // ++open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200111 char_u *tl_opencmd;
112 char_u *tl_eof_chars;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200113 char_u *tl_api; // prefix for terminal API function
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200114
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100115 char_u *tl_arg0_cmd; // To format the status bar
116
Bram Moolenaar4f974752019-02-17 17:44:42 +0100117#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200118 void *tl_winpty_config;
119 void *tl_winpty;
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200120
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100121 HPCON tl_conpty;
122 DYN_STARTUPINFOEXW tl_siex; // Structure that always needs to be hold
123
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200124 FILE *tl_out_fd;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200125#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100126#if defined(FEAT_SESSION)
127 char_u *tl_command;
128#endif
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100129 char_u *tl_kill;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200130
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100131 // last known vterm size
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200132 int tl_rows;
133 int tl_cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200134
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100135 char_u *tl_title; // NULL or allocated
136 char_u *tl_status_text; // NULL or allocated
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200137
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100138 // Range of screen rows to update. Zero based.
139 int tl_dirty_row_start; // MAX_ROW if nothing dirty
140 int tl_dirty_row_end; // row below last one to update
141 int tl_dirty_snapshot; // text updated after making snapshot
Bram Moolenaar56bc8e22018-05-10 18:05:56 +0200142#ifdef FEAT_TIMERS
143 int tl_timer_set;
144 proftime_T tl_timer_due;
145#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100146 int tl_postponed_scroll; // to be scrolled up
Bram Moolenaar6eddadf2018-05-06 16:40:16 +0200147
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200148 garray_T tl_scrollback;
149 int tl_scrollback_scrolled;
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100150 garray_T tl_scrollback_postponed;
151
Bram Moolenaar83d47902020-03-26 20:34:00 +0100152 char_u *tl_highlight_name; // replaces "Terminal"; allocated
153
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200154 cellattr_T tl_default_color;
155
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100156 linenr_T tl_top_diff_rows; // rows of top diff file or zero
157 linenr_T tl_bot_diff_rows; // rows of bottom diff file
Bram Moolenaard96ff162018-02-18 22:13:29 +0100158
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200159 VTermPos tl_cursor_pos;
160 int tl_cursor_visible;
161 int tl_cursor_blink;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100162 int tl_cursor_shape; // 1: block, 2: underline, 3: bar
163 char_u *tl_cursor_color; // NULL or allocated
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200164
LemonBoyb2b3acb2022-05-20 10:10:34 +0100165 long_u *tl_palette; // array of 16 colors specified by term_start, can
166 // be NULL
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200167 int tl_using_altscreen;
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +0200168 garray_T tl_osc_buf; // incomplete OSC string
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200169};
170
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100171#define TMODE_ONCE 1 // CTRL-\ CTRL-N used
172#define TMODE_LOOP 2 // CTRL-W N used
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200173
174/*
175 * List of all active terminals.
176 */
177static term_T *first_term = NULL;
178
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100179// Terminal active in terminal_loop().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200180static term_T *in_terminal_loop = NULL;
181
Bram Moolenaar4f974752019-02-17 17:44:42 +0100182#ifdef MSWIN
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100183static BOOL has_winpty = FALSE;
184static BOOL has_conpty = FALSE;
185#endif
186
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100187#define MAX_ROW 999999 // used for tl_dirty_row_end to update all rows
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200188#define KEY_BUF_LEN 200
189
Bram Moolenaaraeea7212020-04-02 18:50:46 +0200190#define FOR_ALL_TERMS(term) \
191 for ((term) = first_term; (term) != NULL; (term) = (term)->tl_next)
192
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200193/*
194 * Functions with separate implementation for MS-Windows and Unix-like systems.
195 */
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200196static 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 +0200197static int create_pty_only(term_T *term, jobopt_T *opt);
198static void term_report_winsize(term_T *term, int rows, int cols);
199static void term_free_vterm(term_T *term);
Bram Moolenaar13568252018-03-16 20:46:58 +0100200#ifdef FEAT_GUI
201static void update_system_term(term_T *term);
202#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200203
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100204static void handle_postponed_scrollback(term_T *term);
205
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100206// The character that we know (or assume) that the terminal expects for the
207// backspace key.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200208static int term_backspace_char = BS;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200209
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100210// Store the last set and the desired cursor properties, so that we only update
211// them when needed. Doing it unnecessary may result in flicker.
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200212static char_u *last_set_cursor_color = NULL;
213static char_u *desired_cursor_color = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +0100214static int last_set_cursor_shape = -1;
215static int desired_cursor_shape = -1;
216static int last_set_cursor_blink = -1;
217static int desired_cursor_blink = -1;
218
219
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100220///////////////////////////////////////
221// 1. Generic code for all systems.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200222
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200223 static int
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200224cursor_color_equal(char_u *lhs_color, char_u *rhs_color)
225{
226 if (lhs_color != NULL && rhs_color != NULL)
227 return STRCMP(lhs_color, rhs_color) == 0;
228 return lhs_color == NULL && rhs_color == NULL;
229}
230
Bram Moolenaar05af9a42018-05-21 18:48:12 +0200231 static void
232cursor_color_copy(char_u **to_color, char_u *from_color)
233{
234 // Avoid a free & alloc if the value is already right.
235 if (cursor_color_equal(*to_color, from_color))
236 return;
237 vim_free(*to_color);
238 *to_color = (from_color == NULL) ? NULL : vim_strsave(from_color);
239}
240
241 static char_u *
Bram Moolenaar4f7fd562018-05-21 14:55:28 +0200242cursor_color_get(char_u *color)
243{
244 return (color == NULL) ? (char_u *)"" : color;
245}
246
247
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200248/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200249 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the
Bram Moolenaar498c2562018-04-15 23:45:15 +0200250 * current window.
251 * Sets "rows" and/or "cols" to zero when it should follow the window size.
252 * Return TRUE if the size is the minimum size: "24*80".
253 */
254 static int
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200255parse_termwinsize(win_T *wp, int *rows, int *cols)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200256{
257 int minsize = FALSE;
258
259 *rows = 0;
260 *cols = 0;
261
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200262 if (*wp->w_p_tws != NUL)
Bram Moolenaar498c2562018-04-15 23:45:15 +0200263 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200264 char_u *p = vim_strchr(wp->w_p_tws, 'x');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200265
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100266 // Syntax of value was already checked when it's set.
Bram Moolenaar498c2562018-04-15 23:45:15 +0200267 if (p == NULL)
268 {
269 minsize = TRUE;
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200270 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200271 }
Bram Moolenaar6d150f72018-04-21 20:03:20 +0200272 *rows = atoi((char *)wp->w_p_tws);
Bram Moolenaar498c2562018-04-15 23:45:15 +0200273 *cols = atoi((char *)p + 1);
274 }
275 return minsize;
276}
277
278/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200279 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200280 */
281 static void
Bram Moolenaarb936b792020-09-04 18:34:09 +0200282set_term_and_win_size(term_T *term, jobopt_T *opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200283{
Bram Moolenaarb936b792020-09-04 18:34:09 +0200284 int rows, cols;
285 int minsize;
286
Bram Moolenaar13568252018-03-16 20:46:58 +0100287#ifdef FEAT_GUI
288 if (term->tl_system)
289 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100290 // Use the whole screen for the system command. However, it will start
291 // at the command line and scroll up as needed, using tl_toprow.
Bram Moolenaar13568252018-03-16 20:46:58 +0100292 term->tl_rows = Rows;
293 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200294 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100295 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100296#endif
Bram Moolenaarb936b792020-09-04 18:34:09 +0200297 term->tl_rows = curwin->w_height;
298 term->tl_cols = curwin->w_width;
299
300 minsize = parse_termwinsize(curwin, &rows, &cols);
301 if (minsize)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200302 {
Bram Moolenaarb936b792020-09-04 18:34:09 +0200303 if (term->tl_rows < rows)
304 term->tl_rows = rows;
305 if (term->tl_cols < cols)
306 term->tl_cols = cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200307 }
Bram Moolenaarb936b792020-09-04 18:34:09 +0200308 if ((opt->jo_set2 & JO2_TERM_ROWS))
309 term->tl_rows = opt->jo_term_rows;
310 else if (rows != 0)
311 term->tl_rows = rows;
312 if ((opt->jo_set2 & JO2_TERM_COLS))
313 term->tl_cols = opt->jo_term_cols;
314 else if (cols != 0)
315 term->tl_cols = cols;
316
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200317 if (!opt->jo_hidden)
Bram Moolenaarb936b792020-09-04 18:34:09 +0200318 {
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200319 if (term->tl_rows != curwin->w_height)
320 win_setheight_win(term->tl_rows, curwin);
321 if (term->tl_cols != curwin->w_width)
322 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaarb936b792020-09-04 18:34:09 +0200323
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200324 // Set 'winsize' now to avoid a resize at the next redraw.
325 if (!minsize && *curwin->w_p_tws != NUL)
326 {
327 char_u buf[100];
328
329 vim_snprintf((char *)buf, 100, "%dx%d",
330 term->tl_rows, term->tl_cols);
Bram Moolenaarac4174e2022-05-07 16:38:24 +0100331 set_option_value_give_err((char_u *)"termwinsize",
332 0L, buf, OPT_LOCAL);
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200333 }
Bram Moolenaarb936b792020-09-04 18:34:09 +0200334 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200335}
336
337/*
338 * Initialize job options for a terminal job.
339 * Caller may overrule some of them.
340 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100341 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200342init_job_options(jobopt_T *opt)
343{
344 clear_job_options(opt);
345
Bram Moolenaarac4174e2022-05-07 16:38:24 +0100346 opt->jo_mode = CH_MODE_RAW;
347 opt->jo_out_mode = CH_MODE_RAW;
348 opt->jo_err_mode = CH_MODE_RAW;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200349 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
350}
351
352/*
353 * Set job options mandatory for a terminal job.
354 */
355 static void
356setup_job_options(jobopt_T *opt, int rows, int cols)
357{
Bram Moolenaar4f974752019-02-17 17:44:42 +0100358#ifndef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100359 // Win32: Redirecting the job output won't work, thus always connect stdout
360 // here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200361 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200362#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200363 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100364 // Connect stdout to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200365 opt->jo_io[PART_OUT] = JIO_BUFFER;
366 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
367 opt->jo_modifiable[PART_OUT] = 0;
368 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
369 }
370
Bram Moolenaar4f974752019-02-17 17:44:42 +0100371#ifndef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100372 // Win32: Redirecting the job output won't work, thus always connect stderr
373 // here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200374 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200375#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200376 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100377 // Connect stderr to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200378 opt->jo_io[PART_ERR] = JIO_BUFFER;
379 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
380 opt->jo_modifiable[PART_ERR] = 0;
381 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
382 }
383
384 opt->jo_pty = TRUE;
385 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
386 opt->jo_term_rows = rows;
387 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
388 opt->jo_term_cols = cols;
389}
390
391/*
Bram Moolenaar5c381eb2019-06-25 06:50:31 +0200392 * Flush messages on channels.
393 */
394 static void
395term_flush_messages()
396{
397 mch_check_messages();
398 parse_queued_messages();
399}
400
401/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100402 * Close a terminal buffer (and its window). Used when creating the terminal
403 * fails.
404 */
405 static void
406term_close_buffer(buf_T *buf, buf_T *old_curbuf)
407{
408 free_terminal(buf);
409 if (old_curbuf != NULL)
410 {
411 --curbuf->b_nwindows;
412 curbuf = old_curbuf;
413 curwin->w_buffer = curbuf;
414 ++curbuf->b_nwindows;
415 }
Bram Moolenaarcee52202020-03-11 14:19:58 +0100416 CHECK_CURBUF;
Bram Moolenaard96ff162018-02-18 22:13:29 +0100417
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100418 // Wiping out the buffer will also close the window and call
419 // free_terminal().
Bram Moolenaard96ff162018-02-18 22:13:29 +0100420 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
421}
422
423/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200424 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100425 * Use either "argvar" or "argv", the other must be NULL.
426 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
427 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200428 * Returns NULL when failed.
429 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100430 buf_T *
431term_start(
432 typval_T *argvar,
433 char **argv,
434 jobopt_T *opt,
435 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200436{
437 exarg_T split_ea;
438 win_T *old_curwin = curwin;
439 term_T *term;
440 buf_T *old_curbuf = NULL;
441 int res;
442 buf_T *newbuf;
Bram Moolenaare1004402020-10-24 20:49:43 +0200443 int vertical = opt->jo_vertical || (cmdmod.cmod_split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200444 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200445
446 if (check_restricted() || check_secure())
447 return NULL;
Bram Moolenaare5b44862021-05-30 13:54:03 +0200448 if (cmdwin_type != 0)
449 {
450 emsg(_(e_cannot_open_terminal_from_command_line_window));
451 return NULL;
452 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200453
454 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
455 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
456 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
Bram Moolenaarb0992022020-01-30 14:55:42 +0100457 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF))
458 || (argvar != NULL
459 && argvar->v_type == VAR_LIST
460 && argvar->vval.v_list != NULL
461 && argvar->vval.v_list->lv_first == &range_list_item))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200462 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +0000463 emsg(_(e_invalid_argument));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200464 return NULL;
465 }
466
Bram Moolenaarc799fe22019-05-28 23:08:19 +0200467 term = ALLOC_CLEAR_ONE(term_T);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200468 if (term == NULL)
469 return NULL;
470 term->tl_dirty_row_end = MAX_ROW;
471 term->tl_cursor_visible = TRUE;
472 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
473 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100474#ifdef FEAT_GUI
475 term->tl_system = (flags & TERM_START_SYSTEM);
476#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200477 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100478 ga_init2(&term->tl_scrollback_postponed, sizeof(sb_line_T), 300);
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +0200479 ga_init2(&term->tl_osc_buf, sizeof(char), 300);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200480
Bram Moolenaaraeed2a62021-04-29 20:18:45 +0200481 setpcmark();
Bram Moolenaara80faa82020-04-12 19:37:17 +0200482 CLEAR_FIELD(split_ea);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200483 if (opt->jo_curwin)
484 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100485 // Create a new buffer in the current window.
Bram Moolenaar13568252018-03-16 20:46:58 +0100486 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200487 {
488 no_write_message();
489 vim_free(term);
490 return NULL;
491 }
492 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaarb1009092020-05-31 16:04:42 +0200493 (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0)
494 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
495 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200496 {
497 vim_free(term);
498 return NULL;
499 }
500 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100501 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200502 {
503 buf_T *buf;
504
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100505 // Create a new buffer without a window. Make it the current buffer for
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100506 // a moment to be able to do the initializations.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200507 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
508 BLN_NEW | BLN_LISTED);
509 if (buf == NULL || ml_open(buf) == FAIL)
510 {
511 vim_free(term);
512 return NULL;
513 }
514 old_curbuf = curbuf;
515 --curbuf->b_nwindows;
516 curbuf = buf;
517 curwin->w_buffer = buf;
518 ++curbuf->b_nwindows;
519 }
520 else
521 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100522 // Open a new window or tab.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200523 split_ea.cmdidx = CMD_new;
524 split_ea.cmd = (char_u *)"new";
525 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100526 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200527 {
528 split_ea.line2 = opt->jo_term_rows;
529 split_ea.addr_count = 1;
530 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100531 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200532 {
533 split_ea.line2 = opt->jo_term_cols;
534 split_ea.addr_count = 1;
535 }
536
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100537 if (vertical)
Bram Moolenaare1004402020-10-24 20:49:43 +0200538 cmdmod.cmod_split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200539 ex_splitview(&split_ea);
540 if (curwin == old_curwin)
541 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100542 // split failed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200543 vim_free(term);
544 return NULL;
545 }
546 }
547 term->tl_buffer = curbuf;
548 curbuf->b_term = term;
549
550 if (!opt->jo_hidden)
551 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100552 // Only one size was taken care of with :new, do the other one. With
553 // "curwin" both need to be done.
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100554 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200555 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100556 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200557 win_setwidth(opt->jo_term_cols);
558 }
559
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100560 // Link the new terminal in the list of active terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200561 term->tl_next = first_term;
562 first_term = term;
563
Bram Moolenaar5e94a292020-03-19 18:46:57 +0100564 apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf);
565
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200566 if (opt->jo_term_name != NULL)
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100567 {
568 vim_free(curbuf->b_ffname);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200569 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100570 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100571 else if (argv != NULL)
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100572 {
573 vim_free(curbuf->b_ffname);
Bram Moolenaar13568252018-03-16 20:46:58 +0100574 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100575 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200576 else
577 {
578 int i;
579 size_t len;
580 char_u *cmd, *p;
581
582 if (argvar->v_type == VAR_STRING)
583 {
584 cmd = argvar->vval.v_string;
585 if (cmd == NULL)
586 cmd = (char_u *)"";
587 else if (STRCMP(cmd, "NONE") == 0)
588 cmd = (char_u *)"pty";
589 }
590 else if (argvar->v_type != VAR_LIST
591 || argvar->vval.v_list == NULL
Bram Moolenaarb0992022020-01-30 14:55:42 +0100592 || argvar->vval.v_list->lv_len == 0
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100593 || (cmd = tv_get_string_chk(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200594 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
595 cmd = (char_u*)"";
596
597 len = STRLEN(cmd) + 10;
Bram Moolenaar51e14382019-05-25 20:21:28 +0200598 p = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200599
600 for (i = 0; p != NULL; ++i)
601 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100602 // Prepend a ! to the command name to avoid the buffer name equals
603 // the executable, otherwise ":w!" would overwrite it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200604 if (i == 0)
605 vim_snprintf((char *)p, len, "!%s", cmd);
606 else
607 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
608 if (buflist_findname(p) == NULL)
609 {
610 vim_free(curbuf->b_ffname);
611 curbuf->b_ffname = p;
612 break;
613 }
614 }
615 }
Bram Moolenaare010c722020-02-24 21:37:54 +0100616 vim_free(curbuf->b_sfname);
617 curbuf->b_sfname = vim_strsave(curbuf->b_ffname);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200618 curbuf->b_fname = curbuf->b_ffname;
619
Bram Moolenaar5e94a292020-03-19 18:46:57 +0100620 apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf);
621
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200622 if (opt->jo_term_opencmd != NULL)
623 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
624
625 if (opt->jo_eof_chars != NULL)
626 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
627
628 set_string_option_direct((char_u *)"buftype", -1,
629 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar7da1fb52018-08-04 16:54:11 +0200630 // Avoid that 'buftype' is reset when this buffer is entered.
631 curbuf->b_p_initialized = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200632
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100633 // Mark the buffer as not modifiable. It can only be made modifiable after
634 // the job finished.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200635 curbuf->b_p_ma = FALSE;
636
Bram Moolenaarb936b792020-09-04 18:34:09 +0200637 set_term_and_win_size(term, opt);
Bram Moolenaar4f974752019-02-17 17:44:42 +0100638#ifdef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200639 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
640#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200641 setup_job_options(opt, term->tl_rows, term->tl_cols);
642
Bram Moolenaar13568252018-03-16 20:46:58 +0100643 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100644 return curbuf;
645
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100646#if defined(FEAT_SESSION)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100647 // Remember the command for the session file.
Bram Moolenaar13568252018-03-16 20:46:58 +0100648 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100649 term->tl_command = vim_strsave((char_u *)"NONE");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100650 else if (argvar->v_type == VAR_STRING)
651 {
652 char_u *cmd = argvar->vval.v_string;
653
654 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
655 term->tl_command = vim_strsave(cmd);
656 }
657 else if (argvar->v_type == VAR_LIST
658 && argvar->vval.v_list != NULL
659 && argvar->vval.v_list->lv_len > 0)
660 {
661 garray_T ga;
662 listitem_T *item;
663
664 ga_init2(&ga, 1, 100);
Bram Moolenaaraeea7212020-04-02 18:50:46 +0200665 FOR_ALL_LIST_ITEMS(argvar->vval.v_list, item)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100666 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100667 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100668 char_u *p;
669
670 if (s == NULL)
671 break;
Bram Moolenaar21c1a0c2021-10-17 17:20:23 +0100672 p = vim_strsave_fnameescape(s, VSE_NONE);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100673 if (p == NULL)
674 break;
675 ga_concat(&ga, p);
676 vim_free(p);
677 ga_append(&ga, ' ');
678 }
679 if (item == NULL)
680 {
681 ga_append(&ga, NUL);
682 term->tl_command = ga.ga_data;
683 }
684 else
685 ga_clear(&ga);
686 }
687#endif
688
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100689 if (opt->jo_term_kill != NULL)
690 {
691 char_u *p = skiptowhite(opt->jo_term_kill);
692
693 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
694 }
695
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200696 if (opt->jo_term_api != NULL)
Bram Moolenaar21109272020-01-30 16:27:20 +0100697 {
698 char_u *p = skiptowhite(opt->jo_term_api);
699
700 term->tl_api = vim_strnsave(opt->jo_term_api, p - opt->jo_term_api);
701 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200702 else
703 term->tl_api = vim_strsave((char_u *)"Tapi_");
704
Bram Moolenaar83d47902020-03-26 20:34:00 +0100705 if (opt->jo_set2 & JO2_TERM_HIGHLIGHT)
706 term->tl_highlight_name = vim_strsave(opt->jo_term_highlight);
707
Bram Moolenaar30b9a412022-05-26 14:06:37 +0100708#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +0100709 // Save the user-defined palette, it is only used in GUI (or 'tgc' is on).
710 if (opt->jo_set2 & JO2_ANSI_COLORS)
711 {
712 term->tl_palette = ALLOC_MULT(long_u, 16);
713 if (term->tl_palette == NULL)
714 {
715 vim_free(term);
716 return NULL;
717 }
718 memcpy(term->tl_palette, opt->jo_ansi_colors, sizeof(long_u) * 16);
719 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +0100720#endif
LemonBoyb2b3acb2022-05-20 10:10:34 +0100721
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100722 // System dependent: setup the vterm and maybe start the job in it.
Bram Moolenaar13568252018-03-16 20:46:58 +0100723 if (argv == NULL
724 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200725 && argvar->vval.v_string != NULL
726 && STRCMP(argvar->vval.v_string, "NONE") == 0)
727 res = create_pty_only(term, opt);
728 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200729 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200730
731 newbuf = curbuf;
732 if (res == OK)
733 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100734 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200735 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
736 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100737#ifdef FEAT_GUI
738 if (term->tl_system)
739 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100740 // display first line below typed command
Bram Moolenaar13568252018-03-16 20:46:58 +0100741 term->tl_toprow = msg_row + 1;
742 term->tl_dirty_row_end = 0;
743 }
744#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200745
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100746 // Make sure we don't get stuck on sending keys to the job, it leads to
747 // a deadlock if the job is waiting for Vim to read.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200748 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
749
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200750 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200751 {
752 --curbuf->b_nwindows;
753 curbuf = old_curbuf;
754 curwin->w_buffer = curbuf;
755 ++curbuf->b_nwindows;
756 }
Bram Moolenaar81035272021-12-16 18:02:07 +0000757 else if (vgetc_busy
758#ifdef FEAT_TIMERS
759 || timer_busy
760#endif
761 || input_busy)
762 {
763 char_u ignore[4];
764
765 // When waiting for input need to return and possibly end up in
766 // terminal_loop() instead.
767 ignore[0] = K_SPECIAL;
768 ignore[1] = KS_EXTRA;
769 ignore[2] = KE_IGNORE;
770 ignore[3] = NUL;
771 ins_typebuf(ignore, REMAP_NONE, 0, TRUE, FALSE);
772 typebuf_was_filled = TRUE;
773 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200774 }
775 else
776 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100777 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200778 return NULL;
779 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100780
Bram Moolenaar13568252018-03-16 20:46:58 +0100781 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar28ed4df2019-10-26 16:21:40 +0200782 if (!opt->jo_hidden && !(flags & TERM_START_SYSTEM))
783 apply_autocmds(EVENT_TERMINALWINOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200784 return newbuf;
785}
786
787/*
788 * ":terminal": open a terminal window and execute a job in it.
789 */
790 void
791ex_terminal(exarg_T *eap)
792{
793 typval_T argvar[2];
794 jobopt_T opt;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100795 int opt_shell = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200796 char_u *cmd;
797 char_u *tofree = NULL;
798
799 init_job_options(&opt);
800
801 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100802 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200803 {
804 char_u *p, *ep;
805
806 cmd += 2;
807 p = skiptowhite(cmd);
808 ep = vim_strchr(cmd, '=');
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200809 if (ep != NULL)
810 {
811 if (ep < p)
812 p = ep;
813 else
814 ep = NULL;
815 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200816
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200817# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
818 && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
819 if (OPTARG_HAS("close"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200820 opt.jo_term_finish = 'c';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200821 else if (OPTARG_HAS("noclose"))
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100822 opt.jo_term_finish = 'n';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200823 else if (OPTARG_HAS("open"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200824 opt.jo_term_finish = 'o';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200825 else if (OPTARG_HAS("curwin"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200826 opt.jo_curwin = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200827 else if (OPTARG_HAS("hidden"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200828 opt.jo_hidden = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200829 else if (OPTARG_HAS("norestore"))
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100830 opt.jo_term_norestore = 1;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100831 else if (OPTARG_HAS("shell"))
832 opt_shell = TRUE;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200833 else if (OPTARG_HAS("kill") && ep != NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100834 {
835 opt.jo_set2 |= JO2_TERM_KILL;
836 opt.jo_term_kill = ep + 1;
837 p = skiptowhite(cmd);
838 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200839 else if (OPTARG_HAS("api"))
840 {
841 opt.jo_set2 |= JO2_TERM_API;
842 if (ep != NULL)
843 {
844 opt.jo_term_api = ep + 1;
845 p = skiptowhite(cmd);
846 }
847 else
848 opt.jo_term_api = NULL;
849 }
850 else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200851 {
852 opt.jo_set2 |= JO2_TERM_ROWS;
853 opt.jo_term_rows = atoi((char *)ep + 1);
854 p = skiptowhite(cmd);
855 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200856 else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200857 {
858 opt.jo_set2 |= JO2_TERM_COLS;
859 opt.jo_term_cols = atoi((char *)ep + 1);
860 p = skiptowhite(cmd);
861 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200862 else if (OPTARG_HAS("eof") && ep != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200863 {
864 char_u *buf = NULL;
865 char_u *keys;
866
Bram Moolenaar21109272020-01-30 16:27:20 +0100867 vim_free(opt.jo_eof_chars);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200868 p = skiptowhite(cmd);
869 *p = NUL;
Bram Moolenaar459fd782019-10-13 16:43:39 +0200870 keys = replace_termcodes(ep + 1, &buf,
871 REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200872 opt.jo_set2 |= JO2_EOF_CHARS;
873 opt.jo_eof_chars = vim_strsave(keys);
874 vim_free(buf);
875 *p = ' ';
876 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100877#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100878 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "type", 4) == 0
879 && ep != NULL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100880 {
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100881 int tty_type = NUL;
882
883 p = skiptowhite(cmd);
884 if (STRNICMP(ep + 1, "winpty", p - (ep + 1)) == 0)
885 tty_type = 'w';
886 else if (STRNICMP(ep + 1, "conpty", p - (ep + 1)) == 0)
887 tty_type = 'c';
888 else
889 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +0000890 semsg(e_invalid_value_for_argument_str, "type");
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100891 goto theend;
892 }
893 opt.jo_set2 |= JO2_TTY_TYPE;
894 opt.jo_tty_type = tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100895 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100896#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200897 else
898 {
899 if (*p)
900 *p = NUL;
Bram Moolenaard82a47d2022-01-05 20:24:39 +0000901 semsg(_(e_invalid_attribute_str), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100902 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200903 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200904# undef OPTARG_HAS
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200905 cmd = skipwhite(p);
906 }
907 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100908 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100909 // Make a copy of 'shell', an autocommand may change the option.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200910 tofree = cmd = vim_strsave(p_sh);
911
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100912 // default to close when the shell exits
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100913 if (opt.jo_term_finish == NUL)
Bram Moolenaare2978022020-04-26 14:47:44 +0200914 opt.jo_term_finish = TL_FINISH_CLOSE;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100915 }
916
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200917 if (eap->addr_count > 0)
918 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100919 // Write lines from current buffer to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200920 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
921 opt.jo_io[PART_IN] = JIO_BUFFER;
922 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
923 opt.jo_in_top = eap->line1;
924 opt.jo_in_bot = eap->line2;
925 }
926
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100927 if (opt_shell && tofree == NULL)
928 {
929#ifdef UNIX
930 char **argv = NULL;
931 char_u *tofree1 = NULL;
932 char_u *tofree2 = NULL;
933
934 // :term ++shell command
935 if (unix_build_argv(cmd, &argv, &tofree1, &tofree2) == OK)
936 term_start(NULL, argv, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaaradf4aa22019-11-10 22:36:44 +0100937 vim_free(argv);
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100938 vim_free(tofree1);
939 vim_free(tofree2);
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100940 goto theend;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100941#else
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100942# ifdef MSWIN
943 long_u cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
944 char_u *newcmd;
945
946 newcmd = alloc(cmdlen);
947 if (newcmd == NULL)
948 goto theend;
949 tofree = newcmd;
950 vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd);
951 cmd = newcmd;
952# else
Bram Moolenaar9a846fb2022-01-01 21:59:18 +0000953 emsg(_(e_sorry_plusplusshell_not_supported_on_this_system));
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100954 goto theend;
955# endif
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100956#endif
957 }
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100958 argvar[0].v_type = VAR_STRING;
959 argvar[0].vval.v_string = cmd;
960 argvar[1].v_type = VAR_UNKNOWN;
961 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100962
963theend:
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100964 vim_free(tofree);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200965 vim_free(opt.jo_eof_chars);
966}
967
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100968#if defined(FEAT_SESSION) || defined(PROTO)
969/*
970 * Write a :terminal command to the session file to restore the terminal in
971 * window "wp".
972 * Return FAIL if writing fails.
973 */
974 int
Bram Moolenaar0e655112020-09-11 20:36:36 +0200975term_write_session(FILE *fd, win_T *wp, hashtab_T *terminal_bufs)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100976{
Bram Moolenaar0e655112020-09-11 20:36:36 +0200977 const int bufnr = wp->w_buffer->b_fnum;
978 term_T *term = wp->w_buffer->b_term;
979
Bram Moolenaarc2c82052020-09-11 22:10:22 +0200980 if (terminal_bufs != NULL && wp->w_buffer->b_nwindows > 1)
Bram Moolenaar0e655112020-09-11 20:36:36 +0200981 {
982 // There are multiple views into this terminal buffer. We don't want to
983 // create the terminal multiple times. If it's the first time, create,
984 // otherwise link to the first buffer.
985 char id_as_str[NUMBUFLEN];
986 hashitem_T *entry;
987
988 vim_snprintf(id_as_str, sizeof(id_as_str), "%d", bufnr);
989
990 entry = hash_find(terminal_bufs, (char_u *)id_as_str);
991 if (!HASHITEM_EMPTY(entry))
992 {
993 // we've already opened this terminal buffer
994 if (fprintf(fd, "execute 'buffer ' . s:term_buf_%d", bufnr) < 0)
995 return FAIL;
996 return put_eol(fd);
997 }
998 }
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100999
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001000 // Create the terminal and run the command. This is not without
1001 // risk, but let's assume the user only creates a session when this
1002 // will be OK.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01001003 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
1004 term->tl_cols, term->tl_rows) < 0)
1005 return FAIL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01001006#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01001007 if (fprintf(fd, "++type=%s ", term->tl_job->jv_tty_type) < 0)
1008 return FAIL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001009#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01001010 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
1011 return FAIL;
Bram Moolenaar0e655112020-09-11 20:36:36 +02001012 if (put_eol(fd) != OK)
1013 return FAIL;
1014
1015 if (fprintf(fd, "let s:term_buf_%d = bufnr()", bufnr) < 0)
1016 return FAIL;
1017
Bram Moolenaarc2c82052020-09-11 22:10:22 +02001018 if (terminal_bufs != NULL && wp->w_buffer->b_nwindows > 1)
Bram Moolenaar0e655112020-09-11 20:36:36 +02001019 {
1020 char *hash_key = alloc(NUMBUFLEN);
1021
1022 vim_snprintf(hash_key, NUMBUFLEN, "%d", bufnr);
1023 hash_add(terminal_bufs, (char_u *)hash_key);
1024 }
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01001025
1026 return put_eol(fd);
1027}
1028
1029/*
1030 * Return TRUE if "buf" has a terminal that should be restored.
1031 */
1032 int
1033term_should_restore(buf_T *buf)
1034{
1035 term_T *term = buf->b_term;
1036
1037 return term != NULL && (term->tl_command == NULL
1038 || STRCMP(term->tl_command, "NONE") != 0);
1039}
1040#endif
1041
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001042/*
1043 * Free the scrollback buffer for "term".
1044 */
1045 static void
1046free_scrollback(term_T *term)
1047{
1048 int i;
1049
1050 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
1051 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
1052 ga_clear(&term->tl_scrollback);
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001053 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
1054 vim_free(((sb_line_T *)term->tl_scrollback_postponed.ga_data + i)->sb_cells);
1055 ga_clear(&term->tl_scrollback_postponed);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001056}
1057
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001058
1059// Terminals that need to be freed soon.
Bram Moolenaar840d16f2019-09-10 21:27:18 +02001060static term_T *terminals_to_free = NULL;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001061
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001062/*
1063 * Free a terminal and everything it refers to.
1064 * Kills the job if there is one.
1065 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001066 * The actual terminal structure is freed later in free_unused_terminals(),
1067 * because callbacks may wipe out a buffer while the terminal is still
1068 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001069 */
1070 void
1071free_terminal(buf_T *buf)
1072{
1073 term_T *term = buf->b_term;
1074 term_T *tp;
1075
1076 if (term == NULL)
1077 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001078
1079 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001080 if (first_term == term)
1081 first_term = term->tl_next;
1082 else
1083 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
1084 if (tp->tl_next == term)
1085 {
1086 tp->tl_next = term->tl_next;
1087 break;
1088 }
1089
1090 if (term->tl_job != NULL)
1091 {
1092 if (term->tl_job->jv_status != JOB_ENDED
1093 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +01001094 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001095 job_stop(term->tl_job, NULL, "kill");
1096 job_unref(term->tl_job);
1097 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001098 term->tl_next = terminals_to_free;
1099 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001100
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001101 buf->b_term = NULL;
1102 if (in_terminal_loop == term)
1103 in_terminal_loop = NULL;
1104}
1105
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001106 void
1107free_unused_terminals()
1108{
1109 while (terminals_to_free != NULL)
1110 {
1111 term_T *term = terminals_to_free;
1112
1113 terminals_to_free = term->tl_next;
1114
1115 free_scrollback(term);
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02001116 ga_clear(&term->tl_osc_buf);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001117
1118 term_free_vterm(term);
Bram Moolenaard2842ea2019-09-26 23:08:54 +02001119 vim_free(term->tl_api);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001120 vim_free(term->tl_title);
1121#ifdef FEAT_SESSION
1122 vim_free(term->tl_command);
1123#endif
1124 vim_free(term->tl_kill);
1125 vim_free(term->tl_status_text);
1126 vim_free(term->tl_opencmd);
1127 vim_free(term->tl_eof_chars);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001128 vim_free(term->tl_arg0_cmd);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001129#ifdef MSWIN
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001130 if (term->tl_out_fd != NULL)
1131 fclose(term->tl_out_fd);
1132#endif
Bram Moolenaar83d47902020-03-26 20:34:00 +01001133 vim_free(term->tl_highlight_name);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001134 vim_free(term->tl_cursor_color);
LemonBoyb2b3acb2022-05-20 10:10:34 +01001135 vim_free(term->tl_palette);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001136 vim_free(term);
1137 }
1138}
1139
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001140/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001141 * Get the part that is connected to the tty. Normally this is PART_IN, but
1142 * when writing buffer lines to the job it can be another. This makes it
1143 * possible to do "1,5term vim -".
1144 */
1145 static ch_part_T
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02001146get_tty_part(term_T *term UNUSED)
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001147{
1148#ifdef UNIX
1149 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
1150 int i;
1151
1152 for (i = 0; i < 3; ++i)
1153 {
1154 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
1155
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01001156 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001157 return parts[i];
1158 }
1159#endif
1160 return PART_IN;
1161}
1162
1163/*
Bram Moolenaara48d4e42021-12-08 22:13:38 +00001164 * Read any vterm output and send it on the channel.
1165 */
1166 static void
1167term_forward_output(term_T *term)
1168{
1169 VTerm *vterm = term->tl_vterm;
1170 char buf[KEY_BUF_LEN];
1171 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
1172
1173 if (curlen > 0)
1174 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1175 (char_u *)buf, (int)curlen, NULL);
1176}
1177
1178/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001179 * Write job output "msg[len]" to the vterm.
1180 */
1181 static void
Bram Moolenaar36968af2021-11-15 17:13:11 +00001182term_write_job_output(term_T *term, char_u *msg_arg, size_t len_arg)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001183{
Bram Moolenaar36968af2021-11-15 17:13:11 +00001184 char_u *msg = msg_arg;
1185 size_t len = len_arg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001186 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001187 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar36968af2021-11-15 17:13:11 +00001188 size_t limit = term->tl_buffer->b_p_twsl * term->tl_cols * 3;
1189
1190 // Limit the length to 'termwinscroll' * cols * 3 bytes. Keep the text at
1191 // the end.
1192 if (len > limit)
1193 {
1194 char_u *p = msg + len - limit;
1195
1196 p -= (*mb_head_off)(msg, p);
1197 len -= p - msg;
1198 msg = p;
1199 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001200
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001201 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001202
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001203 // flush vterm buffer when vterm responded to control sequence
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001204 if (prevlen != vterm_output_get_buffer_current(vterm))
Bram Moolenaara48d4e42021-12-08 22:13:38 +00001205 term_forward_output(term);
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001206
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001207 // this invokes the damage callbacks
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001208 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
1209}
1210
1211 static void
1212update_cursor(term_T *term, int redraw)
1213{
1214 if (term->tl_normal_mode)
1215 return;
Bram Moolenaar13568252018-03-16 20:46:58 +01001216#ifdef FEAT_GUI
1217 if (term->tl_system)
1218 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
1219 term->tl_cursor_pos.col);
1220 else
1221#endif
1222 setcursor();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001223 if (redraw)
1224 {
1225 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
1226 cursor_on();
1227 out_flush();
1228#ifdef FEAT_GUI
1229 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001230 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001231 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001232 gui_mch_flush();
1233 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001234#endif
1235 }
1236}
1237
1238/*
1239 * Invoked when "msg" output from a job was received. Write it to the terminal
1240 * of "buffer".
1241 */
1242 void
1243write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1244{
1245 size_t len = STRLEN(msg);
1246 term_T *term = buffer->b_term;
1247
Bram Moolenaar4f974752019-02-17 17:44:42 +01001248#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001249 // Win32: Cannot redirect output of the job, intercept it here and write to
1250 // the file.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02001251 if (term->tl_out_fd != NULL)
1252 {
1253 ch_log(channel, "Writing %d bytes to output file", (int)len);
1254 fwrite(msg, len, 1, term->tl_out_fd);
1255 return;
1256 }
1257#endif
1258
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001259 if (term->tl_vterm == NULL)
1260 {
1261 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1262 return;
1263 }
1264 ch_log(channel, "writing %d bytes to terminal", (int)len);
Bram Moolenaarebec3e22020-11-28 20:22:06 +01001265 cursor_off();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001266 term_write_job_output(term, msg, len);
1267
Bram Moolenaar13568252018-03-16 20:46:58 +01001268#ifdef FEAT_GUI
1269 if (term->tl_system)
1270 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001271 // show system output, scrolling up the screen as needed
Bram Moolenaar13568252018-03-16 20:46:58 +01001272 update_system_term(term);
1273 update_cursor(term, TRUE);
1274 }
1275 else
1276#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001277 // In Terminal-Normal mode we are displaying the buffer, not the terminal
1278 // contents, thus no screen update is needed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001279 if (!term->tl_normal_mode)
1280 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001281 // Don't use update_screen() when editing the command line, it gets
1282 // cleared.
1283 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001284 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar24959102022-05-07 20:01:16 +01001285 if (buffer == curbuf && (State & MODE_CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001286 {
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001287 update_screen(UPD_VALID_NO_UPDATE);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001288 // update_screen() can be slow, check the terminal wasn't closed
1289 // already
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001290 if (buffer == curbuf && curbuf->b_term != NULL)
1291 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001292 }
1293 else
Bram Moolenaare5050712021-12-09 10:51:05 +00001294 redraw_after_callback(TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001295 }
1296}
1297
1298/*
1299 * Send a mouse position and click to the vterm
1300 */
1301 static int
1302term_send_mouse(VTerm *vterm, int button, int pressed)
1303{
1304 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01001305 int row = mouse_row - W_WINROW(curwin);
1306 int col = mouse_col - curwin->w_wincol;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001307
Bram Moolenaar219c7d02020-02-01 21:57:29 +01001308#ifdef FEAT_PROP_POPUP
1309 if (popup_is_popup(curwin))
1310 {
1311 row -= popup_top_extra(curwin);
1312 col -= popup_left_extra(curwin);
1313 }
1314#endif
1315 vterm_mouse_move(vterm, row, col, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001316 if (button != 0)
1317 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001318 return TRUE;
1319}
1320
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001321static int enter_mouse_col = -1;
1322static int enter_mouse_row = -1;
1323
1324/*
1325 * Handle a mouse click, drag or release.
1326 * Return TRUE when a mouse event is sent to the terminal.
1327 */
1328 static int
1329term_mouse_click(VTerm *vterm, int key)
1330{
1331#if defined(FEAT_CLIPBOARD)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001332 // For modeless selection mouse drag and release events are ignored, unless
1333 // they are preceded with a mouse down event
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001334 static int ignore_drag_release = TRUE;
1335 VTermMouseState mouse_state;
1336
1337 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1338 if (mouse_state.flags == 0)
1339 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001340 // Terminal is not using the mouse, use modeless selection.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001341 switch (key)
1342 {
1343 case K_LEFTDRAG:
1344 case K_LEFTRELEASE:
1345 case K_RIGHTDRAG:
1346 case K_RIGHTRELEASE:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001347 // Ignore drag and release events when the button-down wasn't
1348 // seen before.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001349 if (ignore_drag_release)
1350 {
1351 int save_mouse_col, save_mouse_row;
1352
1353 if (enter_mouse_col < 0)
1354 break;
1355
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001356 // mouse click in the window gave us focus, handle that
1357 // click now
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001358 save_mouse_col = mouse_col;
1359 save_mouse_row = mouse_row;
1360 mouse_col = enter_mouse_col;
1361 mouse_row = enter_mouse_row;
1362 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1363 mouse_col = save_mouse_col;
1364 mouse_row = save_mouse_row;
1365 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001366 // FALLTHROUGH
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001367 case K_LEFTMOUSE:
1368 case K_RIGHTMOUSE:
1369 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1370 ignore_drag_release = TRUE;
1371 else
1372 ignore_drag_release = FALSE;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001373 // Should we call mouse_has() here?
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001374 if (clip_star.available)
1375 {
1376 int button, is_click, is_drag;
1377
1378 button = get_mouse_button(KEY2TERMCAP1(key),
1379 &is_click, &is_drag);
1380 if (mouse_model_popup() && button == MOUSE_LEFT
1381 && (mod_mask & MOD_MASK_SHIFT))
1382 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001383 // Translate shift-left to right button.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001384 button = MOUSE_RIGHT;
1385 mod_mask &= ~MOD_MASK_SHIFT;
1386 }
1387 clip_modeless(button, is_click, is_drag);
1388 }
1389 break;
1390
1391 case K_MIDDLEMOUSE:
1392 if (clip_star.available)
1393 insert_reg('*', TRUE);
1394 break;
1395 }
1396 enter_mouse_col = -1;
1397 return FALSE;
1398 }
1399#endif
1400 enter_mouse_col = -1;
1401
1402 switch (key)
1403 {
1404 case K_LEFTMOUSE:
1405 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1406 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1407 case K_LEFTRELEASE:
1408 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1409 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1410 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1411 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1412 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1413 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1414 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1415 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1416 }
1417 return TRUE;
1418}
1419
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001420/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001421 * Convert typed key "c" with modifiers "modmask" into bytes to send to the
1422 * job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001423 * Return the number of bytes in "buf".
1424 */
1425 static int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001426term_convert_key(term_T *term, int c, int modmask, char *buf)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001427{
1428 VTerm *vterm = term->tl_vterm;
1429 VTermKey key = VTERM_KEY_NONE;
1430 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001431 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001432
1433 switch (c)
1434 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001435 // don't use VTERM_KEY_ENTER, it may do an unwanted conversion
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001436
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001437 // don't use VTERM_KEY_BACKSPACE, it always
1438 // becomes 0x7f DEL
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001439 case K_BS: c = term_backspace_char; break;
1440
1441 case ESC: key = VTERM_KEY_ESCAPE; break;
1442 case K_DEL: key = VTERM_KEY_DEL; break;
1443 case K_DOWN: key = VTERM_KEY_DOWN; break;
1444 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1445 key = VTERM_KEY_DOWN; break;
1446 case K_END: key = VTERM_KEY_END; break;
1447 case K_S_END: mod = VTERM_MOD_SHIFT;
1448 key = VTERM_KEY_END; break;
1449 case K_C_END: mod = VTERM_MOD_CTRL;
1450 key = VTERM_KEY_END; break;
1451 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1452 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1453 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1454 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1455 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1456 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1457 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1458 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1459 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1460 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1461 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1462 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1463 case K_HOME: key = VTERM_KEY_HOME; break;
1464 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1465 key = VTERM_KEY_HOME; break;
1466 case K_C_HOME: mod = VTERM_MOD_CTRL;
1467 key = VTERM_KEY_HOME; break;
1468 case K_INS: key = VTERM_KEY_INS; break;
1469 case K_K0: key = VTERM_KEY_KP_0; break;
1470 case K_K1: key = VTERM_KEY_KP_1; break;
1471 case K_K2: key = VTERM_KEY_KP_2; break;
1472 case K_K3: key = VTERM_KEY_KP_3; break;
1473 case K_K4: key = VTERM_KEY_KP_4; break;
1474 case K_K5: key = VTERM_KEY_KP_5; break;
1475 case K_K6: key = VTERM_KEY_KP_6; break;
1476 case K_K7: key = VTERM_KEY_KP_7; break;
1477 case K_K8: key = VTERM_KEY_KP_8; break;
1478 case K_K9: key = VTERM_KEY_KP_9; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001479 case K_KDEL: key = VTERM_KEY_DEL; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001480 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001481 case K_KEND: key = VTERM_KEY_KP_1; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001482 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001483 case K_KHOME: key = VTERM_KEY_KP_7; break; // TODO
1484 case K_KINS: key = VTERM_KEY_KP_0; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001485 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1486 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001487 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; // TODO
1488 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001489 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1490 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1491 case K_LEFT: key = VTERM_KEY_LEFT; break;
1492 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1493 key = VTERM_KEY_LEFT; break;
1494 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1495 key = VTERM_KEY_LEFT; break;
1496 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1497 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1498 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1499 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1500 key = VTERM_KEY_RIGHT; break;
1501 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1502 key = VTERM_KEY_RIGHT; break;
1503 case K_UP: key = VTERM_KEY_UP; break;
1504 case K_S_UP: mod = VTERM_MOD_SHIFT;
1505 key = VTERM_KEY_UP; break;
1506 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001507 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1508 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001509
Bram Moolenaara42ad572017-11-16 13:08:04 +01001510 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1511 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02001512 case K_MOUSELEFT: other = term_send_mouse(vterm, 7, 1); break;
1513 case K_MOUSERIGHT: other = term_send_mouse(vterm, 6, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001514
1515 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001516 case K_LEFTMOUSE_NM:
1517 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001518 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001519 case K_LEFTRELEASE_NM:
1520 case K_MOUSEMOVE:
1521 case K_MIDDLEMOUSE:
1522 case K_MIDDLEDRAG:
1523 case K_MIDDLERELEASE:
1524 case K_RIGHTMOUSE:
1525 case K_RIGHTDRAG:
1526 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1527 return 0;
1528 other = TRUE;
1529 break;
1530
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001531 case K_X1MOUSE: /* TODO */ return 0;
1532 case K_X1DRAG: /* TODO */ return 0;
1533 case K_X1RELEASE: /* TODO */ return 0;
1534 case K_X2MOUSE: /* TODO */ return 0;
1535 case K_X2DRAG: /* TODO */ return 0;
1536 case K_X2RELEASE: /* TODO */ return 0;
1537
1538 case K_IGNORE: return 0;
1539 case K_NOP: return 0;
1540 case K_UNDO: return 0;
1541 case K_HELP: return 0;
1542 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1543 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1544 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1545 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1546 case K_SELECT: return 0;
1547#ifdef FEAT_GUI
1548 case K_VER_SCROLLBAR: return 0;
1549 case K_HOR_SCROLLBAR: return 0;
1550#endif
1551#ifdef FEAT_GUI_TABLINE
1552 case K_TABLINE: return 0;
1553 case K_TABMENU: return 0;
1554#endif
1555#ifdef FEAT_NETBEANS_INTG
1556 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1557#endif
1558#ifdef FEAT_DND
1559 case K_DROP: return 0;
1560#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001561 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001562 case K_PS: vterm_keyboard_start_paste(vterm);
1563 other = TRUE;
1564 break;
1565 case K_PE: vterm_keyboard_end_paste(vterm);
1566 other = TRUE;
1567 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001568 }
1569
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001570 // add modifiers for the typed key
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001571 if (modmask & MOD_MASK_SHIFT)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001572 mod |= VTERM_MOD_SHIFT;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001573 if (modmask & MOD_MASK_CTRL)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001574 mod |= VTERM_MOD_CTRL;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001575 if (modmask & (MOD_MASK_ALT | MOD_MASK_META))
Bram Moolenaar459fd782019-10-13 16:43:39 +02001576 mod |= VTERM_MOD_ALT;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001577
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001578 /*
1579 * Convert special keys to vterm keys:
1580 * - Write keys to vterm: vterm_keyboard_key()
1581 * - Write output to channel.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001582 */
1583 if (key != VTERM_KEY_NONE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001584 // Special key, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001585 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001586 else if (!other)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001587 // Normal character, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001588 vterm_keyboard_unichar(vterm, c, mod);
1589
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001590 // Read back the converted escape sequence.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001591 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1592}
1593
1594/*
1595 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001596 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001597 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001598 */
1599 static int
1600term_job_running_check(term_T *term, int check_job_status)
1601{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001602 // Also consider the job finished when the channel is closed, to avoid a
1603 // race condition when updating the title.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001604 if (term != NULL
1605 && term->tl_job != NULL
1606 && channel_is_open(term->tl_job->jv_channel))
1607 {
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001608 job_T *job = term->tl_job;
1609
Dominique Pelleaf4a61a2021-12-27 17:21:41 +00001610 // Careful: Checking the job status may invoke callbacks, which close
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001611 // the buffer and terminate "term". However, "job" will not be freed
1612 // yet.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001613 if (check_job_status)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001614 job_status(job);
1615 return (job->jv_status == JOB_STARTED
1616 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001617 }
1618 return FALSE;
1619}
1620
1621/*
1622 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001623 */
1624 int
1625term_job_running(term_T *term)
1626{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001627 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001628}
1629
1630/*
Bram Moolenaar9e636b92022-05-29 22:37:05 +01001631 * Return TRUE if the job for "term" is still running, ignoring the job was
1632 * "NONE".
1633 */
1634 int
1635term_job_running_not_none(term_T *term)
1636{
1637 return term_job_running(term) && !term_none_open(term);
1638}
1639
1640/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001641 * Return TRUE if "term" has an active channel and used ":term NONE".
1642 */
1643 int
1644term_none_open(term_T *term)
1645{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001646 // Also consider the job finished when the channel is closed, to avoid a
1647 // race condition when updating the title.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001648 return term != NULL
1649 && term->tl_job != NULL
1650 && channel_is_open(term->tl_job->jv_channel)
1651 && term->tl_job->jv_channel->ch_keep_open;
1652}
1653
Yee Cheng Chin15b314f2022-10-09 18:53:32 +01001654//
1655// Used to confirm whether we would like to kill a terminal.
1656// Return OK when the user confirms to kill it.
1657// Return FAIL if the user selects otherwise.
1658//
1659 int
1660term_confirm_stop(buf_T *buf)
1661{
1662 char_u buff[DIALOG_MSG_SIZE];
1663 int ret;
1664
1665 dialog_msg(buff, _("Kill job in \"%s\"?"), buf_get_fname(buf));
1666 ret = vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1);
1667 if (ret == VIM_YES)
1668 return OK;
1669 else
1670 return FAIL;
1671}
1672
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001673/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001674 * Used when exiting: kill the job in "buf" if so desired.
1675 * Return OK when the job finished.
1676 * Return FAIL when the job is still running.
1677 */
1678 int
1679term_try_stop_job(buf_T *buf)
1680{
1681 int count;
1682 char *how = (char *)buf->b_term->tl_kill;
1683
1684#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
Bram Moolenaare1004402020-10-24 20:49:43 +02001685 if ((how == NULL || *how == NUL)
1686 && (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)))
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001687 {
Yee Cheng Chin15b314f2022-10-09 18:53:32 +01001688 if (term_confirm_stop(buf) == OK)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001689 how = "kill";
Yee Cheng Chin15b314f2022-10-09 18:53:32 +01001690 else
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001691 return FAIL;
1692 }
1693#endif
1694 if (how == NULL || *how == NUL)
1695 return FAIL;
1696
1697 job_stop(buf->b_term->tl_job, NULL, how);
1698
Bram Moolenaar9172d232019-01-29 23:06:54 +01001699 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001700 for (count = 0; count < 100; ++count)
1701 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001702 job_T *job;
1703
1704 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001705 if (!buf_valid(buf)
1706 || buf->b_term == NULL
1707 || buf->b_term->tl_job == NULL)
1708 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001709 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001710
Bram Moolenaar9172d232019-01-29 23:06:54 +01001711 // Call job_status() to update jv_status. It may cause the job to be
1712 // cleaned up but it won't be freed.
1713 job_status(job);
1714 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001715 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001716
Bram Moolenaar8f7ab4b2019-10-23 23:16:45 +02001717 ui_delay(10L, TRUE);
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02001718 term_flush_messages();
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001719 }
1720 return FAIL;
1721}
1722
1723/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001724 * Add the last line of the scrollback buffer to the buffer in the window.
1725 */
1726 static void
1727add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1728{
1729 buf_T *buf = term->tl_buffer;
1730 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1731 linenr_T lnum = buf->b_ml.ml_line_count;
1732
Bram Moolenaar4f974752019-02-17 17:44:42 +01001733#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001734 if (!enc_utf8 && enc_codepage > 0)
1735 {
1736 WCHAR *ret = NULL;
1737 int length = 0;
1738
1739 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1740 &ret, &length);
1741 if (ret != NULL)
1742 {
1743 WideCharToMultiByte_alloc(enc_codepage, 0,
1744 ret, length, (char **)&text, &len, 0, 0);
1745 vim_free(ret);
1746 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1747 vim_free(text);
1748 }
1749 }
1750 else
1751#endif
1752 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1753 if (empty)
1754 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001755 // Delete the empty line that was in the empty buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001756 curbuf = buf;
Bram Moolenaarca70c072020-05-30 20:30:46 +02001757 ml_delete(1);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001758 curbuf = curwin->w_buffer;
1759 }
1760}
1761
1762 static void
1763cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1764{
1765 attr->width = cell->width;
1766 attr->attrs = cell->attrs;
1767 attr->fg = cell->fg;
1768 attr->bg = cell->bg;
1769}
1770
1771 static int
1772equal_celattr(cellattr_T *a, cellattr_T *b)
1773{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02001774 // We only compare the RGB colors, ignoring the ANSI index and type.
1775 // Thus black set explicitly is equal the background black.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001776 return a->fg.red == b->fg.red
1777 && a->fg.green == b->fg.green
1778 && a->fg.blue == b->fg.blue
1779 && a->bg.red == b->bg.red
1780 && a->bg.green == b->bg.green
1781 && a->bg.blue == b->bg.blue;
1782}
1783
Bram Moolenaard96ff162018-02-18 22:13:29 +01001784/*
1785 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1786 * line at this position. Otherwise at the end.
1787 */
1788 static int
1789add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1790{
1791 if (ga_grow(&term->tl_scrollback, 1) == OK)
1792 {
1793 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1794 + term->tl_scrollback.ga_len;
1795
1796 if (lnum > 0)
1797 {
1798 int i;
1799
1800 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
1801 {
1802 *line = *(line - 1);
1803 --line;
1804 }
1805 }
1806 line->sb_cols = 0;
1807 line->sb_cells = NULL;
1808 line->sb_fill_attr = *fill_attr;
1809 ++term->tl_scrollback.ga_len;
1810 return OK;
1811 }
1812 return FALSE;
1813}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001814
1815/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001816 * Remove the terminal contents from the scrollback and the buffer.
1817 * Used before adding a new scrollback line or updating the buffer for lines
1818 * displayed in the terminal.
1819 */
1820 static void
1821cleanup_scrollback(term_T *term)
1822{
1823 sb_line_T *line;
1824 garray_T *gap;
1825
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001826 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001827 gap = &term->tl_scrollback;
1828 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1829 && gap->ga_len > 0)
1830 {
Bram Moolenaarca70c072020-05-30 20:30:46 +02001831 ml_delete(curbuf->b_ml.ml_line_count);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001832 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1833 vim_free(line->sb_cells);
1834 --gap->ga_len;
1835 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001836 curbuf = curwin->w_buffer;
1837 if (curbuf == term->tl_buffer)
1838 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001839}
1840
1841/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001842 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001843 */
1844 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001845update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001846{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001847 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001848 int len;
1849 int lines_skipped = 0;
1850 VTermPos pos;
1851 VTermScreenCell cell;
1852 cellattr_T fill_attr, new_fill_attr;
1853 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001854
1855 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1856 "Adding terminal window snapshot to buffer");
1857
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001858 // First remove the lines that were appended before, they might be
1859 // outdated.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001860 cleanup_scrollback(term);
1861
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001862 screen = vterm_obtain_screen(term->tl_vterm);
1863 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001864 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1865 {
1866 len = 0;
1867 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1868 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1869 && cell.chars[0] != NUL)
1870 {
1871 len = pos.col + 1;
1872 new_fill_attr = term->tl_default_color;
1873 }
1874 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001875 // Assume the last attr is the filler attr.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001876 cell2cellattr(&cell, &new_fill_attr);
1877
1878 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1879 ++lines_skipped;
1880 else
1881 {
1882 while (lines_skipped > 0)
1883 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001884 // Line was skipped, add an empty line.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001885 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001886 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001887 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001888 }
1889
1890 if (len == 0)
1891 p = NULL;
1892 else
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001893 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001894 if ((p != NULL || len == 0)
1895 && ga_grow(&term->tl_scrollback, 1) == OK)
1896 {
1897 garray_T ga;
1898 int width;
1899 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1900 + term->tl_scrollback.ga_len;
1901
1902 ga_init2(&ga, 1, 100);
1903 for (pos.col = 0; pos.col < len; pos.col += width)
1904 {
1905 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1906 {
1907 width = 1;
Bram Moolenaara80faa82020-04-12 19:37:17 +02001908 CLEAR_POINTER(p + pos.col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001909 if (ga_grow(&ga, 1) == OK)
1910 ga.ga_len += utf_char2bytes(' ',
1911 (char_u *)ga.ga_data + ga.ga_len);
1912 }
1913 else
1914 {
1915 width = cell.width;
1916
1917 cell2cellattr(&cell, &p[pos.col]);
Bram Moolenaar927495b2020-11-06 17:58:35 +01001918 if (width == 2)
1919 // second cell of double-width character has the
1920 // same attributes.
1921 p[pos.col + 1] = p[pos.col];
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001922
Bram Moolenaara79fd562018-12-20 20:47:32 +01001923 // Each character can be up to 6 bytes.
1924 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001925 {
1926 int i;
1927 int c;
1928
1929 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1930 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1931 (char_u *)ga.ga_data + ga.ga_len);
1932 }
1933 }
1934 }
1935 line->sb_cols = len;
1936 line->sb_cells = p;
1937 line->sb_fill_attr = new_fill_attr;
1938 fill_attr = new_fill_attr;
1939 ++term->tl_scrollback.ga_len;
1940
1941 if (ga_grow(&ga, 1) == FAIL)
1942 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1943 else
1944 {
1945 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1946 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1947 }
1948 ga_clear(&ga);
1949 }
1950 else
1951 vim_free(p);
1952 }
1953 }
1954
Bram Moolenaarf3aea592018-11-11 22:18:21 +01001955 // Add trailing empty lines.
1956 for (pos.row = term->tl_scrollback.ga_len;
1957 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
1958 ++pos.row)
1959 {
1960 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
1961 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1962 }
1963
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001964 term->tl_dirty_snapshot = FALSE;
1965#ifdef FEAT_TIMERS
1966 term->tl_timer_set = FALSE;
1967#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001968}
1969
1970/*
Bram Moolenaare52e0c82020-02-28 22:20:10 +01001971 * Loop over all windows in the current tab, and also curwin, which is not
1972 * encountered when using a terminal in a popup window.
1973 * Return TRUE if "*wp" was set to the next window.
1974 */
1975 static int
1976for_all_windows_and_curwin(win_T **wp, int *did_curwin)
1977{
1978 if (*wp == NULL)
1979 *wp = firstwin;
1980 else if ((*wp)->w_next != NULL)
1981 *wp = (*wp)->w_next;
1982 else if (!*did_curwin)
1983 *wp = curwin;
1984 else
1985 return FALSE;
1986 if (*wp == curwin)
1987 *did_curwin = TRUE;
1988 return TRUE;
1989}
1990
1991/*
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001992 * If needed, add the current lines of the terminal to scrollback and to the
1993 * buffer. Called after the job has ended and when switching to
1994 * Terminal-Normal mode.
1995 * When "redraw" is TRUE redraw the windows that show the terminal.
1996 */
1997 static void
1998may_move_terminal_to_buffer(term_T *term, int redraw)
1999{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002000 if (term->tl_vterm == NULL)
2001 return;
2002
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002003 // Update the snapshot only if something changes or the buffer does not
2004 // have all the lines.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002005 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
2006 <= term->tl_scrollback_scrolled)
2007 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002008
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002009 // Obtain the current background color.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002010 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2011 &term->tl_default_color.fg, &term->tl_default_color.bg);
2012
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002013 if (redraw)
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002014 {
2015 win_T *wp = NULL;
2016 int did_curwin = FALSE;
2017
2018 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002019 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002020 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002021 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002022 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
2023 wp->w_cursor.col = 0;
2024 wp->w_valid = 0;
2025 if (wp->w_cursor.lnum >= wp->w_height)
2026 {
2027 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002028
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002029 if (wp->w_topline < min_topline)
2030 wp->w_topline = min_topline;
2031 }
Bram Moolenaara4d158b2022-08-14 14:17:45 +01002032 redraw_win_later(wp, UPD_NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002033 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002034 }
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002035 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002036}
2037
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002038#if defined(FEAT_TIMERS) || defined(PROTO)
2039/*
2040 * Check if any terminal timer expired. If so, copy text from the terminal to
2041 * the buffer.
2042 * Return the time until the next timer will expire.
2043 */
2044 int
2045term_check_timers(int next_due_arg, proftime_T *now)
2046{
2047 term_T *term;
2048 int next_due = next_due_arg;
2049
Bram Moolenaaraeea7212020-04-02 18:50:46 +02002050 FOR_ALL_TERMS(term)
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002051 {
2052 if (term->tl_timer_set && !term->tl_normal_mode)
2053 {
2054 long this_due = proftime_time_left(&term->tl_timer_due, now);
2055
2056 if (this_due <= 1)
2057 {
2058 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002059 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002060 }
2061 else if (next_due == -1 || next_due > this_due)
2062 next_due = this_due;
2063 }
2064 }
2065
2066 return next_due;
2067}
2068#endif
2069
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002070/*
2071 * When "normal_mode" is TRUE set the terminal to Terminal-Normal mode,
2072 * otherwise end it.
2073 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002074 static void
2075set_terminal_mode(term_T *term, int normal_mode)
2076{
2077 term->tl_normal_mode = normal_mode;
LemonBoy2bf52dd2022-04-09 18:17:34 +01002078 may_trigger_modechanged();
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002079 if (!normal_mode)
2080 handle_postponed_scrollback(term);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002081 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002082 if (term->tl_buffer == curbuf)
2083 maketitle();
2084}
2085
2086/*
Bram Moolenaare2978022020-04-26 14:47:44 +02002087 * Called after the job is finished and Terminal mode is not active:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002088 * Move the vterm contents into the scrollback buffer and free the vterm.
2089 */
2090 static void
2091cleanup_vterm(term_T *term)
2092{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002093 set_terminal_mode(term, FALSE);
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002094 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002095 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002096 term_free_vterm(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002097}
2098
2099/*
2100 * Switch from Terminal-Job mode to Terminal-Normal mode.
2101 * Suspends updating the terminal window.
2102 */
2103 static void
2104term_enter_normal_mode(void)
2105{
2106 term_T *term = curbuf->b_term;
2107
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002108 set_terminal_mode(term, TRUE);
2109
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002110 // Append the current terminal contents to the buffer.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002111 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002112
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002113 // Move the window cursor to the position of the cursor in the
2114 // terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002115 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
2116 + term->tl_cursor_pos.row + 1;
2117 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02002118 if (coladvance(term->tl_cursor_pos.col) == FAIL)
2119 coladvance(MAXCOL);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002120 curwin->w_set_curswant = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002121
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002122 // Display the same lines as in the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002123 curwin->w_topline = term->tl_scrollback_scrolled + 1;
2124}
2125
2126/*
2127 * Returns TRUE if the current window contains a terminal and we are in
2128 * Terminal-Normal mode.
2129 */
2130 int
2131term_in_normal_mode(void)
2132{
2133 term_T *term = curbuf->b_term;
2134
2135 return term != NULL && term->tl_normal_mode;
2136}
2137
2138/*
2139 * Switch from Terminal-Normal mode to Terminal-Job mode.
2140 * Restores updating the terminal window.
2141 */
2142 void
2143term_enter_job_mode()
2144{
2145 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002146
2147 set_terminal_mode(term, FALSE);
2148
2149 if (term->tl_channel_closed)
2150 cleanup_vterm(term);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01002151 redraw_buf_and_status_later(curbuf, UPD_NOT_VALID);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002152#ifdef FEAT_PROP_POPUP
2153 if (WIN_IS_POPUP(curwin))
Bram Moolenaara4d158b2022-08-14 14:17:45 +01002154 redraw_later(UPD_NOT_VALID);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002155#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002156}
2157
2158/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002159 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002160 * Note: while waiting a terminal may be closed and freed if the channel is
Bram Moolenaar829c8e82021-12-14 08:41:38 +00002161 * closed and ++close was used. This may even happen before we get here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002162 */
2163 static int
2164term_vgetc()
2165{
2166 int c;
2167 int save_State = State;
Bram Moolenaar829c8e82021-12-14 08:41:38 +00002168 int modify_other_keys = curbuf->b_term->tl_vterm == NULL ? FALSE
2169 : vterm_is_modify_other_keys(curbuf->b_term->tl_vterm);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002170
Bram Moolenaar24959102022-05-07 20:01:16 +01002171 State = MODE_TERMINAL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002172 got_int = FALSE;
Bram Moolenaar4f974752019-02-17 17:44:42 +01002173#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002174 ctrl_break_was_pressed = FALSE;
2175#endif
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002176 if (modify_other_keys)
2177 ++no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002178 c = vgetc();
2179 got_int = FALSE;
2180 State = save_State;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002181 if (modify_other_keys)
2182 --no_reduce_keys;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002183 return c;
2184}
2185
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002186static int mouse_was_outside = FALSE;
2187
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002188/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002189 * Send key "c" with modifiers "modmask" to terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002190 * Return FAIL when the key needs to be handled in Normal mode.
2191 * Return OK when the key was dropped or sent to the terminal.
2192 */
2193 int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002194send_keys_to_term(term_T *term, int c, int modmask, int typed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002195{
2196 char msg[KEY_BUF_LEN];
2197 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002198 int dragging_outside = FALSE;
2199
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002200 // Catch keys that need to be handled as in Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002201 switch (c)
2202 {
2203 case NUL:
2204 case K_ZERO:
2205 if (typed)
2206 stuffcharReadbuff(c);
2207 return FAIL;
2208
Bram Moolenaar231a2db2018-05-06 13:53:50 +02002209 case K_TABLINE:
2210 stuffcharReadbuff(c);
2211 return FAIL;
2212
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002213 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002214 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002215 return FAIL;
2216
2217 case K_LEFTDRAG:
2218 case K_MIDDLEDRAG:
2219 case K_RIGHTDRAG:
2220 case K_X1DRAG:
2221 case K_X2DRAG:
2222 dragging_outside = mouse_was_outside;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002223 // FALLTHROUGH
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002224 case K_LEFTMOUSE:
2225 case K_LEFTMOUSE_NM:
2226 case K_LEFTRELEASE:
2227 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01002228 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002229 case K_MIDDLEMOUSE:
2230 case K_MIDDLERELEASE:
2231 case K_RIGHTMOUSE:
2232 case K_RIGHTRELEASE:
2233 case K_X1MOUSE:
2234 case K_X1RELEASE:
2235 case K_X2MOUSE:
2236 case K_X2RELEASE:
2237
2238 case K_MOUSEUP:
2239 case K_MOUSEDOWN:
2240 case K_MOUSELEFT:
2241 case K_MOUSERIGHT:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002242 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002243 int row = mouse_row;
2244 int col = mouse_col;
2245
2246#ifdef FEAT_PROP_POPUP
2247 if (popup_is_popup(curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002248 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002249 row -= popup_top_extra(curwin);
2250 col -= popup_left_extra(curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002251 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002252#endif
2253 if (row < W_WINROW(curwin)
2254 || row >= (W_WINROW(curwin) + curwin->w_height)
2255 || col < curwin->w_wincol
2256 || col >= W_ENDCOL(curwin)
2257 || dragging_outside)
2258 {
2259 // click or scroll outside the current window or on status
2260 // line or vertical separator
2261 if (typed)
2262 {
2263 stuffcharReadbuff(c);
2264 mouse_was_outside = TRUE;
2265 }
2266 return FAIL;
2267 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002268 }
Bram Moolenaar957cf672020-11-12 14:21:06 +01002269 break;
2270
2271 case K_COMMAND:
Bram Moolenaare32c3c42022-01-15 18:26:04 +00002272 case K_SCRIPT_COMMAND:
2273 return do_cmdkey_command(c, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002274 }
2275 if (typed)
2276 mouse_was_outside = FALSE;
2277
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002278 // Convert the typed key to a sequence of bytes for the job.
2279 len = term_convert_key(term, c, modmask, msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002280 if (len > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002281 // TODO: if FAIL is returned, stop?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002282 channel_send(term->tl_job->jv_channel, get_tty_part(term),
2283 (char_u *)msg, (int)len, NULL);
2284
2285 return OK;
2286}
2287
2288 static void
Bram Moolenaarebec3e22020-11-28 20:22:06 +01002289position_cursor(win_T *wp, VTermPos *pos)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002290{
2291 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
2292 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002293#ifdef FEAT_PROP_POPUP
Bram Moolenaarebec3e22020-11-28 20:22:06 +01002294 if (popup_is_popup(wp))
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002295 {
Bram Moolenaarf5452692020-11-28 21:56:06 +01002296 wp->w_wrow += popup_top_extra(wp);
2297 wp->w_wcol += popup_left_extra(wp);
Bram Moolenaar6a076442020-11-15 20:32:58 +01002298 wp->w_flags |= WFLAG_WCOL_OFF_ADDED | WFLAG_WROW_OFF_ADDED;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002299 }
Bram Moolenaar6a076442020-11-15 20:32:58 +01002300 else
2301 wp->w_flags &= ~(WFLAG_WCOL_OFF_ADDED | WFLAG_WROW_OFF_ADDED);
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002302#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002303 wp->w_valid |= (VALID_WCOL|VALID_WROW);
2304}
2305
2306/*
2307 * Handle CTRL-W "": send register contents to the job.
2308 */
2309 static void
2310term_paste_register(int prev_c UNUSED)
2311{
2312 int c;
2313 list_T *l;
2314 listitem_T *item;
2315 long reglen = 0;
2316 int type;
2317
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002318 if (add_to_showcmd(prev_c))
2319 if (add_to_showcmd('"'))
2320 out_flush();
Martin Tournoijba43e762022-10-13 22:12:15 +01002321
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002322 c = term_vgetc();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002323 clear_showcmd();
Martin Tournoijba43e762022-10-13 22:12:15 +01002324
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002325 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002326 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002327 return;
2328
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002329 // CTRL-W "= prompt for expression to evaluate.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002330 if (c == '=' && get_expr_register() != '=')
2331 return;
2332 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002333 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002334 return;
2335
2336 l = (list_T *)get_reg_contents(c, GREG_LIST);
2337 if (l != NULL)
2338 {
2339 type = get_reg_type(c, &reglen);
Bram Moolenaaraeea7212020-04-02 18:50:46 +02002340 FOR_ALL_LIST_ITEMS(l, item)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002341 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01002342 char_u *s = tv_get_string(&item->li_tv);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002343#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002344 char_u *tmp = s;
2345
2346 if (!enc_utf8 && enc_codepage > 0)
2347 {
2348 WCHAR *ret = NULL;
2349 int length = 0;
2350
2351 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2352 (int)STRLEN(s), &ret, &length);
2353 if (ret != NULL)
2354 {
2355 WideCharToMultiByte_alloc(CP_UTF8, 0,
2356 ret, length, (char **)&s, &length, 0, 0);
2357 vim_free(ret);
2358 }
2359 }
2360#endif
2361 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2362 s, (int)STRLEN(s), NULL);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002363#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002364 if (tmp != s)
2365 vim_free(s);
2366#endif
2367
2368 if (item->li_next != NULL || type == MLINE)
2369 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2370 (char_u *)"\r", 1, NULL);
2371 }
2372 list_free(l);
2373 }
2374}
2375
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002376/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002377 * Return TRUE when waiting for a character in the terminal, the cursor of the
2378 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002379 */
2380 int
2381terminal_is_active()
2382{
2383 return in_terminal_loop != NULL;
2384}
2385
Bram Moolenaar83d47902020-03-26 20:34:00 +01002386/*
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002387 * Return the highight group ID for the terminal and the window.
Bram Moolenaar83d47902020-03-26 20:34:00 +01002388 */
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002389 static int
2390term_get_highlight_id(term_T *term, win_T *wp)
Bram Moolenaar83d47902020-03-26 20:34:00 +01002391{
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002392 char_u *name;
2393
2394 if (wp != NULL && *wp->w_p_wcr != NUL)
2395 name = wp->w_p_wcr;
2396 else if (term->tl_highlight_name != NULL)
2397 name = term->tl_highlight_name;
2398 else
2399 name = (char_u*)"Terminal";
2400
2401 return syn_name2id(name);
Bram Moolenaar83d47902020-03-26 20:34:00 +01002402}
2403
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002404#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002405 cursorentry_T *
2406term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2407{
2408 term_T *term = in_terminal_loop;
2409 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002410 int id;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002411 guicolor_T term_fg = INVALCOLOR;
2412 guicolor_T term_bg = INVALCOLOR;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002413
Bram Moolenaara80faa82020-04-12 19:37:17 +02002414 CLEAR_FIELD(entry);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002415 entry.shape = entry.mshape =
2416 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2417 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2418 SHAPE_BLOCK;
2419 entry.percentage = 20;
2420 if (term->tl_cursor_blink)
2421 {
2422 entry.blinkwait = 700;
2423 entry.blinkon = 400;
2424 entry.blinkoff = 250;
2425 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002426
Bram Moolenaar83d47902020-03-26 20:34:00 +01002427 // The highlight group overrules the defaults.
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002428 id = term_get_highlight_id(term, curwin);
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002429 if (id != 0)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002430 syn_id2colors(id, &term_fg, &term_bg);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002431 if (term_bg != INVALCOLOR)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002432 *fg = term_bg;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002433 else
2434 *fg = gui.back_pixel;
2435
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002436 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002437 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002438 if (term_fg != INVALCOLOR)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002439 *bg = term_fg;
2440 else
2441 *bg = gui.norm_pixel;
2442 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002443 else
2444 *bg = color_name2handle(term->tl_cursor_color);
2445 entry.name = "n";
2446 entry.used_for = SHAPE_CURSOR;
2447
2448 return &entry;
2449}
2450#endif
2451
Bram Moolenaard317b382018-02-08 22:33:31 +01002452 static void
2453may_output_cursor_props(void)
2454{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002455 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
Bram Moolenaard317b382018-02-08 22:33:31 +01002456 || last_set_cursor_shape != desired_cursor_shape
2457 || last_set_cursor_blink != desired_cursor_blink)
2458 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002459 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002460 last_set_cursor_shape = desired_cursor_shape;
2461 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002462 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002463 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002464 // this will restore the initial cursor style, if possible
Bram Moolenaard317b382018-02-08 22:33:31 +01002465 ui_cursor_shape_forced(TRUE);
2466 else
2467 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2468 }
2469}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002470
Bram Moolenaard317b382018-02-08 22:33:31 +01002471/*
2472 * Set the cursor color and shape, if not last set to these.
2473 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002474 static void
2475may_set_cursor_props(term_T *term)
2476{
2477#ifdef FEAT_GUI
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002478 // For the GUI the cursor properties are obtained with
2479 // term_get_cursor_shape().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002480 if (gui.in_use)
2481 return;
2482#endif
2483 if (in_terminal_loop == term)
2484 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002485 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002486 desired_cursor_shape = term->tl_cursor_shape;
2487 desired_cursor_blink = term->tl_cursor_blink;
2488 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002489 }
2490}
2491
Bram Moolenaard317b382018-02-08 22:33:31 +01002492/*
2493 * Reset the desired cursor properties and restore them when needed.
2494 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002495 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002496prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002497{
2498#ifdef FEAT_GUI
2499 if (gui.in_use)
2500 return;
2501#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002502 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002503 desired_cursor_shape = -1;
2504 desired_cursor_blink = -1;
2505 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002506}
2507
2508/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002509 * Returns TRUE if the current window contains a terminal and we are sending
2510 * keys to the job.
2511 * If "check_job_status" is TRUE update the job status.
2512 */
2513 static int
2514term_use_loop_check(int check_job_status)
2515{
2516 term_T *term = curbuf->b_term;
2517
2518 return term != NULL
2519 && !term->tl_normal_mode
2520 && term->tl_vterm != NULL
2521 && term_job_running_check(term, check_job_status);
2522}
2523
2524/*
2525 * Returns TRUE if the current window contains a terminal and we are sending
2526 * keys to the job.
2527 */
2528 int
2529term_use_loop(void)
2530{
2531 return term_use_loop_check(FALSE);
2532}
2533
2534/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002535 * Called when entering a window with the mouse. If this is a terminal window
2536 * we may want to change state.
2537 */
2538 void
2539term_win_entered()
2540{
2541 term_T *term = curbuf->b_term;
2542
2543 if (term != NULL)
2544 {
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002545 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002546 {
2547 reset_VIsual_and_resel();
Bram Moolenaar24959102022-05-07 20:01:16 +01002548 if (State & MODE_INSERT)
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002549 stop_insert_mode = TRUE;
2550 }
2551 mouse_was_outside = FALSE;
2552 enter_mouse_col = mouse_col;
2553 enter_mouse_row = mouse_row;
2554 }
2555}
2556
Bram Moolenaara48d4e42021-12-08 22:13:38 +00002557 void
2558term_focus_change(int in_focus)
2559{
2560 term_T *term = curbuf->b_term;
2561
2562 if (term != NULL && term->tl_vterm != NULL)
2563 {
2564 VTermState *state = vterm_obtain_state(term->tl_vterm);
2565
2566 if (in_focus)
2567 vterm_state_focus_in(state);
2568 else
2569 vterm_state_focus_out(state);
2570 term_forward_output(term);
2571 }
2572}
2573
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002574/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002575 * vgetc() may not include CTRL in the key when modify_other_keys is set.
2576 * Return the Ctrl-key value in that case.
2577 */
2578 static int
2579raw_c_to_ctrl(int c)
2580{
2581 if ((mod_mask & MOD_MASK_CTRL)
2582 && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')))
2583 return c & 0x1f;
2584 return c;
2585}
2586
2587/*
2588 * When modify_other_keys is set then do the reverse of raw_c_to_ctrl().
2589 * May set "mod_mask".
2590 */
2591 static int
2592ctrl_to_raw_c(int c)
2593{
2594 if (c < 0x20 && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm))
2595 {
2596 mod_mask |= MOD_MASK_CTRL;
2597 return c + '@';
2598 }
2599 return c;
2600}
2601
2602/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002603 * Wait for input and send it to the job.
2604 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2605 * when there is no more typahead.
2606 * Return when the start of a CTRL-W command is typed or anything else that
2607 * should be handled as a Normal mode command.
2608 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2609 * the terminal was closed.
2610 */
2611 int
2612terminal_loop(int blocking)
2613{
2614 int c;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002615 int raw_c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002616 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002617 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002618#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002619 int tty_fd = curbuf->b_term->tl_job->jv_channel
2620 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002621#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002622 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002623
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002624 // Remember the terminal we are sending keys to. However, the terminal
2625 // might be closed while waiting for a character, e.g. typing "exit" in a
2626 // shell and ++close was used. Therefore use curbuf->b_term instead of a
2627 // stored reference.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002628 in_terminal_loop = curbuf->b_term;
2629
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002630 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002631 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002632 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002633 if (termwinkey == Ctrl_W)
2634 termwinkey = 0;
2635 }
Bram Moolenaarebec3e22020-11-28 20:22:06 +01002636 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002637 may_set_cursor_props(curbuf->b_term);
2638
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002639 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002640 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002641#ifdef FEAT_GUI
Bram Moolenaar02764712020-11-14 20:21:55 +01002642 if (curbuf->b_term != NULL && !curbuf->b_term->tl_system)
Bram Moolenaar13568252018-03-16 20:46:58 +01002643#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002644 // TODO: skip screen update when handling a sequence of keys.
2645 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002646 while (must_redraw != 0)
2647 if (update_screen(0) == FAIL)
2648 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002649 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002650 // job finished while redrawing
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002651 break;
2652
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002653 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002654 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002655
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002656 raw_c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002657 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002658 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002659 // Job finished while waiting for a character. Push back the
2660 // received character.
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002661 if (raw_c != K_IGNORE)
2662 vungetc(raw_c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002663 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002664 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002665 if (raw_c == K_IGNORE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002666 continue;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002667 c = raw_c_to_ctrl(raw_c);
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002668
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002669#ifdef UNIX
2670 /*
2671 * The shell or another program may change the tty settings. Getting
2672 * them for every typed character is a bit of overhead, but it's needed
2673 * for the first character typed, e.g. when Vim starts in a shell.
2674 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002675 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002676 {
2677 ttyinfo_T info;
2678
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002679 // Get the current backspace character of the pty.
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002680 if (get_tty_info(tty_fd, &info) == OK)
2681 term_backspace_char = info.backspace;
2682 }
2683#endif
2684
Bram Moolenaar4f974752019-02-17 17:44:42 +01002685#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002686 // On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2687 // Use CTRL-BREAK to kill the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002688 if (ctrl_break_was_pressed)
2689 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2690#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002691 // Was either CTRL-W (termwinkey) or CTRL-\ pressed?
2692 // Not in a system terminal.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002693 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002694#ifdef FEAT_GUI
2695 && !curbuf->b_term->tl_system
2696#endif
2697 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002698 {
2699 int prev_c = c;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002700 int prev_raw_c = raw_c;
2701 int prev_mod_mask = mod_mask;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002702
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002703 if (add_to_showcmd(c))
2704 out_flush();
Martin Tournoijba43e762022-10-13 22:12:15 +01002705
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002706 raw_c = term_vgetc();
2707 c = raw_c_to_ctrl(raw_c);
2708
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002709 clear_showcmd();
Martin Tournoijba43e762022-10-13 22:12:15 +01002710
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002711 if (!term_use_loop_check(TRUE)
2712 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002713 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002714 break;
2715
2716 if (prev_c == Ctrl_BSL)
2717 {
2718 if (c == Ctrl_N)
2719 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002720 // CTRL-\ CTRL-N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002721 term_enter_normal_mode();
2722 ret = FAIL;
2723 goto theend;
2724 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002725 // Send both keys to the terminal, first one here, second one
2726 // below.
2727 send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask,
2728 TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002729 }
2730 else if (c == Ctrl_C)
2731 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002732 // "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002733 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2734 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002735 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002736 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002737 // "CTRL-W .": send CTRL-W to the job
2738 // "'termwinkey' .": send 'termwinkey' to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002739 raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002740 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002741 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002742 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002743 // "CTRL-W CTRL-\": send CTRL-\ to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002744 raw_c = ctrl_to_raw_c(Ctrl_BSL);
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002745 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002746 else if (c == 'N')
2747 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002748 // CTRL-W N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002749 term_enter_normal_mode();
2750 ret = FAIL;
2751 goto theend;
2752 }
2753 else if (c == '"')
2754 {
2755 term_paste_register(prev_c);
2756 continue;
2757 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002758 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002759 {
Bram Moolenaarf43e7ac2020-09-29 21:23:25 +02002760 // space for CTRL-W, modifier, multi-byte char and NUL
2761 char_u buf[1 + 3 + MB_MAXBYTES + 1];
Bram Moolenaara4b26992019-08-15 20:58:54 +02002762
2763 // Put the command into the typeahead buffer, when using the
2764 // stuff buffer KeyStuffed is set and 'langmap' won't be used.
2765 buf[0] = Ctrl_W;
Bram Moolenaarf43e7ac2020-09-29 21:23:25 +02002766 buf[special_to_buf(c, mod_mask, FALSE, buf + 1) + 1] = NUL;
Bram Moolenaara4b26992019-08-15 20:58:54 +02002767 ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002768 ret = OK;
2769 goto theend;
2770 }
2771 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01002772# ifdef MSWIN
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002773 if (!enc_utf8 && has_mbyte && raw_c >= 0x80)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002774 {
2775 WCHAR wc;
2776 char_u mb[3];
2777
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002778 mb[0] = (unsigned)raw_c >> 8;
2779 mb[1] = raw_c;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002780 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002781 raw_c = wc;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002782 }
2783# endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002784 if (send_keys_to_term(curbuf->b_term, raw_c, mod_mask, TRUE) != OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002785 {
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002786 if (raw_c == K_MOUSEMOVE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002787 // We are sure to come back here, don't reset the cursor color
2788 // and shape to avoid flickering.
Bram Moolenaard317b382018-02-08 22:33:31 +01002789 restore_cursor = FALSE;
2790
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002791 ret = OK;
2792 goto theend;
2793 }
2794 }
2795 ret = FAIL;
2796
2797theend:
2798 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002799 if (restore_cursor)
2800 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002801
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002802 // Move a snapshot of the screen contents to the buffer, so that completion
2803 // works in other buffers.
Bram Moolenaar620020e2018-05-13 19:06:12 +02002804 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2805 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002806
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002807 return ret;
2808}
2809
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002810 static void
2811may_toggle_cursor(term_T *term)
2812{
2813 if (in_terminal_loop == term)
2814 {
2815 if (term->tl_cursor_visible)
2816 cursor_on();
2817 else
2818 cursor_off();
2819 }
2820}
2821
2822/*
2823 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002824 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002825 */
2826 static int
2827color2index(VTermColor *color, int fg, int *boldp)
2828{
2829 int red = color->red;
2830 int blue = color->blue;
2831 int green = color->green;
2832
LemonBoyb2b3acb2022-05-20 10:10:34 +01002833 *boldp = FALSE;
2834
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002835 if (VTERM_COLOR_IS_INVALID(color))
Bram Moolenaare5886cc2020-05-21 20:10:04 +02002836 return 0;
LemonBoyb2b3acb2022-05-20 10:10:34 +01002837
Bram Moolenaare5886cc2020-05-21 20:10:04 +02002838 if (VTERM_COLOR_IS_INDEXED(color))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002839 {
LemonBoyb2b3acb2022-05-20 10:10:34 +01002840 // Use the color as-is if possible, give up otherwise.
2841 if (color->index < t_colors)
2842 return color->index + 1;
2843 // 8-color terminals can actually display twice as many colors by
2844 // setting the high-intensity/bold bit.
2845 else if (t_colors == 8 && fg && color->index < 16)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002846 {
LemonBoyb2b3acb2022-05-20 10:10:34 +01002847 *boldp = TRUE;
2848 return (color->index & 7) + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002849 }
LemonBoyb2b3acb2022-05-20 10:10:34 +01002850 return 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002851 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002852
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002853 if (t_colors >= 256)
2854 {
2855 if (red == blue && red == green)
2856 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002857 // 24-color greyscale plus white and black
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002858 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002859 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2860 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2861 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002862 int i;
2863
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002864 if (red < 5)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002865 return 17; // 00/00/00
2866 if (red > 245) // ff/ff/ff
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002867 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002868 for (i = 0; i < 23; ++i)
2869 if (red < cutoff[i])
2870 return i + 233;
2871 return 256;
2872 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002873 {
2874 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2875 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002876
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002877 // 216-color cube
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002878 for (ri = 0; ri < 5; ++ri)
2879 if (red < cutoff[ri])
2880 break;
2881 for (gi = 0; gi < 5; ++gi)
2882 if (green < cutoff[gi])
2883 break;
2884 for (bi = 0; bi < 5; ++bi)
2885 if (blue < cutoff[bi])
2886 break;
2887 return 17 + ri * 36 + gi * 6 + bi;
2888 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002889 }
2890 return 0;
2891}
2892
2893/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002894 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002895 */
2896 static int
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002897vtermAttr2hl(VTermScreenCellAttrs *cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002898{
2899 int attr = 0;
2900
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002901 if (cellattrs->bold)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002902 attr |= HL_BOLD;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002903 if (cellattrs->underline)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002904 attr |= HL_UNDERLINE;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002905 if (cellattrs->italic)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002906 attr |= HL_ITALIC;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002907 if (cellattrs->strike)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002908 attr |= HL_STRIKETHROUGH;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002909 if (cellattrs->reverse)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002910 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002911 return attr;
2912}
2913
2914/*
2915 * Store Vterm attributes in "cell" from highlight flags.
2916 */
2917 static void
2918hl2vtermAttr(int attr, cellattr_T *cell)
2919{
Bram Moolenaara80faa82020-04-12 19:37:17 +02002920 CLEAR_FIELD(cell->attrs);
Bram Moolenaard96ff162018-02-18 22:13:29 +01002921 if (attr & HL_BOLD)
2922 cell->attrs.bold = 1;
2923 if (attr & HL_UNDERLINE)
2924 cell->attrs.underline = 1;
2925 if (attr & HL_ITALIC)
2926 cell->attrs.italic = 1;
2927 if (attr & HL_STRIKETHROUGH)
2928 cell->attrs.strike = 1;
2929 if (attr & HL_INVERSE)
2930 cell->attrs.reverse = 1;
2931}
2932
2933/*
2934 * Convert the attributes of a vterm cell into an attribute index.
2935 */
2936 static int
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002937cell2attr(
Bram Moolenaar83d47902020-03-26 20:34:00 +01002938 term_T *term,
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002939 win_T *wp,
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002940 VTermScreenCellAttrs *cellattrs,
2941 VTermColor *cellfg,
2942 VTermColor *cellbg)
Bram Moolenaard96ff162018-02-18 22:13:29 +01002943{
2944 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002945 VTermColor *fg = cellfg;
2946 VTermColor *bg = cellbg;
2947 int is_default_fg = VTERM_COLOR_IS_DEFAULT_FG(fg);
2948 int is_default_bg = VTERM_COLOR_IS_DEFAULT_BG(bg);
2949
2950 if (is_default_fg || is_default_bg)
2951 {
2952 if (wp != NULL && *wp->w_p_wcr != NUL)
2953 {
2954 if (is_default_fg)
2955 fg = &wp->w_term_wincolor.fg;
2956 if (is_default_bg)
2957 bg = &wp->w_term_wincolor.bg;
2958 }
2959 else
2960 {
2961 if (is_default_fg)
2962 fg = &term->tl_default_color.fg;
2963 if (is_default_bg)
2964 bg = &term->tl_default_color.bg;
2965 }
2966 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002967
2968#ifdef FEAT_GUI
2969 if (gui.in_use)
2970 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002971 guicolor_T guifg = gui_mch_get_rgb_color(fg->red, fg->green, fg->blue);
2972 guicolor_T guibg = gui_mch_get_rgb_color(bg->red, bg->green, bg->blue);
2973 return get_gui_attr_idx(attr, guifg, guibg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002974 }
2975 else
2976#endif
2977#ifdef FEAT_TERMGUICOLORS
2978 if (p_tgc)
2979 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002980 guicolor_T tgcfg = VTERM_COLOR_IS_INVALID(fg)
2981 ? INVALCOLOR
2982 : gui_get_rgb_color_cmn(fg->red, fg->green, fg->blue);
2983 guicolor_T tgcbg = VTERM_COLOR_IS_INVALID(bg)
2984 ? INVALCOLOR
2985 : gui_get_rgb_color_cmn(bg->red, bg->green, bg->blue);
2986 return get_tgc_attr_idx(attr, tgcfg, tgcbg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002987 }
2988 else
2989#endif
2990 {
2991 int bold = MAYBE;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002992 int ctermfg = color2index(fg, TRUE, &bold);
2993 int ctermbg = color2index(bg, FALSE, &bold);
Bram Moolenaar76bb7192017-11-30 22:07:07 +01002994
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002995 // with 8 colors set the bold attribute to get a bright foreground
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002996 if (bold == TRUE)
2997 attr |= HL_BOLD;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002998
2999 return get_cterm_attr_idx(attr, ctermfg, ctermbg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003000 }
3001 return 0;
3002}
3003
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003004 static void
3005set_dirty_snapshot(term_T *term)
3006{
3007 term->tl_dirty_snapshot = TRUE;
3008#ifdef FEAT_TIMERS
3009 if (!term->tl_normal_mode)
3010 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003011 // Update the snapshot after 100 msec of not getting updates.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003012 profile_setlimit(100L, &term->tl_timer_due);
3013 term->tl_timer_set = TRUE;
3014 }
3015#endif
3016}
3017
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003018 static int
3019handle_damage(VTermRect rect, void *user)
3020{
3021 term_T *term = (term_T *)user;
3022
3023 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
3024 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003025 set_dirty_snapshot(term);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003026 redraw_buf_later(term->tl_buffer, UPD_SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003027 return 1;
3028}
3029
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003030 static void
3031term_scroll_up(term_T *term, int start_row, int count)
3032{
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003033 win_T *wp = NULL;
3034 int did_curwin = FALSE;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003035 VTermColor fg, bg;
3036 VTermScreenCellAttrs attr;
3037 int clear_attr;
3038
Bram Moolenaara80faa82020-04-12 19:37:17 +02003039 CLEAR_FIELD(attr);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003040
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003041 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003042 {
3043 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003044 {
3045 // Set the color to clear lines with.
3046 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
3047 &fg, &bg);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003048 clear_attr = cell2attr(term, wp, &attr, &fg, &bg);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003049 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003050 }
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003051 }
3052}
3053
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003054 static int
3055handle_moverect(VTermRect dest, VTermRect src, void *user)
3056{
3057 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003058 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003059
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003060 // Scrolling up is done much more efficiently by deleting lines instead of
3061 // redrawing the text. But avoid doing this multiple times, postpone until
3062 // the redraw happens.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003063 if (dest.start_col == src.start_col
3064 && dest.end_col == src.end_col
3065 && dest.start_row < src.start_row)
3066 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003067 if (dest.start_row == 0)
3068 term->tl_postponed_scroll += count;
3069 else
3070 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003071 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003072
3073 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
3074 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003075 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003076
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003077 // Note sure if the scrolling will work correctly, let's do a complete
3078 // redraw later.
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003079 redraw_buf_later(term->tl_buffer, UPD_NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003080 return 1;
3081}
3082
3083 static int
3084handle_movecursor(
3085 VTermPos pos,
3086 VTermPos oldpos UNUSED,
3087 int visible,
3088 void *user)
3089{
3090 term_T *term = (term_T *)user;
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003091 win_T *wp = NULL;
3092 int did_curwin = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003093
3094 term->tl_cursor_pos = pos;
3095 term->tl_cursor_visible = visible;
3096
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003097 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003098 {
3099 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaarebec3e22020-11-28 20:22:06 +01003100 position_cursor(wp, &pos);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003101 }
3102 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003103 update_cursor(term, term->tl_cursor_visible);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003104
3105 return 1;
3106}
3107
3108 static int
3109handle_settermprop(
3110 VTermProp prop,
3111 VTermValue *value,
3112 void *user)
3113{
3114 term_T *term = (term_T *)user;
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003115 char_u *strval = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003116
3117 switch (prop)
3118 {
3119 case VTERM_PROP_TITLE:
ichizokae1bd872022-01-20 14:57:29 +00003120 if (disable_vterm_title_for_testing)
3121 break;
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003122 strval = vim_strnsave((char_u *)value->string.str,
Bram Moolenaar71ccd032020-06-12 22:59:11 +02003123 value->string.len);
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003124 if (strval == NULL)
3125 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003126 vim_free(term->tl_title);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003127 // a blank title isn't useful, make it empty, so that "running" is
3128 // displayed
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003129 if (*skipwhite(strval) == NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003130 term->tl_title = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003131 // Same as blank
3132 else if (term->tl_arg0_cmd != NULL
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003133 && STRNCMP(term->tl_arg0_cmd, strval,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003134 (int)STRLEN(term->tl_arg0_cmd)) == 0)
3135 term->tl_title = NULL;
3136 // Empty corrupted data of winpty
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003137 else if (STRNCMP(" - ", strval, 4) == 0)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003138 term->tl_title = NULL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01003139#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003140 else if (!enc_utf8 && enc_codepage > 0)
3141 {
3142 WCHAR *ret = NULL;
3143 int length = 0;
3144
3145 MultiByteToWideChar_alloc(CP_UTF8, 0,
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003146 (char*)value->string.str,
3147 (int)value->string.len, &ret, &length);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003148 if (ret != NULL)
3149 {
3150 WideCharToMultiByte_alloc(enc_codepage, 0,
3151 ret, length, (char**)&term->tl_title,
3152 &length, 0, 0);
3153 vim_free(ret);
3154 }
3155 }
3156#endif
3157 else
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003158 {
Bram Moolenaar98f16712020-05-22 13:34:01 +02003159 term->tl_title = strval;
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003160 strval = NULL;
3161 }
Bram Moolenaard23a8232018-02-10 18:45:26 +01003162 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003163 if (term == curbuf->b_term)
LemonBoy327e6dd2022-06-04 19:57:59 +01003164 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003165 maketitle();
LemonBoy327e6dd2022-06-04 19:57:59 +01003166 curwin->w_redr_status = TRUE;
3167 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003168 break;
3169
3170 case VTERM_PROP_CURSORVISIBLE:
3171 term->tl_cursor_visible = value->boolean;
3172 may_toggle_cursor(term);
3173 out_flush();
3174 break;
3175
3176 case VTERM_PROP_CURSORBLINK:
3177 term->tl_cursor_blink = value->boolean;
3178 may_set_cursor_props(term);
3179 break;
3180
3181 case VTERM_PROP_CURSORSHAPE:
3182 term->tl_cursor_shape = value->number;
3183 may_set_cursor_props(term);
3184 break;
3185
3186 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003187 strval = vim_strnsave((char_u *)value->string.str,
Bram Moolenaar71ccd032020-06-12 22:59:11 +02003188 value->string.len);
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003189 if (strval == NULL)
3190 break;
3191 cursor_color_copy(&term->tl_cursor_color, strval);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003192 may_set_cursor_props(term);
3193 break;
3194
3195 case VTERM_PROP_ALTSCREEN:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003196 // TODO: do anything else?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003197 term->tl_using_altscreen = value->boolean;
3198 break;
3199
3200 default:
3201 break;
3202 }
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003203 vim_free(strval);
3204
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003205 // Always return 1, otherwise vterm doesn't store the value internally.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003206 return 1;
3207}
3208
3209/*
3210 * The job running in the terminal resized the terminal.
3211 */
3212 static int
3213handle_resize(int rows, int cols, void *user)
3214{
3215 term_T *term = (term_T *)user;
3216 win_T *wp;
3217
3218 term->tl_rows = rows;
3219 term->tl_cols = cols;
3220 if (term->tl_vterm_size_changed)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003221 // Size was set by vterm_set_size(), don't set the window size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003222 term->tl_vterm_size_changed = FALSE;
3223 else
3224 {
3225 FOR_ALL_WINDOWS(wp)
3226 {
3227 if (wp->w_buffer == term->tl_buffer)
3228 {
3229 win_setheight_win(rows, wp);
3230 win_setwidth_win(cols, wp);
3231 }
3232 }
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003233 redraw_buf_later(term->tl_buffer, UPD_NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003234 }
3235 return 1;
3236}
3237
3238/*
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003239 * If the number of lines that are stored goes over 'termscrollback' then
3240 * delete the first 10%.
3241 * "gap" points to tl_scrollback or tl_scrollback_postponed.
3242 * "update_buffer" is TRUE when the buffer should be updated.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003243 */
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003244 static void
3245limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003246{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003247 if (gap->ga_len >= term->tl_buffer->b_p_twsl)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003248 {
Bram Moolenaar6d150f72018-04-21 20:03:20 +02003249 int todo = term->tl_buffer->b_p_twsl / 10;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003250 int i;
3251
3252 curbuf = term->tl_buffer;
3253 for (i = 0; i < todo; ++i)
3254 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003255 vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells);
3256 if (update_buffer)
Bram Moolenaarca70c072020-05-30 20:30:46 +02003257 ml_delete(1);
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003258 }
3259 curbuf = curwin->w_buffer;
3260
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003261 gap->ga_len -= todo;
3262 mch_memmove(gap->ga_data,
3263 (sb_line_T *)gap->ga_data + todo,
3264 sizeof(sb_line_T) * gap->ga_len);
3265 if (update_buffer)
3266 term->tl_scrollback_scrolled -= todo;
3267 }
3268}
3269
3270/*
3271 * Handle a line that is pushed off the top of the screen.
3272 */
3273 static int
3274handle_pushline(int cols, const VTermScreenCell *cells, void *user)
3275{
3276 term_T *term = (term_T *)user;
3277 garray_T *gap;
3278 int update_buffer;
3279
3280 if (term->tl_normal_mode)
3281 {
3282 // In Terminal-Normal mode the user interacts with the buffer, thus we
3283 // must not change it. Postpone adding the scrollback lines.
3284 gap = &term->tl_scrollback_postponed;
3285 update_buffer = FALSE;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003286 }
3287 else
3288 {
3289 // First remove the lines that were appended before, the pushed line
3290 // goes above it.
3291 cleanup_scrollback(term);
3292 gap = &term->tl_scrollback;
3293 update_buffer = TRUE;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003294 }
3295
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003296 limit_scrollback(term, gap, update_buffer);
3297
3298 if (ga_grow(gap, 1) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003299 {
3300 cellattr_T *p = NULL;
3301 int len = 0;
3302 int i;
3303 int c;
3304 int col;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003305 int text_len;
3306 char_u *text;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003307 sb_line_T *line;
3308 garray_T ga;
3309 cellattr_T fill_attr = term->tl_default_color;
3310
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003311 // do not store empty cells at the end
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003312 for (i = 0; i < cols; ++i)
3313 if (cells[i].chars[0] != 0)
3314 len = i + 1;
3315 else
3316 cell2cellattr(&cells[i], &fill_attr);
3317
3318 ga_init2(&ga, 1, 100);
3319 if (len > 0)
Bram Moolenaarc799fe22019-05-28 23:08:19 +02003320 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003321 if (p != NULL)
3322 {
3323 for (col = 0; col < len; col += cells[col].width)
3324 {
3325 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
3326 {
3327 ga.ga_len = 0;
3328 break;
3329 }
3330 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
3331 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
3332 (char_u *)ga.ga_data + ga.ga_len);
3333 cell2cellattr(&cells[col], &p[col]);
3334 }
3335 }
3336 if (ga_grow(&ga, 1) == FAIL)
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003337 {
3338 if (update_buffer)
3339 text = (char_u *)"";
3340 else
3341 text = vim_strsave((char_u *)"");
3342 text_len = 0;
3343 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003344 else
3345 {
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003346 text = ga.ga_data;
3347 text_len = ga.ga_len;
3348 *(text + text_len) = NUL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003349 }
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003350 if (update_buffer)
3351 add_scrollback_line_to_buffer(term, text, text_len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003352
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003353 line = (sb_line_T *)gap->ga_data + gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003354 line->sb_cols = len;
3355 line->sb_cells = p;
3356 line->sb_fill_attr = fill_attr;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003357 if (update_buffer)
3358 {
3359 line->sb_text = NULL;
3360 ++term->tl_scrollback_scrolled;
3361 ga_clear(&ga); // free the text
3362 }
3363 else
3364 {
3365 line->sb_text = text;
3366 ga_init(&ga); // text is kept in tl_scrollback_postponed
3367 }
3368 ++gap->ga_len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003369 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003370 return 0; // ignored
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003371}
3372
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003373/*
3374 * Called when leaving Terminal-Normal mode: deal with any scrollback that was
3375 * received and stored in tl_scrollback_postponed.
3376 */
3377 static void
3378handle_postponed_scrollback(term_T *term)
3379{
3380 int i;
3381
Bram Moolenaar8376c3d2019-03-19 20:50:43 +01003382 if (term->tl_scrollback_postponed.ga_len == 0)
3383 return;
3384 ch_log(NULL, "Moving postponed scrollback to scrollback");
3385
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003386 // First remove the lines that were appended before, the pushed lines go
3387 // above it.
3388 cleanup_scrollback(term);
3389
3390 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
3391 {
3392 char_u *text;
3393 sb_line_T *pp_line;
3394 sb_line_T *line;
3395
3396 if (ga_grow(&term->tl_scrollback, 1) == FAIL)
3397 break;
3398 pp_line = (sb_line_T *)term->tl_scrollback_postponed.ga_data + i;
3399
3400 text = pp_line->sb_text;
3401 if (text == NULL)
3402 text = (char_u *)"";
3403 add_scrollback_line_to_buffer(term, text, (int)STRLEN(text));
3404 vim_free(pp_line->sb_text);
3405
3406 line = (sb_line_T *)term->tl_scrollback.ga_data
3407 + term->tl_scrollback.ga_len;
3408 line->sb_cols = pp_line->sb_cols;
3409 line->sb_cells = pp_line->sb_cells;
3410 line->sb_fill_attr = pp_line->sb_fill_attr;
3411 line->sb_text = NULL;
3412 ++term->tl_scrollback_scrolled;
3413 ++term->tl_scrollback.ga_len;
3414 }
3415
3416 ga_clear(&term->tl_scrollback_postponed);
3417 limit_scrollback(term, &term->tl_scrollback, TRUE);
3418}
3419
LemonBoy77771d32022-04-13 11:47:25 +01003420/*
3421 * Called when the terminal wants to ring the system bell.
3422 */
3423 static int
3424handle_bell(void *user UNUSED)
3425{
Bram Moolenaaraae97622022-04-13 14:28:07 +01003426 vim_beep(BO_TERM);
LemonBoy77771d32022-04-13 11:47:25 +01003427 return 0;
3428}
3429
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003430static VTermScreenCallbacks screen_callbacks = {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003431 handle_damage, // damage
3432 handle_moverect, // moverect
3433 handle_movecursor, // movecursor
3434 handle_settermprop, // settermprop
LemonBoy77771d32022-04-13 11:47:25 +01003435 handle_bell, // bell
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003436 handle_resize, // resize
3437 handle_pushline, // sb_pushline
3438 NULL // sb_popline
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003439};
3440
3441/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003442 * Do the work after the channel of a terminal was closed.
3443 * Must be called only when updating_screen is FALSE.
3444 * Returns TRUE when a buffer was closed (list of terminals may have changed).
3445 */
3446 static int
3447term_after_channel_closed(term_T *term)
3448{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003449 // Unless in Terminal-Normal mode: clear the vterm.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003450 if (!term->tl_normal_mode)
3451 {
3452 int fnum = term->tl_buffer->b_fnum;
3453
3454 cleanup_vterm(term);
3455
3456 if (term->tl_finish == TL_FINISH_CLOSE)
3457 {
3458 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003459 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003460#ifdef FEAT_PROP_POPUP
3461 win_T *pwin = NULL;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003462
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003463 // If this was a terminal in a popup window, go back to the
3464 // previous window.
3465 if (popup_is_popup(curwin) && curbuf == term->tl_buffer)
3466 {
3467 pwin = curwin;
3468 if (win_valid(prevwin))
3469 win_enter(prevwin, FALSE);
3470 }
3471 else
3472#endif
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003473 // If this is the last normal window: exit Vim.
3474 if (term->tl_buffer->b_nwindows > 0 && only_one_window())
3475 {
3476 exarg_T ea;
3477
Bram Moolenaara80faa82020-04-12 19:37:17 +02003478 CLEAR_FIELD(ea);
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003479 ex_quit(&ea);
3480 return TRUE;
3481 }
3482
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003483 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003484 ch_log(NULL, "terminal job finished, closing window");
3485 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003486 // Avoid closing the window if we temporarily use it.
Bram Moolenaar517f71a2019-06-17 22:40:41 +02003487 if (curwin == aucmd_win)
3488 do_set_w_closing = TRUE;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003489 if (do_set_w_closing)
3490 curwin->w_closing = TRUE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003491 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003492 if (do_set_w_closing)
3493 curwin->w_closing = FALSE;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003494 aucmd_restbuf(&aco);
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003495#ifdef FEAT_PROP_POPUP
3496 if (pwin != NULL)
3497 popup_close_with_retval(pwin, 0);
3498#endif
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003499 return TRUE;
3500 }
3501 if (term->tl_finish == TL_FINISH_OPEN
3502 && term->tl_buffer->b_nwindows == 0)
3503 {
Bram Moolenaar47c5ea42020-11-12 15:12:15 +01003504 char *cmd = term->tl_opencmd == NULL
3505 ? "botright sbuf %d"
3506 : (char *)term->tl_opencmd;
3507 size_t len = strlen(cmd) + 50;
3508 char *buf = alloc(len);
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003509
Bram Moolenaar47c5ea42020-11-12 15:12:15 +01003510 if (buf != NULL)
3511 {
3512 ch_log(NULL, "terminal job finished, opening window");
3513 vim_snprintf(buf, len, cmd, fnum);
3514 do_cmdline_cmd((char_u *)buf);
3515 vim_free(buf);
3516 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003517 }
3518 else
3519 ch_log(NULL, "terminal job finished");
3520 }
3521
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003522 redraw_buf_and_status_later(term->tl_buffer, UPD_NOT_VALID);
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003523 return FALSE;
3524}
3525
Bram Moolenaard98c0b62020-02-02 15:25:16 +01003526#if defined(FEAT_PROP_POPUP) || defined(PROTO)
3527/*
3528 * If the current window is a terminal in a popup window and the job has
3529 * finished, close the popup window and to back to the previous window.
3530 * Otherwise return FAIL.
3531 */
3532 int
3533may_close_term_popup(void)
3534{
3535 if (popup_is_popup(curwin) && curbuf->b_term != NULL
Bram Moolenaar9e636b92022-05-29 22:37:05 +01003536 && !term_job_running_not_none(curbuf->b_term))
Bram Moolenaard98c0b62020-02-02 15:25:16 +01003537 {
3538 win_T *pwin = curwin;
3539
3540 if (win_valid(prevwin))
3541 win_enter(prevwin, FALSE);
3542 popup_close_with_retval(pwin, 0);
3543 return OK;
3544 }
3545 return FAIL;
3546}
3547#endif
3548
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003549/*
Bram Moolenaareea32af2021-11-21 14:51:13 +00003550 * Called when a channel is going to be closed, before invoking the close
3551 * callback.
3552 */
3553 void
3554term_channel_closing(channel_T *ch)
3555{
3556 term_T *term;
3557
3558 for (term = first_term; term != NULL; term = term->tl_next)
3559 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
3560 term->tl_channel_closing = TRUE;
3561}
3562
3563/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003564 * Called when a channel has been closed.
3565 * If this was a channel for a terminal window then finish it up.
3566 */
3567 void
3568term_channel_closed(channel_T *ch)
3569{
3570 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003571 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003572 int did_one = FALSE;
3573
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003574 for (term = first_term; term != NULL; term = next_term)
3575 {
3576 next_term = term->tl_next;
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02003577 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003578 {
3579 term->tl_channel_closed = TRUE;
3580 did_one = TRUE;
3581
Bram Moolenaard23a8232018-02-10 18:45:26 +01003582 VIM_CLEAR(term->tl_title);
3583 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar4f974752019-02-17 17:44:42 +01003584#ifdef MSWIN
Bram Moolenaar402c8392018-05-06 22:01:42 +02003585 if (term->tl_out_fd != NULL)
3586 {
3587 fclose(term->tl_out_fd);
3588 term->tl_out_fd = NULL;
3589 }
3590#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003591
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003592 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003593 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003594 // Cannot open or close windows now. Can happen when
3595 // 'lazyredraw' is set.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003596 term->tl_channel_recently_closed = TRUE;
3597 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003598 }
3599
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003600 if (term_after_channel_closed(term))
3601 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003602 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003603 }
3604
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003605 if (did_one)
3606 {
3607 redraw_statuslines();
3608
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003609 // Need to break out of vgetc().
Bram Moolenaarb42c0d52020-05-29 22:41:41 +02003610 ins_char_typebuf(K_IGNORE, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003611 typebuf_was_filled = TRUE;
3612
3613 term = curbuf->b_term;
3614 if (term != NULL)
3615 {
3616 if (term->tl_job == ch->ch_job)
3617 maketitle();
3618 update_cursor(term, term->tl_cursor_visible);
3619 }
3620 }
3621}
3622
3623/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003624 * To be called after resetting updating_screen: handle any terminal where the
3625 * channel was closed.
3626 */
3627 void
3628term_check_channel_closed_recently()
3629{
3630 term_T *term;
3631 term_T *next_term;
3632
3633 for (term = first_term; term != NULL; term = next_term)
3634 {
3635 next_term = term->tl_next;
3636 if (term->tl_channel_recently_closed)
3637 {
3638 term->tl_channel_recently_closed = FALSE;
3639 if (term_after_channel_closed(term))
3640 // start over, the list may have changed
3641 next_term = first_term;
3642 }
3643 }
3644}
3645
3646/*
Bram Moolenaar13568252018-03-16 20:46:58 +01003647 * Fill one screen line from a line of the terminal.
3648 * Advances "pos" to past the last column.
3649 */
3650 static void
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003651term_line2screenline(
Bram Moolenaar83d47902020-03-26 20:34:00 +01003652 term_T *term,
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003653 win_T *wp,
3654 VTermScreen *screen,
3655 VTermPos *pos,
3656 int max_col)
Bram Moolenaar13568252018-03-16 20:46:58 +01003657{
3658 int off = screen_get_current_line_off();
3659
3660 for (pos->col = 0; pos->col < max_col; )
3661 {
3662 VTermScreenCell cell;
3663 int c;
3664
3665 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
Bram Moolenaara80faa82020-04-12 19:37:17 +02003666 CLEAR_FIELD(cell);
Bram Moolenaar13568252018-03-16 20:46:58 +01003667
3668 c = cell.chars[0];
3669 if (c == NUL)
3670 {
3671 ScreenLines[off] = ' ';
3672 if (enc_utf8)
3673 ScreenLinesUC[off] = NUL;
3674 }
3675 else
3676 {
3677 if (enc_utf8)
3678 {
3679 int i;
3680
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003681 // composing chars
Bram Moolenaar13568252018-03-16 20:46:58 +01003682 for (i = 0; i < Screen_mco
3683 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3684 {
3685 ScreenLinesC[i][off] = cell.chars[i + 1];
3686 if (cell.chars[i + 1] == 0)
3687 break;
3688 }
3689 if (c >= 0x80 || (Screen_mco > 0
3690 && ScreenLinesC[0][off] != 0))
3691 {
3692 ScreenLines[off] = ' ';
3693 ScreenLinesUC[off] = c;
3694 }
3695 else
3696 {
3697 ScreenLines[off] = c;
3698 ScreenLinesUC[off] = NUL;
3699 }
3700 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01003701#ifdef MSWIN
Bram Moolenaar13568252018-03-16 20:46:58 +01003702 else if (has_mbyte && c >= 0x80)
3703 {
3704 char_u mb[MB_MAXBYTES+1];
3705 WCHAR wc = c;
3706
3707 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3708 (char*)mb, 2, 0, 0) > 1)
3709 {
3710 ScreenLines[off] = mb[0];
3711 ScreenLines[off + 1] = mb[1];
3712 cell.width = mb_ptr2cells(mb);
3713 }
3714 else
3715 ScreenLines[off] = c;
3716 }
3717#endif
3718 else
Bram Moolenaar927495b2020-11-06 17:58:35 +01003719 // This will only store the lower byte of "c".
Bram Moolenaar13568252018-03-16 20:46:58 +01003720 ScreenLines[off] = c;
3721 }
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003722 ScreenAttrs[off] = cell2attr(term, wp, &cell.attrs, &cell.fg,
3723 &cell.bg);
Bram Moolenaar13568252018-03-16 20:46:58 +01003724
3725 ++pos->col;
3726 ++off;
3727 if (cell.width == 2)
3728 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003729 // don't set the second byte to NUL for a DBCS encoding, it
3730 // has been set above
Bram Moolenaar927495b2020-11-06 17:58:35 +01003731 if (enc_utf8)
3732 {
3733 ScreenLinesUC[off] = NUL;
Bram Moolenaar13568252018-03-16 20:46:58 +01003734 ScreenLines[off] = NUL;
Bram Moolenaar927495b2020-11-06 17:58:35 +01003735 }
3736 else if (!has_mbyte)
3737 {
3738 // Can't show a double-width character with a single-byte
3739 // 'encoding', just use a space.
3740 ScreenLines[off] = ' ';
3741 ScreenAttrs[off] = ScreenAttrs[off - 1];
3742 }
Bram Moolenaar13568252018-03-16 20:46:58 +01003743
3744 ++pos->col;
3745 ++off;
3746 }
3747 }
3748}
3749
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003750#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003751 static void
3752update_system_term(term_T *term)
3753{
3754 VTermPos pos;
3755 VTermScreen *screen;
3756
3757 if (term->tl_vterm == NULL)
3758 return;
3759 screen = vterm_obtain_screen(term->tl_vterm);
3760
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003761 // Scroll up to make more room for terminal lines if needed.
Bram Moolenaar13568252018-03-16 20:46:58 +01003762 while (term->tl_toprow > 0
3763 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3764 {
3765 int save_p_more = p_more;
3766
3767 p_more = FALSE;
3768 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003769 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003770 p_more = save_p_more;
3771 --term->tl_toprow;
3772 }
3773
3774 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3775 && pos.row < Rows; ++pos.row)
3776 {
3777 if (pos.row < term->tl_rows)
3778 {
3779 int max_col = MIN(Columns, term->tl_cols);
3780
Bram Moolenaar83d47902020-03-26 20:34:00 +01003781 term_line2screenline(term, NULL, screen, &pos, max_col);
Bram Moolenaar13568252018-03-16 20:46:58 +01003782 }
3783 else
3784 pos.col = 0;
3785
Bram Moolenaar510f0372022-07-04 17:46:22 +01003786 screen_line(curwin, term->tl_toprow + pos.row, 0, pos.col, Columns, 0);
Bram Moolenaar13568252018-03-16 20:46:58 +01003787 }
3788
3789 term->tl_dirty_row_start = MAX_ROW;
3790 term->tl_dirty_row_end = 0;
Bram Moolenaar13568252018-03-16 20:46:58 +01003791}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003792#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003793
3794/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003795 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3796 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003797 * Terminal-Normal mode.
3798 */
3799 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003800term_do_update_window(win_T *wp)
3801{
3802 term_T *term = wp->w_buffer->b_term;
3803
3804 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3805}
3806
3807/*
3808 * Called to update a window that contains an active terminal.
3809 */
3810 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003811term_update_window(win_T *wp)
3812{
3813 term_T *term = wp->w_buffer->b_term;
3814 VTerm *vterm;
3815 VTermScreen *screen;
3816 VTermState *state;
3817 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003818 int rows, cols;
3819 int newrows, newcols;
3820 int minsize;
3821 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003822
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003823 vterm = term->tl_vterm;
3824 screen = vterm_obtain_screen(vterm);
3825 state = vterm_obtain_state(vterm);
3826
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003827 // We use UPD_NOT_VALID on a resize or scroll, redraw everything then.
3828 // With UPD_SOME_VALID only redraw what was marked dirty.
3829 if (wp->w_redr_type > UPD_SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003830 {
3831 term->tl_dirty_row_start = 0;
3832 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003833
3834 if (term->tl_postponed_scroll > 0
3835 && term->tl_postponed_scroll < term->tl_rows / 3)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003836 // Scrolling is usually faster than redrawing, when there are only
3837 // a few lines to scroll.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003838 term_scroll_up(term, 0, term->tl_postponed_scroll);
3839 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003840 }
3841
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003842 /*
3843 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003844 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003845 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003846 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003847
Bram Moolenaar498c2562018-04-15 23:45:15 +02003848 newrows = 99999;
3849 newcols = 99999;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003850 for (twp = firstwin; ; twp = twp->w_next)
Bram Moolenaar498c2562018-04-15 23:45:15 +02003851 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003852 // Always use curwin, it may be a popup window.
3853 win_T *wwp = twp == NULL ? curwin : twp;
3854
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003855 // When more than one window shows the same terminal, use the
3856 // smallest size.
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003857 if (wwp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003858 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003859 newrows = MIN(newrows, wwp->w_height);
3860 newcols = MIN(newcols, wwp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003861 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003862 if (twp == NULL)
3863 break;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003864 }
Bram Moolenaare0d749a2019-09-25 22:14:48 +02003865 if (newrows == 99999 || newcols == 99999)
3866 return; // safety exit
Bram Moolenaar498c2562018-04-15 23:45:15 +02003867 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3868 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3869
Bram Moolenaareba13e42021-02-23 17:47:23 +01003870 // If no cell is visible there is no point in resizing. Also, vterm can't
3871 // handle a zero height.
3872 if (newrows == 0 || newcols == 0)
3873 return;
3874
Bram Moolenaar498c2562018-04-15 23:45:15 +02003875 if (term->tl_rows != newrows || term->tl_cols != newcols)
3876 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003877 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003878 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003879 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003880 newrows);
3881 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003882
3883 // Updating the terminal size will cause the snapshot to be cleared.
3884 // When not in terminal_loop() we need to restore it.
3885 if (term != in_terminal_loop)
3886 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003887 }
3888
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003889 // The cursor may have been moved when resizing.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003890 vterm_state_get_cursorpos(state, &pos);
Bram Moolenaarebec3e22020-11-28 20:22:06 +01003891 position_cursor(wp, &pos);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003892
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003893 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3894 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003895 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003896 if (pos.row < term->tl_rows)
3897 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003898 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003899
Bram Moolenaar83d47902020-03-26 20:34:00 +01003900 term_line2screenline(term, wp, screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003901 }
3902 else
3903 pos.col = 0;
3904
Bram Moolenaar510f0372022-07-04 17:46:22 +01003905 screen_line(wp, wp->w_winrow + pos.row
Bram Moolenaarf118d482018-03-13 13:14:00 +01003906#ifdef FEAT_MENU
3907 + winbar_height(wp)
3908#endif
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003909 , wp->w_wincol, pos.col, wp->w_width,
3910#ifdef FEAT_PROP_POPUP
3911 popup_is_popup(wp) ? SLF_POPUP :
3912#endif
3913 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003914 }
Bram Moolenaar3194e5b2021-12-13 21:59:09 +00003915}
3916
3917/*
3918 * Called after updating all windows: may reset dirty rows.
3919 */
3920 void
3921term_did_update_window(win_T *wp)
3922{
3923 term_T *term = wp->w_buffer->b_term;
3924
3925 if (term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode
3926 && wp->w_redr_type == 0)
3927 {
3928 term->tl_dirty_row_start = MAX_ROW;
3929 term->tl_dirty_row_end = 0;
3930 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003931}
3932
3933/*
3934 * Return TRUE if "wp" is a terminal window where the job has finished.
3935 */
3936 int
3937term_is_finished(buf_T *buf)
3938{
3939 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
3940}
3941
3942/*
3943 * Return TRUE if "wp" is a terminal window where the job has finished or we
3944 * are in Terminal-Normal mode, thus we show the buffer contents.
3945 */
3946 int
3947term_show_buffer(buf_T *buf)
3948{
3949 term_T *term = buf->b_term;
3950
3951 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
3952}
3953
3954/*
3955 * The current buffer is going to be changed. If there is terminal
3956 * highlighting remove it now.
3957 */
3958 void
3959term_change_in_curbuf(void)
3960{
3961 term_T *term = curbuf->b_term;
3962
3963 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
3964 {
3965 free_scrollback(term);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003966 redraw_buf_later(term->tl_buffer, UPD_NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003967
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003968 // The buffer is now like a normal buffer, it cannot be easily
3969 // abandoned when changed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003970 set_string_option_direct((char_u *)"buftype", -1,
3971 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
3972 }
3973}
3974
3975/*
3976 * Get the screen attribute for a position in the buffer.
3977 * Use a negative "col" to get the filler background color.
3978 */
3979 int
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003980term_get_attr(win_T *wp, linenr_T lnum, int col)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003981{
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003982 buf_T *buf = wp->w_buffer;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003983 term_T *term = buf->b_term;
3984 sb_line_T *line;
3985 cellattr_T *cellattr;
3986
3987 if (lnum > term->tl_scrollback.ga_len)
3988 cellattr = &term->tl_default_color;
3989 else
3990 {
3991 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
3992 if (col < 0 || col >= line->sb_cols)
3993 cellattr = &line->sb_fill_attr;
3994 else
3995 cellattr = line->sb_cells + col;
3996 }
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003997 return cell2attr(term, wp, &cellattr->attrs, &cellattr->fg, &cellattr->bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003998}
3999
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004000/*
4001 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02004002 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004003 */
4004 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02004005cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004006{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004007 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->index);
4008 if (rgb->index == 0)
4009 rgb->type = VTERM_COLOR_RGB;
4010 else
4011 {
4012 rgb->type = VTERM_COLOR_INDEXED;
4013 --rgb->index;
4014 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004015}
4016
4017/*
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004018 * Initialize vterm color from the synID.
4019 * Returns TRUE if color is set to "fg" and "bg".
4020 * Otherwise returns FALSE.
4021 */
4022 static int
4023get_vterm_color_from_synid(int id, VTermColor *fg, VTermColor *bg)
4024{
4025#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
4026 // Use the actual color for the GUI and when 'termguicolors' is set.
4027 if (0
4028# ifdef FEAT_GUI
4029 || gui.in_use
4030# endif
4031# ifdef FEAT_TERMGUICOLORS
4032 || p_tgc
4033# ifdef FEAT_VTP
4034 // Finally get INVALCOLOR on this execution path
4035 || (!p_tgc && t_colors >= 256)
4036# endif
4037# endif
4038 )
4039 {
4040 guicolor_T fg_rgb = INVALCOLOR;
4041 guicolor_T bg_rgb = INVALCOLOR;
4042
4043 if (id > 0)
4044 syn_id2colors(id, &fg_rgb, &bg_rgb);
4045
4046 if (fg_rgb != INVALCOLOR)
4047 {
4048 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
4049 fg->red = (unsigned)(rgb >> 16);
4050 fg->green = (unsigned)(rgb >> 8) & 255;
4051 fg->blue = (unsigned)rgb & 255;
4052 fg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_FG;
4053 }
4054 else
4055 fg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
4056
4057 if (bg_rgb != INVALCOLOR)
4058 {
4059 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
4060 bg->red = (unsigned)(rgb >> 16);
4061 bg->green = (unsigned)(rgb >> 8) & 255;
4062 bg->blue = (unsigned)rgb & 255;
4063 bg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_BG;
4064 }
4065 else
4066 bg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
4067
4068 return TRUE;
4069 }
4070 else
4071#endif
4072 if (t_colors >= 16)
4073 {
4074 int cterm_fg = -1;
4075 int cterm_bg = -1;
4076
4077 if (id > 0)
4078 syn_id2cterm_bg(id, &cterm_fg, &cterm_bg);
4079
4080 if (cterm_fg >= 0)
4081 {
4082 cterm_color2vterm(cterm_fg, fg);
4083 fg->type |= VTERM_COLOR_DEFAULT_FG;
4084 }
4085 else
4086 fg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
4087
4088 if (cterm_bg >= 0)
4089 {
4090 cterm_color2vterm(cterm_bg, bg);
4091 bg->type |= VTERM_COLOR_DEFAULT_BG;
4092 }
4093 else
4094 bg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
4095
4096 return TRUE;
4097 }
4098
4099 return FALSE;
4100}
4101
4102 void
4103term_reset_wincolor(win_T *wp)
4104{
4105 wp->w_term_wincolor.fg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
4106 wp->w_term_wincolor.bg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
4107}
4108
4109/*
4110 * Cache the color of 'wincolor'.
4111 */
4112 void
4113term_update_wincolor(win_T *wp)
4114{
4115 int id = 0;
4116
4117 if (*wp->w_p_wcr != NUL)
4118 id = syn_name2id(wp->w_p_wcr);
4119 if (id == 0 || !get_vterm_color_from_synid(id, &wp->w_term_wincolor.fg,
4120 &wp->w_term_wincolor.bg))
4121 term_reset_wincolor(wp);
4122}
4123
4124/*
4125 * Called when option 'termguicolors' was set,
4126 * or when any highlight is changed.
4127 */
4128 void
4129term_update_wincolor_all()
4130{
4131 win_T *wp = NULL;
4132 int did_curwin = FALSE;
4133
4134 while (for_all_windows_and_curwin(&wp, &did_curwin))
4135 term_update_wincolor(wp);
4136}
4137
4138/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004139 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004140 */
4141 static void
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004142init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004143{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004144 VTermColor *fg, *bg;
4145 int fgval, bgval;
4146 int id;
4147
Bram Moolenaara80faa82020-04-12 19:37:17 +02004148 CLEAR_FIELD(term->tl_default_color.attrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004149 term->tl_default_color.width = 1;
4150 fg = &term->tl_default_color.fg;
4151 bg = &term->tl_default_color.bg;
4152
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004153 // Vterm uses a default black background. Set it to white when
4154 // 'background' is "light".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004155 if (*p_bg == 'l')
4156 {
4157 fgval = 0;
4158 bgval = 255;
4159 }
4160 else
4161 {
4162 fgval = 255;
4163 bgval = 0;
4164 }
4165 fg->red = fg->green = fg->blue = fgval;
4166 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004167 fg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_FG;
4168 bg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_BG;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004169
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004170 // The highlight group overrules the defaults.
4171 id = term_get_highlight_id(term, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004172
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004173 if (!get_vterm_color_from_synid(id, fg, bg))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004174 {
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004175#if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004176 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004177#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004178
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004179 // In an MS-Windows console we know the normal colors.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004180 if (cterm_normal_fg_color > 0)
4181 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02004182 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004183# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
4184# ifdef VIMDLL
4185 if (!gui.in_use)
4186# endif
4187 {
4188 tmp = fg->red;
4189 fg->red = fg->blue;
4190 fg->blue = tmp;
4191 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004192# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004193 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02004194# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004195 else
4196 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02004197# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004198
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004199 if (cterm_normal_bg_color > 0)
4200 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02004201 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004202# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
4203# ifdef VIMDLL
4204 if (!gui.in_use)
4205# endif
4206 {
4207 tmp = fg->red;
4208 fg->red = fg->blue;
4209 fg->blue = tmp;
4210 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004211# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004212 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02004213# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004214 else
4215 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02004216# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004217 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01004218}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004219
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004220#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
4221/*
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004222 * Return TRUE if the user-defined palette (either g:terminal_ansi_colors or the
4223 * "ansi_colors" argument in term_start()) shall be applied.
4224 */
4225 static int
4226term_use_palette()
4227{
4228 if (0
4229#ifdef FEAT_GUI
4230 || gui.in_use
4231#endif
4232#ifdef FEAT_TERMGUICOLORS
4233 || p_tgc
4234#endif
4235 )
4236 return TRUE;
4237 return FALSE;
4238}
4239
4240/*
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004241 * Set the 16 ANSI colors from array of RGB values
4242 */
4243 static void
4244set_vterm_palette(VTerm *vterm, long_u *rgb)
4245{
4246 int index = 0;
4247 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004248
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004249 for (; index < 16; index++)
4250 {
4251 VTermColor color;
Bram Moolenaaref8c83c2019-04-11 11:40:13 +02004252
Millyb771b6b2021-11-23 12:07:25 +00004253 color.type = VTERM_COLOR_RGB;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004254 color.red = (unsigned)(rgb[index] >> 16);
4255 color.green = (unsigned)(rgb[index] >> 8) & 255;
4256 color.blue = (unsigned)rgb[index] & 255;
Bram Moolenaar68afde42022-02-25 20:48:26 +00004257 color.index = 0;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004258 vterm_state_set_palette_color(state, index, &color);
4259 }
4260}
4261
4262/*
4263 * Set the ANSI color palette from a list of colors
4264 */
4265 static int
4266set_ansi_colors_list(VTerm *vterm, list_T *list)
4267{
4268 int n = 0;
4269 long_u rgb[16];
Bram Moolenaarb0992022020-01-30 14:55:42 +01004270 listitem_T *li;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004271
Bram Moolenaarb0992022020-01-30 14:55:42 +01004272 for (li = list->lv_first; li != NULL && n < 16; li = li->li_next, n++)
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004273 {
4274 char_u *color_name;
4275 guicolor_T guicolor;
4276
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004277 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004278 if (color_name == NULL)
4279 return FAIL;
4280
4281 guicolor = GUI_GET_COLOR(color_name);
4282 if (guicolor == INVALCOLOR)
4283 return FAIL;
4284
4285 rgb[n] = GUI_MCH_GET_RGB(guicolor);
4286 }
4287
4288 if (n != 16 || li != NULL)
4289 return FAIL;
4290
4291 set_vterm_palette(vterm, rgb);
4292
4293 return OK;
4294}
4295
4296/*
4297 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
4298 */
4299 static void
4300init_vterm_ansi_colors(VTerm *vterm)
4301{
4302 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
4303
LemonBoyb2b3acb2022-05-20 10:10:34 +01004304 if (var == NULL)
4305 return;
4306
4307 if (var->di_tv.v_type != VAR_LIST
4308 || var->di_tv.vval.v_list == NULL
4309 || var->di_tv.vval.v_list->lv_first == &range_list_item
4310 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL)
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004311 semsg(_(e_invalid_argument_str), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004312}
4313#endif
4314
Bram Moolenaar52acb112018-03-18 19:20:22 +01004315/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004316 * Handles a "drop" command from the job in the terminal.
4317 * "item" is the file name, "item->li_next" may have options.
4318 */
4319 static void
4320handle_drop_command(listitem_T *item)
4321{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004322 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004323 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004324 int bufnr;
4325 win_T *wp;
4326 tabpage_T *tp;
4327 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004328 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004329
4330 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
4331 FOR_ALL_TAB_WINDOWS(tp, wp)
4332 {
4333 if (wp->w_buffer->b_fnum == bufnr)
4334 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004335 // buffer is in a window already, go there
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004336 goto_tabpage_win(tp, wp);
4337 return;
4338 }
4339 }
4340
Bram Moolenaara80faa82020-04-12 19:37:17 +02004341 CLEAR_FIELD(ea);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004342
4343 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
4344 && opt_item->li_tv.vval.v_dict != NULL)
4345 {
4346 dict_T *dict = opt_item->li_tv.vval.v_dict;
4347 char_u *p;
4348
Bram Moolenaard61efa52022-07-23 09:52:04 +01004349 p = dict_get_string(dict, "ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004350 if (p == NULL)
Bram Moolenaard61efa52022-07-23 09:52:04 +01004351 p = dict_get_string(dict, "fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004352 if (p != NULL)
4353 {
4354 if (check_ff_value(p) == FAIL)
4355 ch_log(NULL, "Invalid ff argument to drop: %s", p);
4356 else
4357 ea.force_ff = *p;
4358 }
Bram Moolenaard61efa52022-07-23 09:52:04 +01004359 p = dict_get_string(dict, "enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004360 if (p == NULL)
Bram Moolenaard61efa52022-07-23 09:52:04 +01004361 p = dict_get_string(dict, "encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004362 if (p != NULL)
4363 {
Bram Moolenaar51e14382019-05-25 20:21:28 +02004364 ea.cmd = alloc(STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004365 if (ea.cmd != NULL)
4366 {
4367 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
4368 ea.force_enc = 11;
4369 tofree = ea.cmd;
4370 }
4371 }
4372
Bram Moolenaard61efa52022-07-23 09:52:04 +01004373 p = dict_get_string(dict, "bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004374 if (p != NULL)
4375 get_bad_opt(p, &ea);
4376
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004377 if (dict_has_key(dict, "bin"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004378 ea.force_bin = FORCE_BIN;
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004379 if (dict_has_key(dict, "binary"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004380 ea.force_bin = FORCE_BIN;
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004381 if (dict_has_key(dict, "nobin"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004382 ea.force_bin = FORCE_NOBIN;
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004383 if (dict_has_key(dict, "nobinary"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004384 ea.force_bin = FORCE_NOBIN;
4385 }
4386
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004387 // open in new window, like ":split fname"
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004388 if (ea.cmd == NULL)
4389 ea.cmd = (char_u *)"split";
4390 ea.arg = fname;
4391 ea.cmdidx = CMD_split;
4392 ex_splitview(&ea);
4393
4394 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004395}
4396
4397/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004398 * Return TRUE if "func" starts with "pat" and "pat" isn't empty.
4399 */
4400 static int
4401is_permitted_term_api(char_u *func, char_u *pat)
4402{
4403 return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
4404}
4405
4406/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004407 * Handles a function call from the job running in a terminal.
4408 * "item" is the function name, "item->li_next" has the arguments.
4409 */
4410 static void
4411handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
4412{
4413 char_u *func;
4414 typval_T argvars[2];
4415 typval_T rettv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02004416 funcexe_T funcexe;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004417
4418 if (item->li_next == NULL)
4419 {
4420 ch_log(channel, "Missing function arguments for call");
4421 return;
4422 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004423 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004424
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004425 if (!is_permitted_term_api(func, term->tl_api))
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004426 {
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004427 ch_log(channel, "Unpermitted function: %s", func);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004428 return;
4429 }
4430
4431 argvars[0].v_type = VAR_NUMBER;
4432 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
4433 argvars[1] = item->li_next->li_tv;
Bram Moolenaara80faa82020-04-12 19:37:17 +02004434 CLEAR_FIELD(funcexe);
Bram Moolenaar851f86b2021-12-13 14:26:44 +00004435 funcexe.fe_firstline = 1L;
4436 funcexe.fe_lastline = 1L;
4437 funcexe.fe_evaluate = TRUE;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02004438 if (call_func(func, -1, &rettv, 2, argvars, &funcexe) == OK)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004439 {
4440 clear_tv(&rettv);
4441 ch_log(channel, "Function %s called", func);
4442 }
4443 else
4444 ch_log(channel, "Calling function %s failed", func);
4445}
4446
4447/*
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004448 * URL decoding (also know as Percent-encoding).
4449 *
4450 * Note this function currently is only used for decoding shell's
4451 * OSC 7 escape sequence which we can assume all bytes are valid
4452 * UTF-8 bytes. Thus we don't need to deal with invalid UTF-8
4453 * encoding bytes like 0xfe, 0xff.
4454 */
4455 static size_t
4456url_decode(const char *src, const size_t len, char_u *dst)
4457{
4458 size_t i = 0, j = 0;
4459
4460 while (i < len)
4461 {
4462 if (src[i] == '%' && i + 2 < len)
4463 {
4464 dst[j] = hexhex2nr((char_u *)&src[i + 1]);
4465 j++;
4466 i += 3;
4467 }
4468 else
4469 {
4470 dst[j] = src[i];
4471 i++;
4472 j++;
4473 }
4474 }
4475 dst[j] = '\0';
4476 return j;
4477}
4478
4479/*
4480 * Sync terminal buffer's cwd with shell's pwd with the help of OSC 7.
4481 *
4482 * The OSC 7 sequence has the format of
4483 * "\033]7;file://HOSTNAME/CURRENT/DIR\033\\"
4484 * and what VTerm provides via VTermStringFragment is
4485 * "file://HOSTNAME/CURRENT/DIR"
4486 */
4487 static void
Bram Moolenaar474ad392022-08-21 11:37:17 +01004488sync_shell_dir(garray_T *gap)
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004489{
Bram Moolenaar474ad392022-08-21 11:37:17 +01004490 int offset = 7; // len of "file://" is 7
4491 char *pos = (char *)gap->ga_data + offset;
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004492 char_u *new_dir;
4493
4494 // remove HOSTNAME to get PWD
Bram Moolenaar474ad392022-08-21 11:37:17 +01004495 while (offset < (int)gap->ga_len && *pos != '/' )
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004496 {
Bram Moolenaar474ad392022-08-21 11:37:17 +01004497 ++offset;
4498 ++pos;
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004499 }
4500
Bram Moolenaar474ad392022-08-21 11:37:17 +01004501 if (offset >= (int)gap->ga_len)
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004502 {
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004503 semsg(_(e_failed_to_extract_pwd_from_str_check_your_shell_config),
Bram Moolenaar474ad392022-08-21 11:37:17 +01004504 gap->ga_data);
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004505 return;
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004506 }
4507
Bram Moolenaar474ad392022-08-21 11:37:17 +01004508 new_dir = alloc(gap->ga_len - offset + 1);
4509 url_decode(pos, gap->ga_len-offset, new_dir);
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004510 changedir_func(new_dir, TRUE, CDSCOPE_WINDOW);
4511 vim_free(new_dir);
4512}
4513
4514/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004515 * Called by libvterm when it cannot recognize an OSC sequence.
4516 * We recognize a terminal API command.
4517 */
4518 static int
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02004519parse_osc(int command, VTermStringFragment frag, void *user)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004520{
4521 term_T *term = (term_T *)user;
4522 js_read_T reader;
4523 typval_T tv;
4524 channel_T *channel = term->tl_job == NULL ? NULL
4525 : term->tl_job->jv_channel;
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004526 garray_T *gap = &term->tl_osc_buf;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004527
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004528 // We recognize only OSC 5 1 ; {command} and OSC 7 ; {command}
Bram Moolenaar474ad392022-08-21 11:37:17 +01004529 if (command != 51 && (command != 7 || !p_asd))
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02004530 return 0;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004531
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004532 // Concatenate what was received until the final piece is found.
4533 if (ga_grow(gap, (int)frag.len + 1) == FAIL)
4534 {
4535 ga_clear(gap);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004536 return 1;
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004537 }
4538 mch_memmove((char *)gap->ga_data + gap->ga_len, frag.str, frag.len);
Bram Moolenaarf4b68e92020-05-27 21:22:14 +02004539 gap->ga_len += (int)frag.len;
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004540 if (!frag.final)
4541 return 1;
4542
4543 ((char *)gap->ga_data)[gap->ga_len] = 0;
Bram Moolenaar474ad392022-08-21 11:37:17 +01004544
4545 if (command == 7)
4546 {
4547 sync_shell_dir(gap);
4548 ga_clear(gap);
4549 return 1;
4550 }
4551
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004552 reader.js_buf = gap->ga_data;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004553 reader.js_fill = NULL;
4554 reader.js_used = 0;
4555 if (json_decode(&reader, &tv, 0) == OK
4556 && tv.v_type == VAR_LIST
4557 && tv.vval.v_list != NULL)
4558 {
4559 listitem_T *item = tv.vval.v_list->lv_first;
4560
4561 if (item == NULL)
4562 ch_log(channel, "Missing command");
4563 else
4564 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004565 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004566
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004567 // Make sure an invoked command doesn't delete the buffer (and the
4568 // terminal) under our fingers.
Bram Moolenaara997b452018-04-17 23:24:06 +02004569 ++term->tl_buffer->b_locked;
4570
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004571 item = item->li_next;
4572 if (item == NULL)
4573 ch_log(channel, "Missing argument for %s", cmd);
4574 else if (STRCMP(cmd, "drop") == 0)
4575 handle_drop_command(item);
4576 else if (STRCMP(cmd, "call") == 0)
4577 handle_call_command(term, channel, item);
4578 else
4579 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02004580 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004581 }
4582 }
4583 else
4584 ch_log(channel, "Invalid JSON received");
4585
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004586 ga_clear(gap);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004587 clear_tv(&tv);
4588 return 1;
4589}
4590
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004591/*
4592 * Called by libvterm when it cannot recognize a CSI sequence.
4593 * We recognize the window position report.
4594 */
4595 static int
4596parse_csi(
4597 const char *leader UNUSED,
4598 const long args[],
4599 int argcount,
4600 const char *intermed UNUSED,
4601 char command,
4602 void *user)
4603{
4604 term_T *term = (term_T *)user;
4605 char buf[100];
4606 int len;
4607 int x = 0;
4608 int y = 0;
4609 win_T *wp;
4610
4611 // We recognize only CSI 13 t
4612 if (command != 't' || argcount != 1 || args[0] != 13)
4613 return 0; // not handled
4614
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004615 // When getting the window position is not possible or it fails it results
4616 // in zero/zero.
Bram Moolenaar16c34c32019-04-06 22:01:24 +02004617#if defined(FEAT_GUI) \
4618 || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
4619 || defined(MSWIN)
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004620 (void)ui_get_winpos(&x, &y, (varnumber_T)100);
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004621#endif
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004622
4623 FOR_ALL_WINDOWS(wp)
4624 if (wp->w_buffer == term->tl_buffer)
4625 break;
4626 if (wp != NULL)
4627 {
4628#ifdef FEAT_GUI
4629 if (gui.in_use)
4630 {
4631 x += wp->w_wincol * gui.char_width;
4632 y += W_WINROW(wp) * gui.char_height;
4633 }
4634 else
4635#endif
4636 {
4637 // We roughly estimate the position of the terminal window inside
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004638 // the Vim window by assuming a 10 x 7 character cell.
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004639 x += wp->w_wincol * 7;
4640 y += W_WINROW(wp) * 10;
4641 }
4642 }
4643
4644 len = vim_snprintf(buf, 100, "\x1b[3;%d;%dt", x, y);
4645 channel_send(term->tl_job->jv_channel, get_tty_part(term),
4646 (char_u *)buf, len, NULL);
4647 return 1;
4648}
4649
Bram Moolenaard8637282020-05-20 18:41:41 +02004650static VTermStateFallbacks state_fallbacks = {
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004651 NULL, // control
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004652 parse_csi, // csi
4653 parse_osc, // osc
Bram Moolenaar7da34152021-11-24 19:30:55 +00004654 NULL, // dcs
4655 NULL, // apc
4656 NULL, // pm
4657 NULL // sos
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004658};
4659
4660/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02004661 * Use Vim's allocation functions for vterm so profiling works.
4662 */
4663 static void *
4664vterm_malloc(size_t size, void *data UNUSED)
4665{
Bram Moolenaar88137392021-11-12 16:01:15 +00004666 // make sure that the length is not zero
4667 return alloc_clear(size == 0 ? 1L : size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02004668}
4669
4670 static void
4671vterm_memfree(void *ptr, void *data UNUSED)
4672{
4673 vim_free(ptr);
4674}
4675
4676static VTermAllocatorFunctions vterm_allocator = {
4677 &vterm_malloc,
4678 &vterm_memfree
4679};
4680
4681/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004682 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004683 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004684 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004685 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01004686create_vterm(term_T *term, int rows, int cols)
4687{
4688 VTerm *vterm;
4689 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004690 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01004691 VTermValue value;
4692
Bram Moolenaar756ef112018-04-10 12:04:27 +02004693 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004694 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004695 if (vterm == NULL)
4696 return FAIL;
4697
4698 // Allocate screen and state here, so we can bail out if that fails.
4699 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004700 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004701 if (state == NULL || screen == NULL)
4702 {
4703 vterm_free(vterm);
4704 return FAIL;
4705 }
4706
Bram Moolenaar52acb112018-03-18 19:20:22 +01004707 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004708 // TODO: depends on 'encoding'.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004709 vterm_set_utf8(vterm, 1);
4710
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004711 init_default_colors(term);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004712
4713 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004714 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01004715 &term->tl_default_color.fg,
4716 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004717
Bram Moolenaar9e587872019-05-13 20:27:23 +02004718 if (t_colors < 16)
4719 // Less than 16 colors: assume that bold means using a bright color for
4720 // the foreground color.
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004721 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
4722
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004723 // Required to initialize most things.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004724 vterm_screen_reset(screen, 1 /* hard */);
4725
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004726 // Allow using alternate screen.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004727 vterm_screen_enable_altscreen(screen, 1);
4728
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004729 // For unix do not use a blinking cursor. In an xterm this causes the
4730 // cursor to blink if it's blinking in the xterm.
4731 // For Windows we respect the system wide setting.
Bram Moolenaar4f974752019-02-17 17:44:42 +01004732#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004733 if (GetCaretBlinkTime() == INFINITE)
4734 value.boolean = 0;
4735 else
4736 value.boolean = 1;
4737#else
4738 value.boolean = 0;
4739#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004740 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
Bram Moolenaard8637282020-05-20 18:41:41 +02004741 vterm_state_set_unrecognised_fallbacks(state, &state_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004742
4743 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004744}
4745
4746/*
LemonBoyb2b3acb2022-05-20 10:10:34 +01004747 * Reset the terminal palette to its default value.
4748 */
4749 static void
4750term_reset_palette(VTerm *vterm)
4751{
4752 VTermState *state = vterm_obtain_state(vterm);
4753 int index;
4754
4755 for (index = 0; index < 16; index++)
4756 {
4757 VTermColor color;
4758
4759 color.type = VTERM_COLOR_INDEXED;
4760 ansi_color2rgb(index, &color.red, &color.green, &color.blue,
4761 &color.index);
4762 // The first valid index starts at 1.
4763 color.index -= 1;
4764
4765 vterm_state_set_palette_color(state, index, &color);
4766 }
4767}
4768
4769 static void
4770term_update_palette(term_T *term)
4771{
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004772#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01004773 if (term_use_palette()
4774 && (term->tl_palette != NULL
4775 || find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE)
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004776 != NULL))
LemonBoyb2b3acb2022-05-20 10:10:34 +01004777 {
4778 if (term->tl_palette != NULL)
4779 set_vterm_palette(term->tl_vterm, term->tl_palette);
4780 else
4781 init_vterm_ansi_colors(term->tl_vterm);
4782 }
4783 else
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004784#endif
LemonBoyb2b3acb2022-05-20 10:10:34 +01004785 term_reset_palette(term->tl_vterm);
4786}
4787
4788/*
4789 * Called when option 'termguicolors' is changed.
4790 */
4791 void
4792term_update_palette_all()
4793{
4794 term_T *term;
4795
4796 FOR_ALL_TERMS(term)
4797 {
4798 if (term->tl_vterm == NULL)
4799 continue;
4800 term_update_palette(term);
4801 }
4802}
4803
4804/*
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004805 * Called when option 'background' or 'termguicolors' was set,
4806 * or when any highlight is changed.
Bram Moolenaarad431992021-05-03 20:40:38 +02004807 */
4808 void
4809term_update_colors_all(void)
4810{
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004811 term_T *term;
Bram Moolenaarad431992021-05-03 20:40:38 +02004812
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004813 FOR_ALL_TERMS(term)
4814 {
4815 if (term->tl_vterm == NULL)
4816 continue;
4817 init_default_colors(term);
4818 vterm_state_set_default_colors(
4819 vterm_obtain_state(term->tl_vterm),
4820 &term->tl_default_color.fg,
4821 &term->tl_default_color.bg);
4822 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01004823}
4824
4825/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004826 * Return the text to show for the buffer name and status.
4827 */
4828 char_u *
4829term_get_status_text(term_T *term)
4830{
4831 if (term->tl_status_text == NULL)
4832 {
4833 char_u *txt;
4834 size_t len;
Bram Moolenaar00806bc2020-11-05 19:36:38 +01004835 char_u *fname;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004836
4837 if (term->tl_normal_mode)
4838 {
4839 if (term_job_running(term))
4840 txt = (char_u *)_("Terminal");
4841 else
4842 txt = (char_u *)_("Terminal-finished");
4843 }
4844 else if (term->tl_title != NULL)
4845 txt = term->tl_title;
4846 else if (term_none_open(term))
4847 txt = (char_u *)_("active");
4848 else if (term_job_running(term))
4849 txt = (char_u *)_("running");
4850 else
4851 txt = (char_u *)_("finished");
Bram Moolenaar00806bc2020-11-05 19:36:38 +01004852 fname = buf_get_fname(term->tl_buffer);
4853 len = 9 + STRLEN(fname) + STRLEN(txt);
Bram Moolenaar51e14382019-05-25 20:21:28 +02004854 term->tl_status_text = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004855 if (term->tl_status_text != NULL)
4856 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
Bram Moolenaar00806bc2020-11-05 19:36:38 +01004857 fname, txt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004858 }
4859 return term->tl_status_text;
4860}
4861
4862/*
Bram Moolenaar3ad69532021-11-19 17:01:08 +00004863 * Clear the cached value of the status text.
4864 */
4865 void
4866term_clear_status_text(term_T *term)
4867{
4868 VIM_CLEAR(term->tl_status_text);
4869}
4870
4871/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004872 * Mark references in jobs of terminals.
4873 */
4874 int
4875set_ref_in_term(int copyID)
4876{
4877 int abort = FALSE;
4878 term_T *term;
4879 typval_T tv;
4880
Bram Moolenaar75a1a942019-06-20 03:45:36 +02004881 for (term = first_term; !abort && term != NULL; term = term->tl_next)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004882 if (term->tl_job != NULL)
4883 {
4884 tv.v_type = VAR_JOB;
4885 tv.vval.v_job = term->tl_job;
4886 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4887 }
4888 return abort;
4889}
4890
4891/*
4892 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004893 * Returns NULL when the buffer is not for a terminal window and logs a message
4894 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004895 */
4896 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004897term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004898{
4899 buf_T *buf;
4900
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004901 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01004902 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004903 --emsg_off;
4904 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004905 {
Bram Moolenaar4d05af02020-11-27 20:55:00 +01004906 (void)tv_get_number(&argvars[0]); // issue errmsg if type error
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004907 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004908 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004909 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004910 return buf;
4911}
4912
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004913 static void
4914clear_cell(VTermScreenCell *cell)
Bram Moolenaard96ff162018-02-18 22:13:29 +01004915{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004916 CLEAR_FIELD(*cell);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004917 cell->fg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
4918 cell->bg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004919}
4920
4921 static void
4922dump_term_color(FILE *fd, VTermColor *color)
4923{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004924 int index;
4925
4926 if (VTERM_COLOR_IS_INDEXED(color))
4927 index = color->index + 1;
4928 else if (color->type == 0)
4929 // use RGB values
4930 index = 255;
4931 else
4932 // default color
4933 index = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004934 fprintf(fd, "%02x%02x%02x%d",
4935 (int)color->red, (int)color->green, (int)color->blue,
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004936 index);
Bram Moolenaard96ff162018-02-18 22:13:29 +01004937}
4938
4939/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004940 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01004941 *
4942 * Each screen cell in full is:
4943 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
4944 * {characters} is a space for an empty cell
4945 * For a double-width character "+" is changed to "*" and the next cell is
4946 * skipped.
4947 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
4948 * when "&" use the same as the previous cell.
4949 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
4950 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
4951 * {color-idx} is a number from 0 to 255
4952 *
4953 * Screen cell with same width, attributes and color as the previous one:
4954 * |{characters}
4955 *
4956 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
4957 *
4958 * Repeating the previous screen cell:
4959 * @{count}
4960 */
4961 void
4962f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
4963{
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02004964 buf_T *buf;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004965 term_T *term;
4966 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004967 int max_height = 0;
4968 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004969 stat_T st;
4970 FILE *fd;
4971 VTermPos pos;
4972 VTermScreen *screen;
4973 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01004974 VTermState *state;
4975 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01004976
4977 if (check_restricted() || check_secure())
4978 return;
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02004979
4980 if (in_vim9script()
4981 && (check_for_buffer_arg(argvars, 0) == FAIL
4982 || check_for_string_arg(argvars, 1) == FAIL
4983 || check_for_opt_dict_arg(argvars, 2) == FAIL))
4984 return;
4985
4986 buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01004987 if (buf == NULL)
4988 return;
4989 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004990 if (term->tl_vterm == NULL)
4991 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00004992 emsg(_(e_job_already_finished));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02004993 return;
4994 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01004995
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01004996 if (argvars[2].v_type != VAR_UNKNOWN)
4997 {
4998 dict_T *d;
4999
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01005000 if (check_for_dict_arg(argvars, 2) == FAIL)
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005001 return;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005002 d = argvars[2].vval.v_dict;
5003 if (d != NULL)
5004 {
Bram Moolenaard61efa52022-07-23 09:52:04 +01005005 max_height = dict_get_number(d, "rows");
5006 max_width = dict_get_number(d, "columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005007 }
5008 }
5009
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005010 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005011 if (fname == NULL)
5012 return;
5013 if (mch_stat((char *)fname, &st) >= 0)
5014 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00005015 semsg(_(e_file_exists_str), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005016 return;
5017 }
5018
Bram Moolenaard96ff162018-02-18 22:13:29 +01005019 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
5020 {
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00005021 semsg(_(e_cant_create_file_str), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005022 return;
5023 }
5024
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005025 clear_cell(&prev_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005026
5027 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005028 state = vterm_obtain_state(term->tl_vterm);
5029 vterm_state_get_cursorpos(state, &cursor_pos);
5030
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005031 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
5032 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005033 {
5034 int repeat = 0;
5035
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005036 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
5037 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005038 {
5039 VTermScreenCell cell;
5040 int same_attr;
5041 int same_chars = TRUE;
5042 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005043 int is_cursor_pos = (pos.col == cursor_pos.col
5044 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005045
5046 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005047 clear_cell(&cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005048
5049 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5050 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01005051 int c = cell.chars[i];
5052 int pc = prev_cell.chars[i];
Bram Moolenaar9c24cd12020-10-23 15:40:39 +02005053 int should_break = c == NUL || pc == NUL;
Bram Moolenaar47015b82018-03-23 22:10:34 +01005054
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005055 // For the first character NUL is the same as space.
Bram Moolenaar47015b82018-03-23 22:10:34 +01005056 if (i == 0)
5057 {
5058 c = (c == NUL) ? ' ' : c;
5059 pc = (pc == NUL) ? ' ' : pc;
5060 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02005061 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005062 same_chars = FALSE;
Bram Moolenaar9c24cd12020-10-23 15:40:39 +02005063 if (should_break)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005064 break;
5065 }
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005066 same_attr = vtermAttr2hl(&cell.attrs)
5067 == vtermAttr2hl(&prev_cell.attrs)
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005068 && vterm_color_is_equal(&cell.fg, &prev_cell.fg)
5069 && vterm_color_is_equal(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005070 if (same_chars && cell.width == prev_cell.width && same_attr
5071 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005072 {
5073 ++repeat;
5074 }
5075 else
5076 {
5077 if (repeat > 0)
5078 {
5079 fprintf(fd, "@%d", repeat);
5080 repeat = 0;
5081 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01005082 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005083
5084 if (cell.chars[0] == NUL)
5085 fputs(" ", fd);
5086 else
5087 {
5088 char_u charbuf[10];
5089 int len;
5090
5091 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
5092 && cell.chars[i] != NUL; ++i)
5093 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02005094 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005095 fwrite(charbuf, len, 1, fd);
5096 }
5097 }
5098
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005099 // When only the characters differ we don't write anything, the
5100 // following "|", "@" or NL will indicate using the same
5101 // attributes.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005102 if (cell.width != prev_cell.width || !same_attr)
5103 {
5104 if (cell.width == 2)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005105 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005106 else
5107 fputs("+", fd);
5108
5109 if (same_attr)
5110 {
5111 fputs("&", fd);
5112 }
5113 else
5114 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005115 fprintf(fd, "%d", vtermAttr2hl(&cell.attrs));
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005116 if (vterm_color_is_equal(&cell.fg, &prev_cell.fg))
Bram Moolenaard96ff162018-02-18 22:13:29 +01005117 fputs("&", fd);
5118 else
5119 {
5120 fputs("#", fd);
5121 dump_term_color(fd, &cell.fg);
5122 }
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005123 if (vterm_color_is_equal(&cell.bg, &prev_cell.bg))
Bram Moolenaard96ff162018-02-18 22:13:29 +01005124 fputs("&", fd);
5125 else
5126 {
5127 fputs("#", fd);
5128 dump_term_color(fd, &cell.bg);
5129 }
5130 }
5131 }
5132
5133 prev_cell = cell;
5134 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005135
5136 if (cell.width == 2)
5137 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005138 }
5139 if (repeat > 0)
5140 fprintf(fd, "@%d", repeat);
5141 fputs("\n", fd);
5142 }
5143
5144 fclose(fd);
5145}
5146
5147/*
5148 * Called when a dump is corrupted. Put a breakpoint here when debugging.
5149 */
5150 static void
5151dump_is_corrupt(garray_T *gap)
5152{
5153 ga_concat(gap, (char_u *)"CORRUPT");
5154}
5155
5156 static void
5157append_cell(garray_T *gap, cellattr_T *cell)
5158{
5159 if (ga_grow(gap, 1) == OK)
5160 {
5161 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
5162 ++gap->ga_len;
5163 }
5164}
5165
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005166 static void
5167clear_cellattr(cellattr_T *cell)
5168{
5169 CLEAR_FIELD(*cell);
5170 cell->fg.type = VTERM_COLOR_DEFAULT_FG;
5171 cell->bg.type = VTERM_COLOR_DEFAULT_BG;
5172}
5173
Bram Moolenaard96ff162018-02-18 22:13:29 +01005174/*
5175 * Read the dump file from "fd" and append lines to the current buffer.
5176 * Return the cell width of the longest line.
5177 */
5178 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01005179read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005180{
5181 int c;
5182 garray_T ga_text;
5183 garray_T ga_cell;
5184 char_u *prev_char = NULL;
5185 int attr = 0;
5186 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005187 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005188 term_T *term = curbuf->b_term;
5189 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005190 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005191
5192 ga_init2(&ga_text, 1, 90);
5193 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005194 clear_cellattr(&cell);
5195 clear_cellattr(&empty_cell);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005196 cursor_pos->row = -1;
5197 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005198
5199 c = fgetc(fd);
5200 for (;;)
5201 {
5202 if (c == EOF)
5203 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02005204 if (c == '\r')
5205 {
5206 // DOS line endings? Ignore.
5207 c = fgetc(fd);
5208 }
5209 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01005210 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005211 // End of a line: append it to the buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005212 if (ga_text.ga_data == NULL)
5213 dump_is_corrupt(&ga_text);
5214 if (ga_grow(&term->tl_scrollback, 1) == OK)
5215 {
5216 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
5217 + term->tl_scrollback.ga_len;
5218
5219 if (max_cells < ga_cell.ga_len)
5220 max_cells = ga_cell.ga_len;
5221 line->sb_cols = ga_cell.ga_len;
5222 line->sb_cells = ga_cell.ga_data;
5223 line->sb_fill_attr = term->tl_default_color;
5224 ++term->tl_scrollback.ga_len;
5225 ga_init(&ga_cell);
5226
5227 ga_append(&ga_text, NUL);
5228 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
5229 ga_text.ga_len, FALSE);
5230 }
5231 else
5232 ga_clear(&ga_cell);
5233 ga_text.ga_len = 0;
5234
5235 c = fgetc(fd);
5236 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01005237 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01005238 {
5239 int prev_len = ga_text.ga_len;
5240
Bram Moolenaar9271d052018-02-25 21:39:46 +01005241 if (c == '>')
5242 {
5243 if (cursor_pos->row != -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005244 dump_is_corrupt(&ga_text); // duplicate cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01005245 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
5246 cursor_pos->col = ga_cell.ga_len;
5247 }
5248
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005249 // normal character(s) followed by "+", "*", "|", "@" or NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01005250 c = fgetc(fd);
5251 if (c != EOF)
5252 ga_append(&ga_text, c);
5253 for (;;)
5254 {
5255 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005256 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01005257 || c == EOF || c == '\n')
5258 break;
5259 ga_append(&ga_text, c);
5260 }
5261
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005262 // save the character for repeating it
Bram Moolenaard96ff162018-02-18 22:13:29 +01005263 vim_free(prev_char);
5264 if (ga_text.ga_data != NULL)
5265 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
5266 ga_text.ga_len - prev_len);
5267
Bram Moolenaar9271d052018-02-25 21:39:46 +01005268 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01005269 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005270 // use all attributes from previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01005271 }
5272 else if (c == '+' || c == '*')
5273 {
5274 int is_bg;
5275
5276 cell.width = c == '+' ? 1 : 2;
5277
5278 c = fgetc(fd);
5279 if (c == '&')
5280 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005281 // use same attr as previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01005282 c = fgetc(fd);
5283 }
5284 else if (isdigit(c))
5285 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005286 // get the decimal attribute
Bram Moolenaard96ff162018-02-18 22:13:29 +01005287 attr = 0;
5288 while (isdigit(c))
5289 {
5290 attr = attr * 10 + (c - '0');
5291 c = fgetc(fd);
5292 }
5293 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005294
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005295 // is_bg == 0: fg, is_bg == 1: bg
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005296 for (is_bg = 0; is_bg <= 1; ++is_bg)
5297 {
5298 if (c == '&')
5299 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005300 // use same color as previous cell
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005301 c = fgetc(fd);
5302 }
5303 else if (c == '#')
5304 {
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005305 int red, green, blue, index = 0, type;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005306
5307 c = fgetc(fd);
5308 red = hex2nr(c);
5309 c = fgetc(fd);
5310 red = (red << 4) + hex2nr(c);
5311 c = fgetc(fd);
5312 green = hex2nr(c);
5313 c = fgetc(fd);
5314 green = (green << 4) + hex2nr(c);
5315 c = fgetc(fd);
5316 blue = hex2nr(c);
5317 c = fgetc(fd);
5318 blue = (blue << 4) + hex2nr(c);
5319 c = fgetc(fd);
5320 if (!isdigit(c))
5321 dump_is_corrupt(&ga_text);
5322 while (isdigit(c))
5323 {
5324 index = index * 10 + (c - '0');
5325 c = fgetc(fd);
5326 }
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005327 if (index == 0 || index == 255)
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005328 {
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005329 type = VTERM_COLOR_RGB;
5330 if (index == 0)
5331 {
5332 if (is_bg)
5333 type |= VTERM_COLOR_DEFAULT_BG;
5334 else
5335 type |= VTERM_COLOR_DEFAULT_FG;
5336 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005337 }
5338 else
5339 {
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005340 type = VTERM_COLOR_INDEXED;
5341 index -= 1;
5342 }
5343 if (is_bg)
5344 {
5345 cell.bg.type = type;
5346 cell.bg.red = red;
5347 cell.bg.green = green;
5348 cell.bg.blue = blue;
5349 cell.bg.index = index;
5350 }
5351 else
5352 {
5353 cell.fg.type = type;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005354 cell.fg.red = red;
5355 cell.fg.green = green;
5356 cell.fg.blue = blue;
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005357 cell.fg.index = index;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005358 }
5359 }
5360 else
5361 dump_is_corrupt(&ga_text);
5362 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005363 }
5364 else
5365 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005366 }
5367 else
5368 dump_is_corrupt(&ga_text);
5369
5370 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005371 if (cell.width == 2)
5372 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005373 }
5374 else if (c == '@')
5375 {
5376 if (prev_char == NULL)
5377 dump_is_corrupt(&ga_text);
5378 else
5379 {
5380 int count = 0;
5381
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005382 // repeat previous character, get the count
Bram Moolenaard96ff162018-02-18 22:13:29 +01005383 for (;;)
5384 {
5385 c = fgetc(fd);
5386 if (!isdigit(c))
5387 break;
5388 count = count * 10 + (c - '0');
5389 }
5390
5391 while (count-- > 0)
5392 {
5393 ga_concat(&ga_text, prev_char);
5394 append_cell(&ga_cell, &cell);
5395 }
5396 }
5397 }
5398 else
5399 {
5400 dump_is_corrupt(&ga_text);
5401 c = fgetc(fd);
5402 }
5403 }
5404
5405 if (ga_text.ga_len > 0)
5406 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005407 // trailing characters after last NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01005408 dump_is_corrupt(&ga_text);
5409 ga_append(&ga_text, NUL);
5410 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
5411 ga_text.ga_len, FALSE);
5412 }
5413
5414 ga_clear(&ga_text);
Bram Moolenaar86173482019-10-01 17:02:16 +02005415 ga_clear(&ga_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005416 vim_free(prev_char);
5417
5418 return max_cells;
5419}
5420
5421/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02005422 * Return an allocated string with at least "text_width" "=" characters and
5423 * "fname" inserted in the middle.
5424 */
5425 static char_u *
5426get_separator(int text_width, char_u *fname)
5427{
5428 int width = MAX(text_width, curwin->w_width);
5429 char_u *textline;
5430 int fname_size;
5431 char_u *p = fname;
5432 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02005433 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005434
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02005435 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02005436 if (textline == NULL)
5437 return NULL;
5438
5439 fname_size = vim_strsize(fname);
5440 if (fname_size < width - 8)
5441 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005442 // enough room, don't use the full window width
Bram Moolenaar4a696342018-04-05 18:45:26 +02005443 width = MAX(text_width, fname_size + 8);
5444 }
5445 else if (fname_size > width - 8)
5446 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005447 // full name doesn't fit, use only the tail
Bram Moolenaar4a696342018-04-05 18:45:26 +02005448 p = gettail(fname);
5449 fname_size = vim_strsize(p);
5450 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005451 // skip characters until the name fits
Bram Moolenaar4a696342018-04-05 18:45:26 +02005452 while (fname_size > width - 8)
5453 {
5454 p += (*mb_ptr2len)(p);
5455 fname_size = vim_strsize(p);
5456 }
5457
5458 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
5459 textline[i] = '=';
5460 textline[i++] = ' ';
5461
5462 STRCPY(textline + i, p);
5463 off = STRLEN(textline);
5464 textline[off] = ' ';
5465 for (i = 1; i < (width - fname_size) / 2; ++i)
5466 textline[off + i] = '=';
5467 textline[off + i] = NUL;
5468
5469 return textline;
5470}
5471
5472/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01005473 * Common for "term_dumpdiff()" and "term_dumpload()".
5474 */
5475 static void
5476term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
5477{
5478 jobopt_T opt;
Bram Moolenaar87abab92019-06-03 21:14:59 +02005479 buf_T *buf = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005480 char_u buf1[NUMBUFLEN];
5481 char_u buf2[NUMBUFLEN];
5482 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005483 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005484 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005485 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005486 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005487 char_u *textline = NULL;
5488
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005489 // First open the files. If this fails bail out.
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005490 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005491 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005492 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005493 if (fname1 == NULL || (do_diff && fname2 == NULL))
5494 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00005495 emsg(_(e_invalid_argument));
Bram Moolenaard96ff162018-02-18 22:13:29 +01005496 return;
5497 }
5498 fd1 = mch_fopen((char *)fname1, READBIN);
5499 if (fd1 == NULL)
5500 {
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00005501 semsg(_(e_cant_read_file_str), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005502 return;
5503 }
5504 if (do_diff)
5505 {
5506 fd2 = mch_fopen((char *)fname2, READBIN);
5507 if (fd2 == NULL)
5508 {
5509 fclose(fd1);
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00005510 semsg(_(e_cant_read_file_str), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005511 return;
5512 }
5513 }
5514
5515 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005516 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
5517 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
5518 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
5519 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
5520 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005521
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005522 if (opt.jo_term_name == NULL)
5523 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01005524 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005525
Bram Moolenaar51e14382019-05-25 20:21:28 +02005526 fname_tofree = alloc(len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005527 if (fname_tofree != NULL)
5528 {
5529 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
5530 opt.jo_term_name = fname_tofree;
5531 }
5532 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005533
Bram Moolenaar87abab92019-06-03 21:14:59 +02005534 if (opt.jo_bufnr_buf != NULL)
5535 {
5536 win_T *wp = buf_jump_open_win(opt.jo_bufnr_buf);
5537
5538 // With "bufnr" argument: enter the window with this buffer and make it
5539 // empty.
5540 if (wp == NULL)
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00005541 semsg(_(e_invalid_argument_str), "bufnr");
Bram Moolenaar87abab92019-06-03 21:14:59 +02005542 else
5543 {
5544 buf = curbuf;
5545 while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
Bram Moolenaarca70c072020-05-30 20:30:46 +02005546 ml_delete((linenr_T)1);
Bram Moolenaar86173482019-10-01 17:02:16 +02005547 free_scrollback(curbuf->b_term);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01005548 redraw_later(UPD_NOT_VALID);
Bram Moolenaar87abab92019-06-03 21:14:59 +02005549 }
5550 }
5551 else
5552 // Create a new terminal window.
5553 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
5554
Bram Moolenaard96ff162018-02-18 22:13:29 +01005555 if (buf != NULL && buf->b_term != NULL)
5556 {
5557 int i;
5558 linenr_T bot_lnum;
5559 linenr_T lnum;
5560 term_T *term = buf->b_term;
5561 int width;
5562 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005563 VTermPos cursor_pos1;
5564 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005565
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005566 init_default_colors(term);
Bram Moolenaar52acb112018-03-18 19:20:22 +01005567
Bram Moolenaard96ff162018-02-18 22:13:29 +01005568 rettv->vval.v_number = buf->b_fnum;
5569
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005570 // read the files, fill the buffer with the diff
Bram Moolenaar9271d052018-02-25 21:39:46 +01005571 width = read_dump_file(fd1, &cursor_pos1);
5572
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005573 // position the cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01005574 if (cursor_pos1.row >= 0)
5575 {
5576 curwin->w_cursor.lnum = cursor_pos1.row + 1;
5577 coladvance(cursor_pos1.col);
5578 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005579
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005580 // Delete the empty line that was in the empty buffer.
Bram Moolenaarca70c072020-05-30 20:30:46 +02005581 ml_delete(1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005582
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005583 // For term_dumpload() we are done here.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005584 if (!do_diff)
5585 goto theend;
5586
5587 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
5588
Bram Moolenaar4a696342018-04-05 18:45:26 +02005589 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005590 if (textline == NULL)
5591 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005592 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
5593 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
5594 vim_free(textline);
5595
5596 textline = get_separator(width, fname2);
5597 if (textline == NULL)
5598 goto theend;
5599 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
5600 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005601 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005602
5603 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005604 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005605 if (width2 > width)
5606 {
5607 vim_free(textline);
5608 textline = alloc(width2 + 1);
5609 if (textline == NULL)
5610 goto theend;
5611 width = width2;
5612 textline[width] = NUL;
5613 }
5614 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
5615
5616 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
5617 {
5618 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
5619 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005620 // bottom part has fewer rows, fill with "-"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005621 for (i = 0; i < width; ++i)
5622 textline[i] = '-';
5623 }
5624 else
5625 {
5626 char_u *line1;
5627 char_u *line2;
5628 char_u *p1;
5629 char_u *p2;
5630 int col;
5631 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5632 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
5633 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
5634 ->sb_cells;
5635
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005636 // Make a copy, getting the second line will invalidate it.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005637 line1 = vim_strsave(ml_get(lnum));
5638 if (line1 == NULL)
5639 break;
5640 p1 = line1;
5641
5642 line2 = ml_get(lnum + bot_lnum);
5643 p2 = line2;
5644 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
5645 {
5646 int len1 = utfc_ptr2len(p1);
5647 int len2 = utfc_ptr2len(p2);
5648
5649 textline[col] = ' ';
5650 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005651 // text differs
Bram Moolenaard96ff162018-02-18 22:13:29 +01005652 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01005653 else if (lnum == cursor_pos1.row + 1
5654 && col == cursor_pos1.col
5655 && (cursor_pos1.row != cursor_pos2.row
5656 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005657 // cursor in first but not in second
Bram Moolenaar9271d052018-02-25 21:39:46 +01005658 textline[col] = '>';
5659 else if (lnum == cursor_pos2.row + 1
5660 && col == cursor_pos2.col
5661 && (cursor_pos1.row != cursor_pos2.row
5662 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005663 // cursor in second but not in first
Bram Moolenaar9271d052018-02-25 21:39:46 +01005664 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01005665 else if (cellattr1 != NULL && cellattr2 != NULL)
5666 {
5667 if ((cellattr1 + col)->width
5668 != (cellattr2 + col)->width)
5669 textline[col] = 'w';
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005670 else if (!vterm_color_is_equal(&(cellattr1 + col)->fg,
Bram Moolenaard96ff162018-02-18 22:13:29 +01005671 &(cellattr2 + col)->fg))
5672 textline[col] = 'f';
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005673 else if (!vterm_color_is_equal(&(cellattr1 + col)->bg,
Bram Moolenaard96ff162018-02-18 22:13:29 +01005674 &(cellattr2 + col)->bg))
5675 textline[col] = 'b';
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005676 else if (vtermAttr2hl(&(cellattr1 + col)->attrs)
5677 != vtermAttr2hl(&((cellattr2 + col)->attrs)))
Bram Moolenaard96ff162018-02-18 22:13:29 +01005678 textline[col] = 'a';
5679 }
5680 p1 += len1;
5681 p2 += len2;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005682 // TODO: handle different width
Bram Moolenaard96ff162018-02-18 22:13:29 +01005683 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005684
5685 while (col < width)
5686 {
5687 if (*p1 == NUL && *p2 == NUL)
5688 textline[col] = '?';
5689 else if (*p1 == NUL)
5690 {
5691 textline[col] = '+';
5692 p2 += utfc_ptr2len(p2);
5693 }
5694 else
5695 {
5696 textline[col] = '-';
5697 p1 += utfc_ptr2len(p1);
5698 }
5699 ++col;
5700 }
Bram Moolenaar81aa0f52019-02-14 23:23:19 +01005701
5702 vim_free(line1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005703 }
5704 if (add_empty_scrollback(term, &term->tl_default_color,
5705 term->tl_top_diff_rows) == OK)
5706 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5707 ++bot_lnum;
5708 }
5709
5710 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
5711 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005712 // bottom part has more rows, fill with "+"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005713 for (i = 0; i < width; ++i)
5714 textline[i] = '+';
5715 if (add_empty_scrollback(term, &term->tl_default_color,
5716 term->tl_top_diff_rows) == OK)
5717 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5718 ++lnum;
5719 ++bot_lnum;
5720 }
5721
5722 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005723
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005724 // looks better without wrapping
Bram Moolenaar4a696342018-04-05 18:45:26 +02005725 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005726 }
5727
5728theend:
5729 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005730 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005731 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005732 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005733 fclose(fd2);
5734}
5735
5736/*
5737 * If the current buffer shows the output of term_dumpdiff(), swap the top and
5738 * bottom files.
5739 * Return FAIL when this is not possible.
5740 */
5741 int
5742term_swap_diff()
5743{
5744 term_T *term = curbuf->b_term;
5745 linenr_T line_count;
5746 linenr_T top_rows;
5747 linenr_T bot_rows;
5748 linenr_T bot_start;
5749 linenr_T lnum;
5750 char_u *p;
5751 sb_line_T *sb_line;
5752
5753 if (term == NULL
5754 || !term_is_finished(curbuf)
5755 || term->tl_top_diff_rows == 0
5756 || term->tl_scrollback.ga_len == 0)
5757 return FAIL;
5758
5759 line_count = curbuf->b_ml.ml_line_count;
5760 top_rows = term->tl_top_diff_rows;
5761 bot_rows = term->tl_bot_diff_rows;
5762 bot_start = line_count - bot_rows;
5763 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5764
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005765 // move lines from top to above the bottom part
Bram Moolenaard96ff162018-02-18 22:13:29 +01005766 for (lnum = 1; lnum <= top_rows; ++lnum)
5767 {
5768 p = vim_strsave(ml_get(1));
5769 if (p == NULL)
5770 return OK;
5771 ml_append(bot_start, p, 0, FALSE);
Bram Moolenaarca70c072020-05-30 20:30:46 +02005772 ml_delete(1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005773 vim_free(p);
5774 }
5775
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005776 // move lines from bottom to the top
Bram Moolenaard96ff162018-02-18 22:13:29 +01005777 for (lnum = 1; lnum <= bot_rows; ++lnum)
5778 {
5779 p = vim_strsave(ml_get(bot_start + lnum));
5780 if (p == NULL)
5781 return OK;
Bram Moolenaarca70c072020-05-30 20:30:46 +02005782 ml_delete(bot_start + lnum);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005783 ml_append(lnum - 1, p, 0, FALSE);
5784 vim_free(p);
5785 }
5786
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005787 // move top title to bottom
5788 p = vim_strsave(ml_get(bot_rows + 1));
5789 if (p == NULL)
5790 return OK;
5791 ml_append(line_count - top_rows - 1, p, 0, FALSE);
Bram Moolenaarca70c072020-05-30 20:30:46 +02005792 ml_delete(bot_rows + 1);
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005793 vim_free(p);
5794
5795 // move bottom title to top
5796 p = vim_strsave(ml_get(line_count - top_rows));
5797 if (p == NULL)
5798 return OK;
Bram Moolenaarca70c072020-05-30 20:30:46 +02005799 ml_delete(line_count - top_rows);
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005800 ml_append(bot_rows, p, 0, FALSE);
5801 vim_free(p);
5802
Bram Moolenaard96ff162018-02-18 22:13:29 +01005803 if (top_rows == bot_rows)
5804 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005805 // rows counts are equal, can swap cell properties
Bram Moolenaard96ff162018-02-18 22:13:29 +01005806 for (lnum = 0; lnum < top_rows; ++lnum)
5807 {
5808 sb_line_T temp;
5809
5810 temp = *(sb_line + lnum);
5811 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
5812 *(sb_line + bot_start + lnum) = temp;
5813 }
5814 }
5815 else
5816 {
5817 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005818 sb_line_T *temp = alloc(size);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005819
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005820 // need to copy cell properties into temp memory
Bram Moolenaard96ff162018-02-18 22:13:29 +01005821 if (temp != NULL)
5822 {
5823 mch_memmove(temp, term->tl_scrollback.ga_data, size);
5824 mch_memmove(term->tl_scrollback.ga_data,
5825 temp + bot_start,
5826 sizeof(sb_line_T) * bot_rows);
5827 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
5828 temp + top_rows,
5829 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
5830 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
5831 + line_count - top_rows,
5832 temp,
5833 sizeof(sb_line_T) * top_rows);
5834 vim_free(temp);
5835 }
5836 }
5837
5838 term->tl_top_diff_rows = bot_rows;
5839 term->tl_bot_diff_rows = top_rows;
5840
Bram Moolenaara4d158b2022-08-14 14:17:45 +01005841 update_screen(UPD_NOT_VALID);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005842 return OK;
5843}
5844
5845/*
5846 * "term_dumpdiff(filename, filename, options)" function
5847 */
5848 void
5849f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
5850{
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02005851 if (in_vim9script()
5852 && (check_for_string_arg(argvars, 0) == FAIL
5853 || check_for_string_arg(argvars, 1) == FAIL
5854 || check_for_opt_dict_arg(argvars, 2) == FAIL))
5855 return;
5856
Bram Moolenaard96ff162018-02-18 22:13:29 +01005857 term_load_dump(argvars, rettv, TRUE);
5858}
5859
5860/*
5861 * "term_dumpload(filename, options)" function
5862 */
5863 void
5864f_term_dumpload(typval_T *argvars, typval_T *rettv)
5865{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005866 if (in_vim9script()
5867 && (check_for_string_arg(argvars, 0) == FAIL
Yegappan Lakshmananfc3b7752021-09-08 14:57:42 +02005868 || check_for_opt_dict_arg(argvars, 1) == FAIL))
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005869 return;
5870
Bram Moolenaard96ff162018-02-18 22:13:29 +01005871 term_load_dump(argvars, rettv, FALSE);
5872}
5873
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005874/*
5875 * "term_getaltscreen(buf)" function
5876 */
5877 void
5878f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
5879{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005880 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005881
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005882 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
5883 return;
5884
5885 buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005886 if (buf == NULL)
5887 return;
5888 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
5889}
5890
5891/*
5892 * "term_getattr(attr, name)" function
5893 */
5894 void
5895f_term_getattr(typval_T *argvars, typval_T *rettv)
5896{
5897 int attr;
5898 size_t i;
5899 char_u *name;
5900
5901 static struct {
5902 char *name;
5903 int attr;
5904 } attrs[] = {
5905 {"bold", HL_BOLD},
5906 {"italic", HL_ITALIC},
5907 {"underline", HL_UNDERLINE},
5908 {"strike", HL_STRIKETHROUGH},
5909 {"reverse", HL_INVERSE},
5910 };
5911
Yegappan Lakshmanan1a71d312021-07-15 12:49:58 +02005912 if (in_vim9script()
5913 && (check_for_number_arg(argvars, 0) == FAIL
5914 || check_for_string_arg(argvars, 1) == FAIL))
5915 return;
5916
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005917 attr = tv_get_number(&argvars[0]);
5918 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005919 if (name == NULL)
5920 return;
5921
Bram Moolenaar7ee80f72019-09-08 20:55:06 +02005922 if (attr > HL_ALL)
5923 attr = syn_attr2attr(attr);
K.Takataeeec2542021-06-02 13:28:16 +02005924 for (i = 0; i < ARRAY_LENGTH(attrs); ++i)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005925 if (STRCMP(name, attrs[i].name) == 0)
5926 {
5927 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
5928 break;
5929 }
5930}
5931
5932/*
5933 * "term_getcursor(buf)" function
5934 */
5935 void
5936f_term_getcursor(typval_T *argvars, typval_T *rettv)
5937{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005938 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005939 term_T *term;
5940 list_T *l;
5941 dict_T *d;
5942
5943 if (rettv_list_alloc(rettv) == FAIL)
5944 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005945
5946 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
5947 return;
5948
5949 buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005950 if (buf == NULL)
5951 return;
5952 term = buf->b_term;
5953
5954 l = rettv->vval.v_list;
5955 list_append_number(l, term->tl_cursor_pos.row + 1);
5956 list_append_number(l, term->tl_cursor_pos.col + 1);
5957
5958 d = dict_alloc();
5959 if (d != NULL)
5960 {
Bram Moolenaare0be1672018-07-08 16:50:37 +02005961 dict_add_number(d, "visible", term->tl_cursor_visible);
5962 dict_add_number(d, "blink", blink_state_is_inverted()
5963 ? !term->tl_cursor_blink : term->tl_cursor_blink);
5964 dict_add_number(d, "shape", term->tl_cursor_shape);
5965 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005966 list_append_dict(l, d);
5967 }
5968}
5969
5970/*
5971 * "term_getjob(buf)" function
5972 */
5973 void
5974f_term_getjob(typval_T *argvars, typval_T *rettv)
5975{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005976 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005977
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005978 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
5979 return;
5980
5981 buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005982 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005983 {
5984 rettv->v_type = VAR_SPECIAL;
5985 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005986 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005987 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005988
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01005989 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005990 rettv->vval.v_job = buf->b_term->tl_job;
5991 if (rettv->vval.v_job != NULL)
5992 ++rettv->vval.v_job->jv_refcount;
5993}
5994
5995 static int
5996get_row_number(typval_T *tv, term_T *term)
5997{
5998 if (tv->v_type == VAR_STRING
5999 && tv->vval.v_string != NULL
6000 && STRCMP(tv->vval.v_string, ".") == 0)
6001 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006002 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006003}
6004
6005/*
6006 * "term_getline(buf, row)" function
6007 */
6008 void
6009f_term_getline(typval_T *argvars, typval_T *rettv)
6010{
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006011 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006012 term_T *term;
6013 int row;
6014
6015 rettv->v_type = VAR_STRING;
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006016
6017 if (in_vim9script()
6018 && (check_for_buffer_arg(argvars, 0) == FAIL
6019 || check_for_lnum_arg(argvars, 1) == FAIL))
6020 return;
6021
6022 buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006023 if (buf == NULL)
6024 return;
6025 term = buf->b_term;
6026 row = get_row_number(&argvars[1], term);
6027
6028 if (term->tl_vterm == NULL)
6029 {
6030 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
6031
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006032 // vterm is finished, get the text from the buffer
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006033 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
6034 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
6035 }
6036 else
6037 {
6038 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
6039 VTermRect rect;
6040 int len;
6041 char_u *p;
6042
6043 if (row < 0 || row >= term->tl_rows)
6044 return;
6045 len = term->tl_cols * MB_MAXBYTES + 1;
6046 p = alloc(len);
6047 if (p == NULL)
6048 return;
6049 rettv->vval.v_string = p;
6050
6051 rect.start_col = 0;
6052 rect.end_col = term->tl_cols;
6053 rect.start_row = row;
6054 rect.end_row = row + 1;
6055 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
6056 }
6057}
6058
6059/*
6060 * "term_getscrolled(buf)" function
6061 */
6062 void
6063f_term_getscrolled(typval_T *argvars, typval_T *rettv)
6064{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006065 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006066
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006067 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6068 return;
6069
6070 buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006071 if (buf == NULL)
6072 return;
6073 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
6074}
6075
6076/*
6077 * "term_getsize(buf)" function
6078 */
6079 void
6080f_term_getsize(typval_T *argvars, typval_T *rettv)
6081{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006082 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006083 list_T *l;
6084
6085 if (rettv_list_alloc(rettv) == FAIL)
6086 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006087
6088 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6089 return;
6090
6091 buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006092 if (buf == NULL)
6093 return;
6094
6095 l = rettv->vval.v_list;
6096 list_append_number(l, buf->b_term->tl_rows);
6097 list_append_number(l, buf->b_term->tl_cols);
6098}
6099
6100/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006101 * "term_setsize(buf, rows, cols)" function
6102 */
6103 void
6104f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
6105{
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006106 buf_T *buf;
Bram Moolenaara42d3632018-04-14 17:05:38 +02006107 term_T *term;
6108 varnumber_T rows, cols;
6109
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006110 if (in_vim9script()
6111 && (check_for_buffer_arg(argvars, 0) == FAIL
6112 || check_for_number_arg(argvars, 1) == FAIL
6113 || check_for_number_arg(argvars, 2) == FAIL))
6114 return;
6115
6116 buf = term_get_buf(argvars, "term_setsize()");
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02006117 if (buf == NULL)
6118 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00006119 emsg(_(e_not_terminal_buffer));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02006120 return;
6121 }
6122 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02006123 return;
6124 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006125 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02006126 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006127 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02006128 cols = cols <= 0 ? term->tl_cols : cols;
6129 vterm_set_size(term->tl_vterm, rows, cols);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006130 // handle_resize() will resize the windows
Bram Moolenaara42d3632018-04-14 17:05:38 +02006131
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006132 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaara42d3632018-04-14 17:05:38 +02006133 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
6134 term_report_winsize(term, term->tl_rows, term->tl_cols);
6135}
6136
6137/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006138 * "term_getstatus(buf)" function
6139 */
6140 void
6141f_term_getstatus(typval_T *argvars, typval_T *rettv)
6142{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006143 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006144 term_T *term;
6145 char_u val[100];
6146
6147 rettv->v_type = VAR_STRING;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006148
6149 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6150 return;
6151
6152 buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006153 if (buf == NULL)
6154 return;
6155 term = buf->b_term;
6156
6157 if (term_job_running(term))
6158 STRCPY(val, "running");
6159 else
6160 STRCPY(val, "finished");
6161 if (term->tl_normal_mode)
6162 STRCAT(val, ",normal");
6163 rettv->vval.v_string = vim_strsave(val);
6164}
6165
6166/*
6167 * "term_gettitle(buf)" function
6168 */
6169 void
6170f_term_gettitle(typval_T *argvars, typval_T *rettv)
6171{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006172 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006173
6174 rettv->v_type = VAR_STRING;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006175
6176 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6177 return;
6178
6179 buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006180 if (buf == NULL)
6181 return;
6182
6183 if (buf->b_term->tl_title != NULL)
6184 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
6185}
6186
6187/*
6188 * "term_gettty(buf)" function
6189 */
6190 void
6191f_term_gettty(typval_T *argvars, typval_T *rettv)
6192{
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006193 buf_T *buf;
Bram Moolenaar9b50f362018-05-07 20:10:17 +02006194 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006195 int num = 0;
6196
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006197 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006198 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006199 || check_for_opt_bool_arg(argvars, 1) == FAIL))
6200 return;
6201
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006202 rettv->v_type = VAR_STRING;
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006203 buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006204 if (buf == NULL)
6205 return;
6206 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaarad304702020-09-06 18:22:53 +02006207 num = tv_get_bool(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006208
6209 switch (num)
6210 {
6211 case 0:
6212 if (buf->b_term->tl_job != NULL)
6213 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006214 break;
6215 case 1:
6216 if (buf->b_term->tl_job != NULL)
6217 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006218 break;
6219 default:
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00006220 semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006221 return;
6222 }
6223 if (p != NULL)
6224 rettv->vval.v_string = vim_strsave(p);
6225}
6226
6227/*
6228 * "term_list()" function
6229 */
6230 void
6231f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
6232{
6233 term_T *tp;
6234 list_T *l;
6235
6236 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
6237 return;
6238
6239 l = rettv->vval.v_list;
Bram Moolenaaraeea7212020-04-02 18:50:46 +02006240 FOR_ALL_TERMS(tp)
Bram Moolenaarad431992021-05-03 20:40:38 +02006241 if (tp->tl_buffer != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006242 if (list_append_number(l,
6243 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
6244 return;
6245}
6246
6247/*
6248 * "term_scrape(buf, row)" function
6249 */
6250 void
6251f_term_scrape(typval_T *argvars, typval_T *rettv)
6252{
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006253 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006254 VTermScreen *screen = NULL;
6255 VTermPos pos;
6256 list_T *l;
6257 term_T *term;
6258 char_u *p;
6259 sb_line_T *line;
6260
6261 if (rettv_list_alloc(rettv) == FAIL)
6262 return;
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006263
6264 if (in_vim9script()
6265 && (check_for_buffer_arg(argvars, 0) == FAIL
6266 || check_for_lnum_arg(argvars, 1) == FAIL))
6267 return;
6268
6269 buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006270 if (buf == NULL)
6271 return;
6272 term = buf->b_term;
6273
6274 l = rettv->vval.v_list;
6275 pos.row = get_row_number(&argvars[1], term);
6276
6277 if (term->tl_vterm != NULL)
6278 {
6279 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01006280 if (screen == NULL) // can't really happen
6281 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006282 p = NULL;
6283 line = NULL;
6284 }
6285 else
6286 {
6287 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
6288
6289 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
6290 return;
6291 p = ml_get_buf(buf, lnum + 1, FALSE);
6292 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
6293 }
6294
6295 for (pos.col = 0; pos.col < term->tl_cols; )
6296 {
6297 dict_T *dcell;
6298 int width;
6299 VTermScreenCellAttrs attrs;
6300 VTermColor fg, bg;
6301 char_u rgb[8];
6302 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
6303 int off = 0;
6304 int i;
6305
6306 if (screen == NULL)
6307 {
6308 cellattr_T *cellattr;
6309 int len;
6310
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006311 // vterm has finished, get the cell from scrollback
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006312 if (pos.col >= line->sb_cols)
6313 break;
6314 cellattr = line->sb_cells + pos.col;
6315 width = cellattr->width;
6316 attrs = cellattr->attrs;
6317 fg = cellattr->fg;
6318 bg = cellattr->bg;
Bram Moolenaar1614a142019-10-06 22:00:13 +02006319 len = mb_ptr2len(p);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006320 mch_memmove(mbs, p, len);
6321 mbs[len] = NUL;
6322 p += len;
6323 }
6324 else
6325 {
6326 VTermScreenCell cell;
Bram Moolenaare5886cc2020-05-21 20:10:04 +02006327
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006328 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
6329 break;
6330 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
6331 {
6332 if (cell.chars[i] == 0)
6333 break;
6334 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
6335 }
6336 mbs[off] = NUL;
6337 width = cell.width;
6338 attrs = cell.attrs;
6339 fg = cell.fg;
6340 bg = cell.bg;
6341 }
6342 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01006343 if (dcell == NULL)
6344 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006345 list_append_dict(l, dcell);
6346
Bram Moolenaare0be1672018-07-08 16:50:37 +02006347 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006348
6349 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
6350 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02006351 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006352 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
6353 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02006354 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006355
Bram Moolenaar87fd0922021-11-20 13:47:45 +00006356 dict_add_number(dcell, "attr",
6357 cell2attr(term, NULL, &attrs, &fg, &bg));
Bram Moolenaare0be1672018-07-08 16:50:37 +02006358 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006359
6360 ++pos.col;
6361 if (width == 2)
6362 ++pos.col;
6363 }
6364}
6365
6366/*
6367 * "term_sendkeys(buf, keys)" function
6368 */
6369 void
Bram Moolenaar3a05ce62020-03-11 19:30:01 +01006370f_term_sendkeys(typval_T *argvars, typval_T *rettv UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006371{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006372 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006373 char_u *msg;
6374 term_T *term;
6375
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006376 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006377 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006378 || check_for_string_arg(argvars, 1) == FAIL))
6379 return;
6380
6381 buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006382 if (buf == NULL)
6383 return;
6384
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006385 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006386 if (msg == NULL)
6387 return;
6388 term = buf->b_term;
6389 if (term->tl_vterm == NULL)
6390 return;
6391
6392 while (*msg != NUL)
6393 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02006394 int c;
6395
6396 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
6397 {
6398 c = TO_SPECIAL(msg[1], msg[2]);
6399 msg += 3;
6400 }
6401 else
6402 {
6403 c = PTR2CHAR(msg);
6404 msg += MB_CPTR2LEN(msg);
6405 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01006406 send_keys_to_term(term, c, 0, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006407 }
6408}
6409
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006410#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
6411/*
6412 * "term_getansicolors(buf)" function
6413 */
6414 void
6415f_term_getansicolors(typval_T *argvars, typval_T *rettv)
6416{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006417 buf_T *buf;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006418 term_T *term;
6419 VTermState *state;
6420 VTermColor color;
6421 char_u hexbuf[10];
6422 int index;
6423 list_T *list;
6424
6425 if (rettv_list_alloc(rettv) == FAIL)
6426 return;
6427
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006428 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6429 return;
6430
6431 buf = term_get_buf(argvars, "term_getansicolors()");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006432 if (buf == NULL)
6433 return;
6434 term = buf->b_term;
6435 if (term->tl_vterm == NULL)
6436 return;
6437
6438 list = rettv->vval.v_list;
6439 state = vterm_obtain_state(term->tl_vterm);
6440 for (index = 0; index < 16; index++)
6441 {
6442 vterm_state_get_palette_color(state, index, &color);
6443 sprintf((char *)hexbuf, "#%02x%02x%02x",
6444 color.red, color.green, color.blue);
6445 if (list_append_string(list, hexbuf, 7) == FAIL)
6446 return;
6447 }
6448}
6449
6450/*
6451 * "term_setansicolors(buf, list)" function
6452 */
6453 void
6454f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
6455{
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006456 buf_T *buf;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006457 term_T *term;
LemonBoyb2b3acb2022-05-20 10:10:34 +01006458 listitem_T *li;
6459 int n = 0;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006460
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006461 if (in_vim9script()
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006462 && (check_for_buffer_arg(argvars, 0) == FAIL
6463 || check_for_list_arg(argvars, 1) == FAIL))
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006464 return;
6465
6466 buf = term_get_buf(argvars, "term_setansicolors()");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006467 if (buf == NULL)
6468 return;
6469 term = buf->b_term;
6470 if (term->tl_vterm == NULL)
6471 return;
6472
Bram Moolenaard83392a2022-09-01 12:22:46 +01006473 if (check_for_nonnull_list_arg(argvars, 1) == FAIL)
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006474 return;
Bram Moolenaard83392a2022-09-01 12:22:46 +01006475
LemonBoyb2b3acb2022-05-20 10:10:34 +01006476 if (argvars[1].vval.v_list->lv_first == &range_list_item
6477 || argvars[1].vval.v_list->lv_len != 16)
6478 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00006479 emsg(_(e_invalid_argument));
LemonBoyb2b3acb2022-05-20 10:10:34 +01006480 return;
6481 }
6482
6483 if (term->tl_palette == NULL)
6484 term->tl_palette = ALLOC_MULT(long_u, 16);
6485 if (term->tl_palette == NULL)
6486 return;
6487
6488 FOR_ALL_LIST_ITEMS(argvars[1].vval.v_list, li)
6489 {
6490 char_u *color_name;
6491 guicolor_T guicolor;
6492
6493 color_name = tv_get_string_chk(&li->li_tv);
6494 if (color_name == NULL)
6495 return;
6496
6497 guicolor = GUI_GET_COLOR(color_name);
6498 if (guicolor == INVALCOLOR)
6499 {
6500 semsg(_(e_cannot_allocate_color_str), color_name);
6501 return;
6502 }
6503
6504 term->tl_palette[n++] = GUI_MCH_GET_RGB(guicolor);
6505 }
6506
6507 term_update_palette(term);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006508}
6509#endif
6510
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006511/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006512 * "term_setapi(buf, api)" function
6513 */
6514 void
6515f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
6516{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006517 buf_T *buf;
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006518 term_T *term;
6519 char_u *api;
6520
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006521 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006522 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006523 || check_for_string_arg(argvars, 1) == FAIL))
6524 return;
6525
6526 buf = term_get_buf(argvars, "term_setapi()");
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006527 if (buf == NULL)
6528 return;
6529 term = buf->b_term;
6530 vim_free(term->tl_api);
6531 api = tv_get_string_chk(&argvars[1]);
6532 if (api != NULL)
6533 term->tl_api = vim_strsave(api);
6534 else
6535 term->tl_api = NULL;
6536}
6537
6538/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006539 * "term_setrestore(buf, command)" function
6540 */
6541 void
6542f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
6543{
6544#if defined(FEAT_SESSION)
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006545 buf_T *buf;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006546 term_T *term;
6547 char_u *cmd;
6548
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006549 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006550 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006551 || check_for_string_arg(argvars, 1) == FAIL))
6552 return;
6553
6554 buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006555 if (buf == NULL)
6556 return;
6557 term = buf->b_term;
6558 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006559 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006560 if (cmd != NULL)
6561 term->tl_command = vim_strsave(cmd);
6562 else
6563 term->tl_command = NULL;
6564#endif
6565}
6566
6567/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006568 * "term_setkill(buf, how)" function
6569 */
6570 void
6571f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
6572{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006573 buf_T *buf;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006574 term_T *term;
6575 char_u *how;
6576
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006577 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006578 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006579 || check_for_string_arg(argvars, 1) == FAIL))
6580 return;
6581
6582 buf = term_get_buf(argvars, "term_setkill()");
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006583 if (buf == NULL)
6584 return;
6585 term = buf->b_term;
6586 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006587 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006588 if (how != NULL)
6589 term->tl_kill = vim_strsave(how);
6590 else
6591 term->tl_kill = NULL;
6592}
6593
6594/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006595 * "term_start(command, options)" function
6596 */
6597 void
6598f_term_start(typval_T *argvars, typval_T *rettv)
6599{
6600 jobopt_T opt;
6601 buf_T *buf;
6602
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006603 if (in_vim9script()
6604 && (check_for_string_or_list_arg(argvars, 0) == FAIL
6605 || check_for_opt_dict_arg(argvars, 1) == FAIL))
6606 return;
6607
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006608 init_job_options(&opt);
6609 if (argvars[1].v_type != VAR_UNKNOWN
6610 && get_job_options(&argvars[1], &opt,
6611 JO_TIMEOUT_ALL + JO_STOPONEXIT
6612 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
6613 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
6614 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
6615 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006616 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar83d47902020-03-26 20:34:00 +01006617 + JO2_NORESTORE + JO2_TERM_KILL + JO2_TERM_HIGHLIGHT
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006618 + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006619 return;
6620
Bram Moolenaar13568252018-03-16 20:46:58 +01006621 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006622
6623 if (buf != NULL && buf->b_term != NULL)
6624 rettv->vval.v_number = buf->b_fnum;
6625}
6626
6627/*
6628 * "term_wait" function
6629 */
6630 void
6631f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
6632{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006633 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006634
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006635 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006636 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006637 || check_for_opt_number_arg(argvars, 1) == FAIL))
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006638 return;
6639
6640 buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006641 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006642 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006643 if (buf->b_term->tl_job == NULL)
6644 {
6645 ch_log(NULL, "term_wait(): no job to wait for");
6646 return;
6647 }
6648 if (buf->b_term->tl_job->jv_channel == NULL)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006649 // channel is closed, nothing to do
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006650 return;
6651
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006652 // Get the job status, this will detect a job that finished.
Bram Moolenaara15ef452018-02-09 16:46:00 +01006653 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006654 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
6655 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006656 // The job is dead, keep reading channel I/O until the channel is
6657 // closed. buf->b_term may become NULL if the terminal was closed while
6658 // waiting.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006659 ch_log(NULL, "term_wait(): waiting for channel to close");
6660 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
6661 {
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006662 term_flush_messages();
6663
Bram Moolenaard45aa552018-05-21 22:50:29 +02006664 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01006665 if (!buf_valid(buf))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006666 // If the terminal is closed when the channel is closed the
6667 // buffer disappears.
Bram Moolenaare5182262017-11-19 15:05:44 +01006668 break;
Bram Moolenaareea32af2021-11-21 14:51:13 +00006669 if (buf->b_term == NULL || buf->b_term->tl_channel_closing)
6670 // came here from a close callback, only wait one time
6671 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006672 }
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006673
6674 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006675 }
6676 else
6677 {
6678 long wait = 10L;
6679
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006680 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006681
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006682 // Wait for some time for any channel I/O.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006683 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006684 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006685 ui_delay(wait, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006686
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006687 // Flushing messages on channels is hopefully sufficient.
6688 // TODO: is there a better way?
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006689 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006690 }
6691}
6692
6693/*
6694 * Called when a channel has sent all the lines to a terminal.
6695 * Send a CTRL-D to mark the end of the text.
6696 */
6697 void
6698term_send_eof(channel_T *ch)
6699{
6700 term_T *term;
6701
Bram Moolenaaraeea7212020-04-02 18:50:46 +02006702 FOR_ALL_TERMS(term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006703 if (term->tl_job == ch->ch_job)
6704 {
6705 if (term->tl_eof_chars != NULL)
6706 {
6707 channel_send(ch, PART_IN, term->tl_eof_chars,
6708 (int)STRLEN(term->tl_eof_chars), NULL);
6709 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
6710 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01006711# ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006712 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006713 // Default: CTRL-D
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006714 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
6715# endif
6716 }
6717}
6718
Bram Moolenaar113e1072019-01-20 15:30:40 +01006719#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02006720 job_T *
6721term_getjob(term_T *term)
6722{
6723 return term != NULL ? term->tl_job : NULL;
6724}
Bram Moolenaar113e1072019-01-20 15:30:40 +01006725#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02006726
Bram Moolenaar4f974752019-02-17 17:44:42 +01006727# if defined(MSWIN) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006728
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006729///////////////////////////////////////
6730// 2. MS-Windows implementation.
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006731#ifdef PROTO
6732typedef int COORD;
6733typedef int DWORD;
6734typedef int HANDLE;
6735typedef int *DWORD_PTR;
6736typedef int HPCON;
6737typedef int HRESULT;
6738typedef int LPPROC_THREAD_ATTRIBUTE_LIST;
Bram Moolenaarad3ec762019-04-21 00:00:13 +02006739typedef int SIZE_T;
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006740typedef int PSIZE_T;
6741typedef int PVOID;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01006742typedef int BOOL;
6743# define WINAPI
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006744#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006745
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006746HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
6747HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
6748HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
Bram Moolenaar48773f12019-02-12 21:46:46 +01006749BOOL (WINAPI *pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
6750BOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
6751void (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006752
6753 static int
6754dyn_conpty_init(int verbose)
6755{
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006756 static HMODULE hKerneldll = NULL;
6757 int i;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006758 static struct
6759 {
6760 char *name;
6761 FARPROC *ptr;
6762 } conpty_entry[] =
6763 {
6764 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
6765 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
6766 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
6767 {"InitializeProcThreadAttributeList",
6768 (FARPROC*)&pInitializeProcThreadAttributeList},
6769 {"UpdateProcThreadAttribute",
6770 (FARPROC*)&pUpdateProcThreadAttribute},
6771 {"DeleteProcThreadAttributeList",
6772 (FARPROC*)&pDeleteProcThreadAttributeList},
6773 {NULL, NULL}
6774 };
6775
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01006776 if (!has_conpty_working())
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006777 {
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006778 if (verbose)
Bram Moolenaard82a47d2022-01-05 20:24:39 +00006779 emsg(_(e_conpty_is_not_available));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006780 return FAIL;
6781 }
6782
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006783 // No need to initialize twice.
6784 if (hKerneldll)
6785 return OK;
6786
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006787 hKerneldll = vimLoadLib("kernel32.dll");
6788 for (i = 0; conpty_entry[i].name != NULL
6789 && conpty_entry[i].ptr != NULL; ++i)
6790 {
6791 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
6792 conpty_entry[i].name)) == NULL)
6793 {
6794 if (verbose)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00006795 semsg(_(e_could_not_load_library_function_str), conpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006796 hKerneldll = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006797 return FAIL;
6798 }
6799 }
6800
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006801 return OK;
6802}
6803
6804 static int
6805conpty_term_and_job_init(
6806 term_T *term,
6807 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02006808 char **argv UNUSED,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006809 jobopt_T *opt,
6810 jobopt_T *orig_opt)
6811{
6812 WCHAR *cmd_wchar = NULL;
6813 WCHAR *cmd_wchar_copy = NULL;
6814 WCHAR *cwd_wchar = NULL;
6815 WCHAR *env_wchar = NULL;
6816 channel_T *channel = NULL;
6817 job_T *job = NULL;
6818 HANDLE jo = NULL;
6819 garray_T ga_cmd, ga_env;
6820 char_u *cmd = NULL;
6821 HRESULT hr;
6822 COORD consize;
6823 SIZE_T breq;
6824 PROCESS_INFORMATION proc_info;
6825 HANDLE i_theirs = NULL;
6826 HANDLE o_theirs = NULL;
6827 HANDLE i_ours = NULL;
6828 HANDLE o_ours = NULL;
6829
Bram Moolenaar04935fb2022-01-08 16:19:22 +00006830 ga_init2(&ga_cmd, sizeof(char*), 20);
6831 ga_init2(&ga_env, sizeof(char*), 20);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006832
6833 if (argvar->v_type == VAR_STRING)
6834 {
6835 cmd = argvar->vval.v_string;
6836 }
6837 else if (argvar->v_type == VAR_LIST)
6838 {
6839 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
6840 goto failed;
6841 cmd = ga_cmd.ga_data;
6842 }
6843 if (cmd == NULL || *cmd == NUL)
6844 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00006845 emsg(_(e_invalid_argument));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006846 goto failed;
6847 }
6848
6849 term->tl_arg0_cmd = vim_strsave(cmd);
6850
6851 cmd_wchar = enc_to_utf16(cmd, NULL);
6852
6853 if (cmd_wchar != NULL)
6854 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006855 // Request by CreateProcessW
6856 breq = wcslen(cmd_wchar) + 1 + 1; // Addition of NUL by API
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006857 cmd_wchar_copy = ALLOC_MULT(WCHAR, breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006858 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
6859 }
6860
6861 ga_clear(&ga_cmd);
6862 if (cmd_wchar == NULL)
6863 goto failed;
6864 if (opt->jo_cwd != NULL)
6865 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
6866
6867 win32_build_env(opt->jo_env, &ga_env, TRUE);
6868 env_wchar = ga_env.ga_data;
6869
6870 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
6871 goto failed;
6872 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
6873 goto failed;
6874
6875 consize.X = term->tl_cols;
6876 consize.Y = term->tl_rows;
6877 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
6878 &term->tl_conpty);
6879 if (FAILED(hr))
6880 goto failed;
6881
6882 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
6883
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006884 // Set up pipe inheritance safely: Vista or later.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006885 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006886 term->tl_siex.lpAttributeList = alloc(breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006887 if (!term->tl_siex.lpAttributeList)
6888 goto failed;
6889 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
6890 0, &breq))
6891 goto failed;
6892 if (!pUpdateProcThreadAttribute(
6893 term->tl_siex.lpAttributeList, 0,
6894 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
6895 sizeof(HPCON), NULL, NULL))
6896 goto failed;
6897
6898 channel = add_channel();
6899 if (channel == NULL)
6900 goto failed;
6901
6902 job = job_alloc();
6903 if (job == NULL)
6904 goto failed;
6905 if (argvar->v_type == VAR_STRING)
6906 {
6907 int argc;
6908
6909 build_argv_from_string(cmd, &job->jv_argv, &argc);
6910 }
6911 else
6912 {
6913 int argc;
6914
6915 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
6916 }
6917
6918 if (opt->jo_set & JO_IN_BUF)
6919 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
6920
6921 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
6922 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
Bram Moolenaar07b761a2020-04-26 16:06:01 +02006923 | CREATE_SUSPENDED | CREATE_DEFAULT_ERROR_MODE,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006924 env_wchar, cwd_wchar,
6925 &term->tl_siex.StartupInfo, &proc_info))
6926 goto failed;
6927
6928 CloseHandle(i_theirs);
6929 CloseHandle(o_theirs);
6930
6931 channel_set_pipes(channel,
6932 (sock_T)i_ours,
6933 (sock_T)o_ours,
6934 (sock_T)o_ours);
6935
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006936 // Write lines with CR instead of NL.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006937 channel->ch_write_text_mode = TRUE;
6938
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006939 // Use to explicitly delete anonymous pipe handle.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006940 channel->ch_anonymous_pipe = TRUE;
6941
6942 jo = CreateJobObject(NULL, NULL);
6943 if (jo == NULL)
6944 goto failed;
6945
6946 if (!AssignProcessToJobObject(jo, proc_info.hProcess))
6947 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006948 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006949 CloseHandle(jo);
6950 jo = NULL;
6951 }
6952
6953 ResumeThread(proc_info.hThread);
6954 CloseHandle(proc_info.hThread);
6955
6956 vim_free(cmd_wchar);
6957 vim_free(cmd_wchar_copy);
6958 vim_free(cwd_wchar);
6959 vim_free(env_wchar);
6960
6961 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
6962 goto failed;
6963
Bram Moolenaar30b9a412022-05-26 14:06:37 +01006964#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01006965 if (term_use_palette())
6966 {
6967 if (term->tl_palette != NULL)
6968 set_vterm_palette(term->tl_vterm, term->tl_palette);
6969 else
6970 init_vterm_ansi_colors(term->tl_vterm);
6971 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +01006972#endif
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006973
6974 channel_set_job(channel, job, opt);
6975 job_set_options(job, opt);
6976
6977 job->jv_channel = channel;
6978 job->jv_proc_info = proc_info;
6979 job->jv_job_object = jo;
6980 job->jv_status = JOB_STARTED;
Bram Moolenaar18442cb2019-02-13 21:22:12 +01006981 job->jv_tty_type = vim_strsave((char_u *)"conpty");
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006982 ++job->jv_refcount;
6983 term->tl_job = job;
6984
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006985 // Redirecting stdout and stderr doesn't work at the job level. Instead
6986 // open the file here and handle it in. opt->jo_io was changed in
6987 // setup_job_options(), use the original flags here.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006988 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
6989 {
6990 char_u *fname = opt->jo_io_name[PART_OUT];
6991
6992 ch_log(channel, "Opening output file %s", fname);
6993 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
6994 if (term->tl_out_fd == NULL)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00006995 semsg(_(e_cant_open_file_str), fname);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006996 }
6997
6998 return OK;
6999
7000failed:
7001 ga_clear(&ga_cmd);
7002 ga_clear(&ga_env);
7003 vim_free(cmd_wchar);
7004 vim_free(cmd_wchar_copy);
7005 vim_free(cwd_wchar);
7006 if (channel != NULL)
7007 channel_clear(channel);
7008 if (job != NULL)
7009 {
7010 job->jv_channel = NULL;
7011 job_cleanup(job);
7012 }
7013 term->tl_job = NULL;
7014 if (jo != NULL)
7015 CloseHandle(jo);
7016
7017 if (term->tl_siex.lpAttributeList != NULL)
7018 {
7019 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
7020 vim_free(term->tl_siex.lpAttributeList);
7021 }
7022 term->tl_siex.lpAttributeList = NULL;
7023 if (o_theirs != NULL)
7024 CloseHandle(o_theirs);
7025 if (o_ours != NULL)
7026 CloseHandle(o_ours);
7027 if (i_ours != NULL)
7028 CloseHandle(i_ours);
7029 if (i_theirs != NULL)
7030 CloseHandle(i_theirs);
7031 if (term->tl_conpty != NULL)
7032 pClosePseudoConsole(term->tl_conpty);
7033 term->tl_conpty = NULL;
7034 return FAIL;
7035}
7036
7037 static void
7038conpty_term_report_winsize(term_T *term, int rows, int cols)
7039{
7040 COORD consize;
7041
7042 consize.X = cols;
7043 consize.Y = rows;
7044 pResizePseudoConsole(term->tl_conpty, consize);
7045}
7046
Bram Moolenaar840d16f2019-09-10 21:27:18 +02007047 static void
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007048term_free_conpty(term_T *term)
7049{
7050 if (term->tl_siex.lpAttributeList != NULL)
7051 {
7052 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
7053 vim_free(term->tl_siex.lpAttributeList);
7054 }
7055 term->tl_siex.lpAttributeList = NULL;
7056 if (term->tl_conpty != NULL)
7057 pClosePseudoConsole(term->tl_conpty);
7058 term->tl_conpty = NULL;
7059}
7060
7061 int
7062use_conpty(void)
7063{
7064 return has_conpty;
7065}
7066
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007067# ifndef PROTO
7068
7069#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
7070#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01007071#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007072
7073void* (*winpty_config_new)(UINT64, void*);
7074void* (*winpty_open)(void*, void*);
7075void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
7076BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
7077void (*winpty_config_set_mouse_mode)(void*, int);
7078void (*winpty_config_set_initial_size)(void*, int, int);
7079LPCWSTR (*winpty_conin_name)(void*);
7080LPCWSTR (*winpty_conout_name)(void*);
7081LPCWSTR (*winpty_conerr_name)(void*);
7082void (*winpty_free)(void*);
7083void (*winpty_config_free)(void*);
7084void (*winpty_spawn_config_free)(void*);
7085void (*winpty_error_free)(void*);
7086LPCWSTR (*winpty_error_msg)(void*);
7087BOOL (*winpty_set_size)(void*, int, int, void*);
7088HANDLE (*winpty_agent_process)(void*);
7089
7090#define WINPTY_DLL "winpty.dll"
7091
7092static HINSTANCE hWinPtyDLL = NULL;
7093# endif
7094
7095 static int
7096dyn_winpty_init(int verbose)
7097{
7098 int i;
7099 static struct
7100 {
7101 char *name;
7102 FARPROC *ptr;
7103 } winpty_entry[] =
7104 {
7105 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
7106 {"winpty_config_free", (FARPROC*)&winpty_config_free},
7107 {"winpty_config_new", (FARPROC*)&winpty_config_new},
7108 {"winpty_config_set_mouse_mode",
7109 (FARPROC*)&winpty_config_set_mouse_mode},
7110 {"winpty_config_set_initial_size",
7111 (FARPROC*)&winpty_config_set_initial_size},
7112 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
7113 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
7114 {"winpty_error_free", (FARPROC*)&winpty_error_free},
7115 {"winpty_free", (FARPROC*)&winpty_free},
7116 {"winpty_open", (FARPROC*)&winpty_open},
7117 {"winpty_spawn", (FARPROC*)&winpty_spawn},
7118 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
7119 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
7120 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
7121 {"winpty_set_size", (FARPROC*)&winpty_set_size},
7122 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
7123 {NULL, NULL}
7124 };
7125
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007126 // No need to initialize twice.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007127 if (hWinPtyDLL)
7128 return OK;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007129 // Load winpty.dll, prefer using the 'winptydll' option, fall back to just
7130 // winpty.dll.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007131 if (*p_winptydll != NUL)
7132 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
7133 if (!hWinPtyDLL)
7134 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
7135 if (!hWinPtyDLL)
7136 {
7137 if (verbose)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00007138 semsg(_(e_could_not_load_library_str_str),
Martin Tournoij1a3e5742021-07-24 13:57:29 +02007139 (*p_winptydll != NUL ? p_winptydll : (char_u *)WINPTY_DLL),
7140 GetWin32Error());
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007141 return FAIL;
7142 }
7143 for (i = 0; winpty_entry[i].name != NULL
7144 && winpty_entry[i].ptr != NULL; ++i)
7145 {
7146 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
7147 winpty_entry[i].name)) == NULL)
7148 {
7149 if (verbose)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00007150 semsg(_(e_could_not_load_library_function_str), winpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01007151 hWinPtyDLL = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007152 return FAIL;
7153 }
7154 }
7155
7156 return OK;
7157}
7158
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007159 static int
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007160winpty_term_and_job_init(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007161 term_T *term,
7162 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02007163 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007164 jobopt_T *opt,
7165 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007166{
7167 WCHAR *cmd_wchar = NULL;
7168 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007169 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007170 channel_T *channel = NULL;
7171 job_T *job = NULL;
7172 DWORD error;
7173 HANDLE jo = NULL;
7174 HANDLE child_process_handle;
7175 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01007176 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007177 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007178 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007179 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007180
Bram Moolenaar04935fb2022-01-08 16:19:22 +00007181 ga_init2(&ga_cmd, sizeof(char*), 20);
7182 ga_init2(&ga_env, sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007183
7184 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007185 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007186 cmd = argvar->vval.v_string;
7187 }
7188 else if (argvar->v_type == VAR_LIST)
7189 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007190 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007191 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007192 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007193 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007194 if (cmd == NULL || *cmd == NUL)
7195 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00007196 emsg(_(e_invalid_argument));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007197 goto failed;
7198 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007199
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007200 term->tl_arg0_cmd = vim_strsave(cmd);
7201
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007202 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007203 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007204 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007205 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007206 if (opt->jo_cwd != NULL)
7207 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01007208
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01007209 win32_build_env(opt->jo_env, &ga_env, TRUE);
7210 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007211
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007212 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
7213 if (term->tl_winpty_config == NULL)
7214 goto failed;
7215
7216 winpty_config_set_mouse_mode(term->tl_winpty_config,
7217 WINPTY_MOUSE_MODE_FORCE);
7218 winpty_config_set_initial_size(term->tl_winpty_config,
7219 term->tl_cols, term->tl_rows);
7220 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
7221 if (term->tl_winpty == NULL)
7222 goto failed;
7223
7224 spawn_config = winpty_spawn_config_new(
7225 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
7226 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
7227 NULL,
7228 cmd_wchar,
7229 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007230 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007231 &winpty_err);
7232 if (spawn_config == NULL)
7233 goto failed;
7234
7235 channel = add_channel();
7236 if (channel == NULL)
7237 goto failed;
7238
7239 job = job_alloc();
7240 if (job == NULL)
7241 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02007242 if (argvar->v_type == VAR_STRING)
7243 {
7244 int argc;
7245
7246 build_argv_from_string(cmd, &job->jv_argv, &argc);
7247 }
7248 else
7249 {
7250 int argc;
7251
7252 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
7253 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007254
7255 if (opt->jo_set & JO_IN_BUF)
7256 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
7257
7258 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
7259 &child_thread_handle, &error, &winpty_err))
7260 goto failed;
7261
7262 channel_set_pipes(channel,
7263 (sock_T)CreateFileW(
7264 winpty_conin_name(term->tl_winpty),
7265 GENERIC_WRITE, 0, NULL,
7266 OPEN_EXISTING, 0, NULL),
7267 (sock_T)CreateFileW(
7268 winpty_conout_name(term->tl_winpty),
7269 GENERIC_READ, 0, NULL,
7270 OPEN_EXISTING, 0, NULL),
7271 (sock_T)CreateFileW(
7272 winpty_conerr_name(term->tl_winpty),
7273 GENERIC_READ, 0, NULL,
7274 OPEN_EXISTING, 0, NULL));
7275
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007276 // Write lines with CR instead of NL.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007277 channel->ch_write_text_mode = TRUE;
7278
7279 jo = CreateJobObject(NULL, NULL);
7280 if (jo == NULL)
7281 goto failed;
7282
7283 if (!AssignProcessToJobObject(jo, child_process_handle))
7284 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007285 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007286 CloseHandle(jo);
7287 jo = NULL;
7288 }
7289
7290 winpty_spawn_config_free(spawn_config);
7291 vim_free(cmd_wchar);
7292 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007293 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007294
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007295 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7296 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007297
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007298#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01007299 if (term_use_palette())
7300 {
7301 if (term->tl_palette != NULL)
7302 set_vterm_palette(term->tl_vterm, term->tl_palette);
7303 else
7304 init_vterm_ansi_colors(term->tl_vterm);
7305 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007306#endif
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02007307
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007308 channel_set_job(channel, job, opt);
7309 job_set_options(job, opt);
7310
7311 job->jv_channel = channel;
7312 job->jv_proc_info.hProcess = child_process_handle;
7313 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
7314 job->jv_job_object = jo;
7315 job->jv_status = JOB_STARTED;
7316 job->jv_tty_in = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007317 (short_u *)winpty_conin_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007318 job->jv_tty_out = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007319 (short_u *)winpty_conout_name(term->tl_winpty), NULL);
Bram Moolenaar18442cb2019-02-13 21:22:12 +01007320 job->jv_tty_type = vim_strsave((char_u *)"winpty");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007321 ++job->jv_refcount;
7322 term->tl_job = job;
7323
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007324 // Redirecting stdout and stderr doesn't work at the job level. Instead
7325 // open the file here and handle it in. opt->jo_io was changed in
7326 // setup_job_options(), use the original flags here.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007327 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
7328 {
7329 char_u *fname = opt->jo_io_name[PART_OUT];
7330
7331 ch_log(channel, "Opening output file %s", fname);
7332 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
7333 if (term->tl_out_fd == NULL)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00007334 semsg(_(e_cant_open_file_str), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007335 }
7336
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007337 return OK;
7338
7339failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007340 ga_clear(&ga_cmd);
7341 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007342 vim_free(cmd_wchar);
7343 vim_free(cwd_wchar);
7344 if (spawn_config != NULL)
7345 winpty_spawn_config_free(spawn_config);
7346 if (channel != NULL)
7347 channel_clear(channel);
7348 if (job != NULL)
7349 {
7350 job->jv_channel = NULL;
7351 job_cleanup(job);
7352 }
7353 term->tl_job = NULL;
7354 if (jo != NULL)
7355 CloseHandle(jo);
7356 if (term->tl_winpty != NULL)
7357 winpty_free(term->tl_winpty);
7358 term->tl_winpty = NULL;
7359 if (term->tl_winpty_config != NULL)
7360 winpty_config_free(term->tl_winpty_config);
7361 term->tl_winpty_config = NULL;
7362 if (winpty_err != NULL)
7363 {
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007364 char *msg = (char *)utf16_to_enc(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007365 (short_u *)winpty_error_msg(winpty_err), NULL);
7366
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01007367 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007368 winpty_error_free(winpty_err);
7369 }
7370 return FAIL;
7371}
7372
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007373/*
7374 * Create a new terminal of "rows" by "cols" cells.
7375 * Store a reference in "term".
7376 * Return OK or FAIL.
7377 */
7378 static int
7379term_and_job_init(
7380 term_T *term,
7381 typval_T *argvar,
Bram Moolenaar197c6b72019-11-03 23:37:12 +01007382 char **argv,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007383 jobopt_T *opt,
7384 jobopt_T *orig_opt)
7385{
7386 int use_winpty = FALSE;
7387 int use_conpty = FALSE;
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007388 int tty_type = *p_twt;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007389
7390 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
7391 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
7392
7393 if (!has_winpty && !has_conpty)
7394 // If neither is available give the errors for winpty, since when
7395 // conpty is not available it can't be installed either.
7396 return dyn_winpty_init(TRUE);
7397
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007398 if (opt->jo_tty_type != NUL)
7399 tty_type = opt->jo_tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007400
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007401 if (tty_type == NUL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007402 {
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01007403 if (has_conpty && (is_conpty_stable() || !has_winpty))
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007404 use_conpty = TRUE;
7405 else if (has_winpty)
7406 use_winpty = TRUE;
7407 // else: error
7408 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007409 else if (tty_type == 'w') // winpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007410 {
7411 if (has_winpty)
7412 use_winpty = TRUE;
7413 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007414 else if (tty_type == 'c') // conpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007415 {
7416 if (has_conpty)
7417 use_conpty = TRUE;
7418 else
7419 return dyn_conpty_init(TRUE);
7420 }
7421
7422 if (use_conpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007423 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007424
7425 if (use_winpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007426 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007427
7428 // error
7429 return dyn_winpty_init(TRUE);
7430}
7431
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007432 static int
7433create_pty_only(term_T *term, jobopt_T *options)
7434{
7435 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
7436 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
7437 char in_name[80], out_name[80];
7438 channel_T *channel = NULL;
7439
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007440 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7441 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007442
7443 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
7444 GetCurrentProcessId(),
7445 curbuf->b_fnum);
7446 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
7447 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
7448 PIPE_UNLIMITED_INSTANCES,
7449 0, 0, NMPWAIT_NOWAIT, NULL);
7450 if (hPipeIn == INVALID_HANDLE_VALUE)
7451 goto failed;
7452
7453 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
7454 GetCurrentProcessId(),
7455 curbuf->b_fnum);
7456 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
7457 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
7458 PIPE_UNLIMITED_INSTANCES,
7459 0, 0, 0, NULL);
7460 if (hPipeOut == INVALID_HANDLE_VALUE)
7461 goto failed;
7462
7463 ConnectNamedPipe(hPipeIn, NULL);
7464 ConnectNamedPipe(hPipeOut, NULL);
7465
7466 term->tl_job = job_alloc();
7467 if (term->tl_job == NULL)
7468 goto failed;
7469 ++term->tl_job->jv_refcount;
7470
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007471 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007472 term->tl_job->jv_status = JOB_FINISHED;
7473
7474 channel = add_channel();
7475 if (channel == NULL)
7476 goto failed;
7477 term->tl_job->jv_channel = channel;
7478 channel->ch_keep_open = TRUE;
7479 channel->ch_named_pipe = TRUE;
7480
7481 channel_set_pipes(channel,
7482 (sock_T)hPipeIn,
7483 (sock_T)hPipeOut,
7484 (sock_T)hPipeOut);
7485 channel_set_job(channel, term->tl_job, options);
7486 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
7487 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
7488
7489 return OK;
7490
7491failed:
7492 if (hPipeIn != NULL)
7493 CloseHandle(hPipeIn);
7494 if (hPipeOut != NULL)
7495 CloseHandle(hPipeOut);
7496 return FAIL;
7497}
7498
7499/*
7500 * Free the terminal emulator part of "term".
7501 */
7502 static void
7503term_free_vterm(term_T *term)
7504{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007505 term_free_conpty(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007506 if (term->tl_winpty != NULL)
7507 winpty_free(term->tl_winpty);
7508 term->tl_winpty = NULL;
7509 if (term->tl_winpty_config != NULL)
7510 winpty_config_free(term->tl_winpty_config);
7511 term->tl_winpty_config = NULL;
7512 if (term->tl_vterm != NULL)
7513 vterm_free(term->tl_vterm);
7514 term->tl_vterm = NULL;
7515}
7516
7517/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02007518 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007519 */
7520 static void
7521term_report_winsize(term_T *term, int rows, int cols)
7522{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007523 if (term->tl_conpty)
7524 conpty_term_report_winsize(term, rows, cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007525 if (term->tl_winpty)
7526 winpty_set_size(term->tl_winpty, cols, rows, NULL);
7527}
7528
7529 int
7530terminal_enabled(void)
7531{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007532 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007533}
7534
7535# else
7536
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007537///////////////////////////////////////
7538// 3. Unix-like implementation.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007539
7540/*
7541 * Create a new terminal of "rows" by "cols" cells.
7542 * Start job for "cmd".
7543 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01007544 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007545 * Return OK or FAIL.
7546 */
7547 static int
7548term_and_job_init(
7549 term_T *term,
7550 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01007551 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007552 jobopt_T *opt,
7553 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007554{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007555 term->tl_arg0_cmd = NULL;
7556
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007557 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7558 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007559
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007560#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01007561 if (term_use_palette())
7562 {
7563 if (term->tl_palette != NULL)
7564 set_vterm_palette(term->tl_vterm, term->tl_palette);
7565 else
7566 init_vterm_ansi_colors(term->tl_vterm);
7567 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007568#endif
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02007569
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007570 // This may change a string in "argvar".
Bram Moolenaar21109272020-01-30 16:27:20 +01007571 term->tl_job = job_start(argvar, argv, opt, &term->tl_job);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007572 if (term->tl_job != NULL)
7573 ++term->tl_job->jv_refcount;
7574
7575 return term->tl_job != NULL
7576 && term->tl_job->jv_channel != NULL
7577 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
7578}
7579
7580 static int
7581create_pty_only(term_T *term, jobopt_T *opt)
7582{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007583 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7584 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007585
7586 term->tl_job = job_alloc();
7587 if (term->tl_job == NULL)
7588 return FAIL;
7589 ++term->tl_job->jv_refcount;
7590
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007591 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007592 term->tl_job->jv_status = JOB_FINISHED;
7593
7594 return mch_create_pty_channel(term->tl_job, opt);
7595}
7596
7597/*
7598 * Free the terminal emulator part of "term".
7599 */
7600 static void
7601term_free_vterm(term_T *term)
7602{
7603 if (term->tl_vterm != NULL)
7604 vterm_free(term->tl_vterm);
7605 term->tl_vterm = NULL;
7606}
7607
7608/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02007609 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007610 */
7611 static void
7612term_report_winsize(term_T *term, int rows, int cols)
7613{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007614 // Use an ioctl() to report the new window size to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007615 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
7616 {
7617 int fd = -1;
7618 int part;
7619
7620 for (part = PART_OUT; part < PART_COUNT; ++part)
7621 {
7622 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01007623 if (mch_isatty(fd))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007624 break;
7625 }
7626 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
7627 mch_signal_job(term->tl_job, (char_u *)"winch");
7628 }
7629}
7630
7631# endif
7632
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007633#endif // FEAT_TERMINAL