blob: 71566657142303614e1931df43c90104c4723d62 [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
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +0000262 if (*wp->w_p_tws == NUL)
263 return FALSE;
Bram Moolenaar498c2562018-04-15 23:45:15 +0200264
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +0000265 char_u *p = vim_strchr(wp->w_p_tws, 'x');
266
267 // Syntax of value was already checked when it's set.
268 if (p == NULL)
269 {
270 minsize = TRUE;
271 p = vim_strchr(wp->w_p_tws, '*');
Bram Moolenaar498c2562018-04-15 23:45:15 +0200272 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +0000273 *rows = atoi((char *)wp->w_p_tws);
274 *cols = atoi((char *)p + 1);
Christian Brabandtceee7a82023-09-21 16:55:06 +0200275 if (*rows > VTERM_MAX_ROWS)
276 *rows = VTERM_MAX_ROWS;
277 if (*cols > VTERM_MAX_COLS)
278 *cols = VTERM_MAX_COLS;
Bram Moolenaar498c2562018-04-15 23:45:15 +0200279 return minsize;
280}
281
282/*
Bram Moolenaarb833c1e2018-05-05 16:36:06 +0200283 * Determine the terminal size from 'termwinsize' and the current window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200284 */
285 static void
Bram Moolenaarb936b792020-09-04 18:34:09 +0200286set_term_and_win_size(term_T *term, jobopt_T *opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200287{
Bram Moolenaarb936b792020-09-04 18:34:09 +0200288 int rows, cols;
289 int minsize;
290
Bram Moolenaar13568252018-03-16 20:46:58 +0100291#ifdef FEAT_GUI
292 if (term->tl_system)
293 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100294 // Use the whole screen for the system command. However, it will start
295 // at the command line and scroll up as needed, using tl_toprow.
Bram Moolenaar13568252018-03-16 20:46:58 +0100296 term->tl_rows = Rows;
297 term->tl_cols = Columns;
Bram Moolenaar07b46af2018-04-10 14:56:18 +0200298 return;
Bram Moolenaar13568252018-03-16 20:46:58 +0100299 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100300#endif
Bram Moolenaarb936b792020-09-04 18:34:09 +0200301 term->tl_rows = curwin->w_height;
302 term->tl_cols = curwin->w_width;
303
304 minsize = parse_termwinsize(curwin, &rows, &cols);
305 if (minsize)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200306 {
Bram Moolenaarb936b792020-09-04 18:34:09 +0200307 if (term->tl_rows < rows)
308 term->tl_rows = rows;
309 if (term->tl_cols < cols)
310 term->tl_cols = cols;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200311 }
Bram Moolenaarb936b792020-09-04 18:34:09 +0200312 if ((opt->jo_set2 & JO2_TERM_ROWS))
313 term->tl_rows = opt->jo_term_rows;
314 else if (rows != 0)
315 term->tl_rows = rows;
316 if ((opt->jo_set2 & JO2_TERM_COLS))
317 term->tl_cols = opt->jo_term_cols;
318 else if (cols != 0)
319 term->tl_cols = cols;
320
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200321 if (!opt->jo_hidden)
Bram Moolenaarb936b792020-09-04 18:34:09 +0200322 {
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200323 if (term->tl_rows != curwin->w_height)
324 win_setheight_win(term->tl_rows, curwin);
325 if (term->tl_cols != curwin->w_width)
326 win_setwidth_win(term->tl_cols, curwin);
Bram Moolenaarb936b792020-09-04 18:34:09 +0200327
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200328 // Set 'winsize' now to avoid a resize at the next redraw.
329 if (!minsize && *curwin->w_p_tws != NUL)
330 {
331 char_u buf[100];
332
333 vim_snprintf((char *)buf, 100, "%dx%d",
334 term->tl_rows, term->tl_cols);
Bram Moolenaarac4174e2022-05-07 16:38:24 +0100335 set_option_value_give_err((char_u *)"termwinsize",
336 0L, buf, OPT_LOCAL);
Bram Moolenaar2ce14582020-09-05 16:08:49 +0200337 }
Bram Moolenaarb936b792020-09-04 18:34:09 +0200338 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200339}
340
341/*
342 * Initialize job options for a terminal job.
343 * Caller may overrule some of them.
344 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100345 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200346init_job_options(jobopt_T *opt)
347{
348 clear_job_options(opt);
349
Bram Moolenaarac4174e2022-05-07 16:38:24 +0100350 opt->jo_mode = CH_MODE_RAW;
351 opt->jo_out_mode = CH_MODE_RAW;
352 opt->jo_err_mode = CH_MODE_RAW;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200353 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
354}
355
356/*
357 * Set job options mandatory for a terminal job.
358 */
359 static void
360setup_job_options(jobopt_T *opt, int rows, int cols)
361{
Bram Moolenaar4f974752019-02-17 17:44:42 +0100362#ifndef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100363 // Win32: Redirecting the job output won't work, thus always connect stdout
364 // here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200365 if (!(opt->jo_set & JO_OUT_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200366#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200367 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100368 // Connect stdout to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200369 opt->jo_io[PART_OUT] = JIO_BUFFER;
370 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
371 opt->jo_modifiable[PART_OUT] = 0;
372 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE;
373 }
374
Bram Moolenaar4f974752019-02-17 17:44:42 +0100375#ifndef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100376 // Win32: Redirecting the job output won't work, thus always connect stderr
377 // here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200378 if (!(opt->jo_set & JO_ERR_IO))
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200379#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200380 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100381 // Connect stderr to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200382 opt->jo_io[PART_ERR] = JIO_BUFFER;
383 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
384 opt->jo_modifiable[PART_ERR] = 0;
385 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE;
386 }
387
388 opt->jo_pty = TRUE;
389 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0)
390 opt->jo_term_rows = rows;
391 if ((opt->jo_set2 & JO2_TERM_COLS) == 0)
392 opt->jo_term_cols = cols;
393}
394
395/*
Bram Moolenaar5c381eb2019-06-25 06:50:31 +0200396 * Flush messages on channels.
397 */
398 static void
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +0000399term_flush_messages(void)
Bram Moolenaar5c381eb2019-06-25 06:50:31 +0200400{
401 mch_check_messages();
402 parse_queued_messages();
403}
404
405/*
Bram Moolenaard96ff162018-02-18 22:13:29 +0100406 * Close a terminal buffer (and its window). Used when creating the terminal
407 * fails.
408 */
409 static void
410term_close_buffer(buf_T *buf, buf_T *old_curbuf)
411{
412 free_terminal(buf);
413 if (old_curbuf != NULL)
414 {
415 --curbuf->b_nwindows;
416 curbuf = old_curbuf;
417 curwin->w_buffer = curbuf;
418 ++curbuf->b_nwindows;
419 }
Bram Moolenaarcee52202020-03-11 14:19:58 +0100420 CHECK_CURBUF;
Bram Moolenaard96ff162018-02-18 22:13:29 +0100421
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100422 // Wiping out the buffer will also close the window and call
423 // free_terminal().
Bram Moolenaard96ff162018-02-18 22:13:29 +0100424 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE);
425}
426
427/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200428 * Start a terminal window and return its buffer.
Bram Moolenaar13568252018-03-16 20:46:58 +0100429 * Use either "argvar" or "argv", the other must be NULL.
430 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open
431 * the window.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200432 * Returns NULL when failed.
433 */
Bram Moolenaar13568252018-03-16 20:46:58 +0100434 buf_T *
435term_start(
436 typval_T *argvar,
437 char **argv,
438 jobopt_T *opt,
439 int flags)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200440{
441 exarg_T split_ea;
442 win_T *old_curwin = curwin;
443 term_T *term;
444 buf_T *old_curbuf = NULL;
445 int res;
446 buf_T *newbuf;
Bram Moolenaare1004402020-10-24 20:49:43 +0200447 int vertical = opt->jo_vertical || (cmdmod.cmod_split & WSP_VERT);
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200448 jobopt_T orig_opt; // only partly filled
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200449
450 if (check_restricted() || check_secure())
451 return NULL;
Bram Moolenaare5b44862021-05-30 13:54:03 +0200452 if (cmdwin_type != 0)
453 {
454 emsg(_(e_cannot_open_terminal_from_command_line_window));
455 return NULL;
456 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200457
458 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO))
459 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)
460 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF))
Bram Moolenaarb0992022020-01-30 14:55:42 +0100461 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF))
462 || (argvar != NULL
463 && argvar->v_type == VAR_LIST
464 && argvar->vval.v_list != NULL
465 && argvar->vval.v_list->lv_first == &range_list_item))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200466 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +0000467 emsg(_(e_invalid_argument));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200468 return NULL;
469 }
470
Bram Moolenaarc799fe22019-05-28 23:08:19 +0200471 term = ALLOC_CLEAR_ONE(term_T);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200472 if (term == NULL)
473 return NULL;
474 term->tl_dirty_row_end = MAX_ROW;
475 term->tl_cursor_visible = TRUE;
476 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK;
477 term->tl_finish = opt->jo_term_finish;
Bram Moolenaar13568252018-03-16 20:46:58 +0100478#ifdef FEAT_GUI
479 term->tl_system = (flags & TERM_START_SYSTEM);
480#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200481 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaar29ae2232019-02-14 21:22:01 +0100482 ga_init2(&term->tl_scrollback_postponed, sizeof(sb_line_T), 300);
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +0200483 ga_init2(&term->tl_osc_buf, sizeof(char), 300);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200484
Bram Moolenaaraeed2a62021-04-29 20:18:45 +0200485 setpcmark();
Bram Moolenaara80faa82020-04-12 19:37:17 +0200486 CLEAR_FIELD(split_ea);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200487 if (opt->jo_curwin)
488 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100489 // Create a new buffer in the current window.
Bram Moolenaar13568252018-03-16 20:46:58 +0100490 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200491 {
492 no_write_message();
493 vim_free(term);
494 return NULL;
495 }
496 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE,
Bram Moolenaarb1009092020-05-31 16:04:42 +0200497 (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0)
498 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0),
499 curwin) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200500 {
501 vim_free(term);
502 return NULL;
503 }
504 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100505 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200506 {
507 buf_T *buf;
508
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100509 // Create a new buffer without a window. Make it the current buffer for
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100510 // a moment to be able to do the initializations.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200511 buf = buflist_new((char_u *)"", NULL, (linenr_T)0,
512 BLN_NEW | BLN_LISTED);
513 if (buf == NULL || ml_open(buf) == FAIL)
514 {
515 vim_free(term);
516 return NULL;
517 }
518 old_curbuf = curbuf;
519 --curbuf->b_nwindows;
520 curbuf = buf;
521 curwin->w_buffer = buf;
522 ++curbuf->b_nwindows;
523 }
524 else
525 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100526 // Open a new window or tab.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200527 split_ea.cmdidx = CMD_new;
528 split_ea.cmd = (char_u *)"new";
529 split_ea.arg = (char_u *)"";
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100530 if (opt->jo_term_rows > 0 && !vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200531 {
532 split_ea.line2 = opt->jo_term_rows;
533 split_ea.addr_count = 1;
534 }
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100535 if (opt->jo_term_cols > 0 && vertical)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200536 {
537 split_ea.line2 = opt->jo_term_cols;
538 split_ea.addr_count = 1;
539 }
540
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100541 if (vertical)
Bram Moolenaare1004402020-10-24 20:49:43 +0200542 cmdmod.cmod_split |= WSP_VERT;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200543 ex_splitview(&split_ea);
544 if (curwin == old_curwin)
545 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100546 // split failed
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200547 vim_free(term);
548 return NULL;
549 }
550 }
551 term->tl_buffer = curbuf;
552 curbuf->b_term = term;
553
554 if (!opt->jo_hidden)
555 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100556 // Only one size was taken care of with :new, do the other one. With
557 // "curwin" both need to be done.
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100558 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200559 win_setheight(opt->jo_term_rows);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +0100560 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200561 win_setwidth(opt->jo_term_cols);
562 }
563
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100564 // Link the new terminal in the list of active terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200565 term->tl_next = first_term;
566 first_term = term;
567
Bram Moolenaar5e94a292020-03-19 18:46:57 +0100568 apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf);
569
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200570 if (opt->jo_term_name != NULL)
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100571 {
572 vim_free(curbuf->b_ffname);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200573 curbuf->b_ffname = vim_strsave(opt->jo_term_name);
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100574 }
Bram Moolenaar13568252018-03-16 20:46:58 +0100575 else if (argv != NULL)
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100576 {
577 vim_free(curbuf->b_ffname);
Bram Moolenaar13568252018-03-16 20:46:58 +0100578 curbuf->b_ffname = vim_strsave((char_u *)"!system");
Bram Moolenaard5bc32d2020-03-22 19:25:50 +0100579 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200580 else
581 {
582 int i;
583 size_t len;
584 char_u *cmd, *p;
585
586 if (argvar->v_type == VAR_STRING)
587 {
588 cmd = argvar->vval.v_string;
589 if (cmd == NULL)
590 cmd = (char_u *)"";
591 else if (STRCMP(cmd, "NONE") == 0)
592 cmd = (char_u *)"pty";
593 }
594 else if (argvar->v_type != VAR_LIST
595 || argvar->vval.v_list == NULL
Bram Moolenaarb0992022020-01-30 14:55:42 +0100596 || argvar->vval.v_list->lv_len == 0
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100597 || (cmd = tv_get_string_chk(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200598 &argvar->vval.v_list->lv_first->li_tv)) == NULL)
599 cmd = (char_u*)"";
600
601 len = STRLEN(cmd) + 10;
Bram Moolenaar51e14382019-05-25 20:21:28 +0200602 p = alloc(len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200603
604 for (i = 0; p != NULL; ++i)
605 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100606 // Prepend a ! to the command name to avoid the buffer name equals
607 // the executable, otherwise ":w!" would overwrite it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200608 if (i == 0)
609 vim_snprintf((char *)p, len, "!%s", cmd);
610 else
611 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i);
612 if (buflist_findname(p) == NULL)
613 {
614 vim_free(curbuf->b_ffname);
615 curbuf->b_ffname = p;
616 break;
617 }
618 }
619 }
Bram Moolenaare010c722020-02-24 21:37:54 +0100620 vim_free(curbuf->b_sfname);
621 curbuf->b_sfname = vim_strsave(curbuf->b_ffname);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200622 curbuf->b_fname = curbuf->b_ffname;
623
Bram Moolenaar5e94a292020-03-19 18:46:57 +0100624 apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf);
625
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200626 if (opt->jo_term_opencmd != NULL)
627 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd);
628
629 if (opt->jo_eof_chars != NULL)
630 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars);
631
632 set_string_option_direct((char_u *)"buftype", -1,
633 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar7da1fb52018-08-04 16:54:11 +0200634 // Avoid that 'buftype' is reset when this buffer is entered.
635 curbuf->b_p_initialized = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200636
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100637 // Mark the buffer as not modifiable. It can only be made modifiable after
638 // the job finished.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200639 curbuf->b_p_ma = FALSE;
640
Bram Moolenaarb936b792020-09-04 18:34:09 +0200641 set_term_and_win_size(term, opt);
Bram Moolenaar4f974752019-02-17 17:44:42 +0100642#ifdef MSWIN
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200643 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io));
644#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200645 setup_job_options(opt, term->tl_rows, term->tl_cols);
646
Bram Moolenaar13568252018-03-16 20:46:58 +0100647 if (flags & TERM_START_NOJOB)
Bram Moolenaard96ff162018-02-18 22:13:29 +0100648 return curbuf;
649
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100650#if defined(FEAT_SESSION)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100651 // Remember the command for the session file.
Bram Moolenaar13568252018-03-16 20:46:58 +0100652 if (opt->jo_term_norestore || argv != NULL)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100653 term->tl_command = vim_strsave((char_u *)"NONE");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100654 else if (argvar->v_type == VAR_STRING)
655 {
656 char_u *cmd = argvar->vval.v_string;
657
658 if (cmd != NULL && STRCMP(cmd, p_sh) != 0)
659 term->tl_command = vim_strsave(cmd);
660 }
661 else if (argvar->v_type == VAR_LIST
662 && argvar->vval.v_list != NULL
663 && argvar->vval.v_list->lv_len > 0)
664 {
665 garray_T ga;
666 listitem_T *item;
667
668 ga_init2(&ga, 1, 100);
Bram Moolenaaraeea7212020-04-02 18:50:46 +0200669 FOR_ALL_LIST_ITEMS(argvar->vval.v_list, item)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100670 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +0100671 char_u *s = tv_get_string_chk(&item->li_tv);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100672 char_u *p;
673
674 if (s == NULL)
675 break;
Bram Moolenaar21c1a0c2021-10-17 17:20:23 +0100676 p = vim_strsave_fnameescape(s, VSE_NONE);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100677 if (p == NULL)
678 break;
679 ga_concat(&ga, p);
680 vim_free(p);
681 ga_append(&ga, ' ');
682 }
683 if (item == NULL)
684 {
685 ga_append(&ga, NUL);
686 term->tl_command = ga.ga_data;
687 }
688 else
689 ga_clear(&ga);
690 }
691#endif
692
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100693 if (opt->jo_term_kill != NULL)
694 {
695 char_u *p = skiptowhite(opt->jo_term_kill);
696
697 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill);
698 }
699
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200700 if (opt->jo_term_api != NULL)
Bram Moolenaar21109272020-01-30 16:27:20 +0100701 {
702 char_u *p = skiptowhite(opt->jo_term_api);
703
704 term->tl_api = vim_strnsave(opt->jo_term_api, p - opt->jo_term_api);
705 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200706 else
707 term->tl_api = vim_strsave((char_u *)"Tapi_");
708
Bram Moolenaar83d47902020-03-26 20:34:00 +0100709 if (opt->jo_set2 & JO2_TERM_HIGHLIGHT)
710 term->tl_highlight_name = vim_strsave(opt->jo_term_highlight);
711
Bram Moolenaar30b9a412022-05-26 14:06:37 +0100712#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +0100713 // Save the user-defined palette, it is only used in GUI (or 'tgc' is on).
714 if (opt->jo_set2 & JO2_ANSI_COLORS)
715 {
716 term->tl_palette = ALLOC_MULT(long_u, 16);
717 if (term->tl_palette == NULL)
718 {
719 vim_free(term);
720 return NULL;
721 }
722 memcpy(term->tl_palette, opt->jo_ansi_colors, sizeof(long_u) * 16);
723 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +0100724#endif
LemonBoyb2b3acb2022-05-20 10:10:34 +0100725
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100726 // System dependent: setup the vterm and maybe start the job in it.
Bram Moolenaar13568252018-03-16 20:46:58 +0100727 if (argv == NULL
728 && argvar->v_type == VAR_STRING
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200729 && argvar->vval.v_string != NULL
730 && STRCMP(argvar->vval.v_string, "NONE") == 0)
731 res = create_pty_only(term, opt);
732 else
Bram Moolenaarf25329c2018-05-06 21:49:32 +0200733 res = term_and_job_init(term, argvar, argv, opt, &orig_opt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200734
735 newbuf = curbuf;
736 if (res == OK)
737 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100738 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200739 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
740 term_report_winsize(term, term->tl_rows, term->tl_cols);
Bram Moolenaar13568252018-03-16 20:46:58 +0100741#ifdef FEAT_GUI
742 if (term->tl_system)
743 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100744 // display first line below typed command
Bram Moolenaar13568252018-03-16 20:46:58 +0100745 term->tl_toprow = msg_row + 1;
746 term->tl_dirty_row_end = 0;
747 }
748#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200749
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100750 // Make sure we don't get stuck on sending keys to the job, it leads to
751 // a deadlock if the job is waiting for Vim to read.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200752 channel_set_nonblock(term->tl_job->jv_channel, PART_IN);
753
Bram Moolenaar606cb8b2018-05-03 20:40:20 +0200754 if (old_curbuf != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200755 {
756 --curbuf->b_nwindows;
757 curbuf = old_curbuf;
758 curwin->w_buffer = curbuf;
759 ++curbuf->b_nwindows;
760 }
Bram Moolenaar81035272021-12-16 18:02:07 +0000761 else if (vgetc_busy
762#ifdef FEAT_TIMERS
763 || timer_busy
764#endif
765 || input_busy)
766 {
767 char_u ignore[4];
768
769 // When waiting for input need to return and possibly end up in
770 // terminal_loop() instead.
771 ignore[0] = K_SPECIAL;
772 ignore[1] = KS_EXTRA;
773 ignore[2] = KE_IGNORE;
774 ignore[3] = NUL;
775 ins_typebuf(ignore, REMAP_NONE, 0, TRUE, FALSE);
776 typebuf_was_filled = TRUE;
777 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200778 }
779 else
780 {
Bram Moolenaard96ff162018-02-18 22:13:29 +0100781 term_close_buffer(curbuf, old_curbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200782 return NULL;
783 }
Bram Moolenaarb852c3e2018-03-11 16:55:36 +0100784
Bram Moolenaar13568252018-03-16 20:46:58 +0100785 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar28ed4df2019-10-26 16:21:40 +0200786 if (!opt->jo_hidden && !(flags & TERM_START_SYSTEM))
787 apply_autocmds(EVENT_TERMINALWINOPEN, NULL, NULL, FALSE, newbuf);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200788 return newbuf;
789}
790
791/*
792 * ":terminal": open a terminal window and execute a job in it.
793 */
794 void
795ex_terminal(exarg_T *eap)
796{
797 typval_T argvar[2];
798 jobopt_T opt;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100799 int opt_shell = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200800 char_u *cmd;
801 char_u *tofree = NULL;
802
803 init_job_options(&opt);
804
805 cmd = eap->arg;
Bram Moolenaara15ef452018-02-09 16:46:00 +0100806 while (*cmd == '+' && *(cmd + 1) == '+')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200807 {
808 char_u *p, *ep;
809
810 cmd += 2;
811 p = skiptowhite(cmd);
812 ep = vim_strchr(cmd, '=');
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200813 if (ep != NULL)
814 {
815 if (ep < p)
816 p = ep;
817 else
818 ep = NULL;
819 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200820
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200821# define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \
822 && STRNICMP(cmd, name, sizeof(name) - 1) == 0)
823 if (OPTARG_HAS("close"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200824 opt.jo_term_finish = 'c';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200825 else if (OPTARG_HAS("noclose"))
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100826 opt.jo_term_finish = 'n';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200827 else if (OPTARG_HAS("open"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200828 opt.jo_term_finish = 'o';
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200829 else if (OPTARG_HAS("curwin"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200830 opt.jo_curwin = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200831 else if (OPTARG_HAS("hidden"))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200832 opt.jo_hidden = 1;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200833 else if (OPTARG_HAS("norestore"))
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100834 opt.jo_term_norestore = 1;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100835 else if (OPTARG_HAS("shell"))
836 opt_shell = TRUE;
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200837 else if (OPTARG_HAS("kill") && ep != NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100838 {
839 opt.jo_set2 |= JO2_TERM_KILL;
840 opt.jo_term_kill = ep + 1;
841 p = skiptowhite(cmd);
842 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200843 else if (OPTARG_HAS("api"))
844 {
845 opt.jo_set2 |= JO2_TERM_API;
846 if (ep != NULL)
847 {
848 opt.jo_term_api = ep + 1;
849 p = skiptowhite(cmd);
850 }
851 else
852 opt.jo_term_api = NULL;
853 }
854 else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200855 {
856 opt.jo_set2 |= JO2_TERM_ROWS;
857 opt.jo_term_rows = atoi((char *)ep + 1);
858 p = skiptowhite(cmd);
859 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200860 else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1]))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200861 {
862 opt.jo_set2 |= JO2_TERM_COLS;
863 opt.jo_term_cols = atoi((char *)ep + 1);
864 p = skiptowhite(cmd);
865 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200866 else if (OPTARG_HAS("eof") && ep != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200867 {
868 char_u *buf = NULL;
869 char_u *keys;
870
Bram Moolenaar21109272020-01-30 16:27:20 +0100871 vim_free(opt.jo_eof_chars);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200872 p = skiptowhite(cmd);
873 *p = NUL;
zeertzjq7e0bae02023-08-11 23:15:38 +0200874 keys = replace_termcodes(ep + 1, &buf, 0,
Bram Moolenaar459fd782019-10-13 16:43:39 +0200875 REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200876 opt.jo_set2 |= JO2_EOF_CHARS;
877 opt.jo_eof_chars = vim_strsave(keys);
878 vim_free(buf);
879 *p = ' ';
880 }
Bram Moolenaar4f974752019-02-17 17:44:42 +0100881#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100882 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "type", 4) == 0
883 && ep != NULL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100884 {
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100885 int tty_type = NUL;
886
887 p = skiptowhite(cmd);
888 if (STRNICMP(ep + 1, "winpty", p - (ep + 1)) == 0)
889 tty_type = 'w';
890 else if (STRNICMP(ep + 1, "conpty", p - (ep + 1)) == 0)
891 tty_type = 'c';
892 else
893 {
Bram Moolenaar50809a42023-05-20 16:39:07 +0100894 semsg(_(e_invalid_value_for_argument_str), "type");
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100895 goto theend;
896 }
897 opt.jo_set2 |= JO2_TTY_TYPE;
898 opt.jo_tty_type = tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +0100899 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +0100900#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200901 else
902 {
903 if (*p)
904 *p = NUL;
Bram Moolenaard82a47d2022-01-05 20:24:39 +0000905 semsg(_(e_invalid_attribute_str), cmd);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100906 goto theend;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200907 }
Bram Moolenaard2842ea2019-09-26 23:08:54 +0200908# undef OPTARG_HAS
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200909 cmd = skipwhite(p);
910 }
911 if (*cmd == NUL)
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100912 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100913 // Make a copy of 'shell', an autocommand may change the option.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200914 tofree = cmd = vim_strsave(p_sh);
915
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100916 // default to close when the shell exits
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100917 if (opt.jo_term_finish == NUL)
Bram Moolenaare2978022020-04-26 14:47:44 +0200918 opt.jo_term_finish = TL_FINISH_CLOSE;
Bram Moolenaar1dd98332018-03-16 22:54:53 +0100919 }
920
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200921 if (eap->addr_count > 0)
922 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +0100923 // Write lines from current buffer to the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200924 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT;
925 opt.jo_io[PART_IN] = JIO_BUFFER;
926 opt.jo_io_buf[PART_IN] = curbuf->b_fnum;
927 opt.jo_in_top = eap->line1;
928 opt.jo_in_bot = eap->line2;
929 }
930
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100931 if (opt_shell && tofree == NULL)
932 {
933#ifdef UNIX
934 char **argv = NULL;
935 char_u *tofree1 = NULL;
936 char_u *tofree2 = NULL;
937
938 // :term ++shell command
939 if (unix_build_argv(cmd, &argv, &tofree1, &tofree2) == OK)
940 term_start(NULL, argv, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaaradf4aa22019-11-10 22:36:44 +0100941 vim_free(argv);
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100942 vim_free(tofree1);
943 vim_free(tofree2);
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100944 goto theend;
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100945#else
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100946# ifdef MSWIN
947 long_u cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10;
948 char_u *newcmd;
949
950 newcmd = alloc(cmdlen);
951 if (newcmd == NULL)
952 goto theend;
953 tofree = newcmd;
954 vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd);
955 cmd = newcmd;
956# else
Bram Moolenaar9a846fb2022-01-01 21:59:18 +0000957 emsg(_(e_sorry_plusplusshell_not_supported_on_this_system));
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100958 goto theend;
959# endif
Bram Moolenaar197c6b72019-11-03 23:37:12 +0100960#endif
961 }
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100962 argvar[0].v_type = VAR_STRING;
963 argvar[0].vval.v_string = cmd;
964 argvar[1].v_type = VAR_UNKNOWN;
965 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +0100966
967theend:
Bram Moolenaar2d6d76f2019-11-04 23:18:35 +0100968 vim_free(tofree);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +0200969 vim_free(opt.jo_eof_chars);
970}
971
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100972#if defined(FEAT_SESSION) || defined(PROTO)
973/*
974 * Write a :terminal command to the session file to restore the terminal in
975 * window "wp".
976 * Return FAIL if writing fails.
977 */
978 int
Bram Moolenaar0e655112020-09-11 20:36:36 +0200979term_write_session(FILE *fd, win_T *wp, hashtab_T *terminal_bufs)
Bram Moolenaar4d8bac82018-03-09 21:33:34 +0100980{
Bram Moolenaar0e655112020-09-11 20:36:36 +0200981 const int bufnr = wp->w_buffer->b_fnum;
982 term_T *term = wp->w_buffer->b_term;
983
Bram Moolenaarc2c82052020-09-11 22:10:22 +0200984 if (terminal_bufs != NULL && wp->w_buffer->b_nwindows > 1)
Bram Moolenaar0e655112020-09-11 20:36:36 +0200985 {
986 // There are multiple views into this terminal buffer. We don't want to
987 // create the terminal multiple times. If it's the first time, create,
988 // otherwise link to the first buffer.
989 char id_as_str[NUMBUFLEN];
990 hashitem_T *entry;
991
992 vim_snprintf(id_as_str, sizeof(id_as_str), "%d", bufnr);
993
994 entry = hash_find(terminal_bufs, (char_u *)id_as_str);
995 if (!HASHITEM_EMPTY(entry))
996 {
997 // we've already opened this terminal buffer
998 if (fprintf(fd, "execute 'buffer ' . s:term_buf_%d", bufnr) < 0)
999 return FAIL;
1000 return put_eol(fd);
1001 }
1002 }
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01001003
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001004 // Create the terminal and run the command. This is not without
1005 // risk, but let's assume the user only creates a session when this
1006 // will be OK.
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01001007 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ",
1008 term->tl_cols, term->tl_rows) < 0)
1009 return FAIL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01001010#ifdef MSWIN
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01001011 if (fprintf(fd, "++type=%s ", term->tl_job->jv_tty_type) < 0)
1012 return FAIL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001013#endif
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01001014 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0)
1015 return FAIL;
Bram Moolenaar0e655112020-09-11 20:36:36 +02001016 if (put_eol(fd) != OK)
1017 return FAIL;
1018
1019 if (fprintf(fd, "let s:term_buf_%d = bufnr()", bufnr) < 0)
1020 return FAIL;
1021
Bram Moolenaarc2c82052020-09-11 22:10:22 +02001022 if (terminal_bufs != NULL && wp->w_buffer->b_nwindows > 1)
Bram Moolenaar0e655112020-09-11 20:36:36 +02001023 {
1024 char *hash_key = alloc(NUMBUFLEN);
1025
1026 vim_snprintf(hash_key, NUMBUFLEN, "%d", bufnr);
Bram Moolenaaref2c3252022-11-25 16:31:51 +00001027 hash_add(terminal_bufs, (char_u *)hash_key, "terminal session");
Bram Moolenaar0e655112020-09-11 20:36:36 +02001028 }
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01001029
1030 return put_eol(fd);
1031}
1032
1033/*
1034 * Return TRUE if "buf" has a terminal that should be restored.
1035 */
1036 int
1037term_should_restore(buf_T *buf)
1038{
1039 term_T *term = buf->b_term;
1040
1041 return term != NULL && (term->tl_command == NULL
1042 || STRCMP(term->tl_command, "NONE") != 0);
1043}
1044#endif
1045
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001046/*
1047 * Free the scrollback buffer for "term".
1048 */
1049 static void
1050free_scrollback(term_T *term)
1051{
1052 int i;
1053
1054 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
1055 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
1056 ga_clear(&term->tl_scrollback);
Bram Moolenaar29ae2232019-02-14 21:22:01 +01001057 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
1058 vim_free(((sb_line_T *)term->tl_scrollback_postponed.ga_data + i)->sb_cells);
1059 ga_clear(&term->tl_scrollback_postponed);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001060}
1061
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001062
1063// Terminals that need to be freed soon.
Bram Moolenaar840d16f2019-09-10 21:27:18 +02001064static term_T *terminals_to_free = NULL;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001065
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001066/*
1067 * Free a terminal and everything it refers to.
1068 * Kills the job if there is one.
1069 * Called when wiping out a buffer.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001070 * The actual terminal structure is freed later in free_unused_terminals(),
1071 * because callbacks may wipe out a buffer while the terminal is still
1072 * referenced.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001073 */
1074 void
1075free_terminal(buf_T *buf)
1076{
1077 term_T *term = buf->b_term;
1078 term_T *tp;
1079
1080 if (term == NULL)
1081 return;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001082
1083 // Unlink the terminal form the list of terminals.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001084 if (first_term == term)
1085 first_term = term->tl_next;
1086 else
1087 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
1088 if (tp->tl_next == term)
1089 {
1090 tp->tl_next = term->tl_next;
1091 break;
1092 }
1093
1094 if (term->tl_job != NULL)
1095 {
1096 if (term->tl_job->jv_status != JOB_ENDED
1097 && term->tl_job->jv_status != JOB_FINISHED
Bram Moolenaard317b382018-02-08 22:33:31 +01001098 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001099 job_stop(term->tl_job, NULL, "kill");
1100 job_unref(term->tl_job);
1101 }
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001102 term->tl_next = terminals_to_free;
1103 terminals_to_free = term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001104
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001105 buf->b_term = NULL;
1106 if (in_terminal_loop == term)
1107 in_terminal_loop = NULL;
1108}
1109
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001110 void
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00001111free_unused_terminals(void)
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001112{
1113 while (terminals_to_free != NULL)
1114 {
1115 term_T *term = terminals_to_free;
1116
1117 terminals_to_free = term->tl_next;
1118
1119 free_scrollback(term);
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02001120 ga_clear(&term->tl_osc_buf);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001121
1122 term_free_vterm(term);
Bram Moolenaard2842ea2019-09-26 23:08:54 +02001123 vim_free(term->tl_api);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001124 vim_free(term->tl_title);
1125#ifdef FEAT_SESSION
1126 vim_free(term->tl_command);
1127#endif
1128 vim_free(term->tl_kill);
1129 vim_free(term->tl_status_text);
1130 vim_free(term->tl_opencmd);
1131 vim_free(term->tl_eof_chars);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01001132 vim_free(term->tl_arg0_cmd);
Bram Moolenaar4f974752019-02-17 17:44:42 +01001133#ifdef MSWIN
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001134 if (term->tl_out_fd != NULL)
1135 fclose(term->tl_out_fd);
1136#endif
Bram Moolenaar83d47902020-03-26 20:34:00 +01001137 vim_free(term->tl_highlight_name);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001138 vim_free(term->tl_cursor_color);
LemonBoyb2b3acb2022-05-20 10:10:34 +01001139 vim_free(term->tl_palette);
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001140 vim_free(term);
1141 }
1142}
1143
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001144/*
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001145 * Get the part that is connected to the tty. Normally this is PART_IN, but
1146 * when writing buffer lines to the job it can be another. This makes it
1147 * possible to do "1,5term vim -".
1148 */
1149 static ch_part_T
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02001150get_tty_part(term_T *term UNUSED)
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001151{
1152#ifdef UNIX
1153 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR};
1154 int i;
1155
1156 for (i = 0; i < 3; ++i)
1157 {
1158 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd;
1159
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01001160 if (mch_isatty(fd))
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001161 return parts[i];
1162 }
1163#endif
1164 return PART_IN;
1165}
1166
1167/*
Bram Moolenaara48d4e42021-12-08 22:13:38 +00001168 * Read any vterm output and send it on the channel.
1169 */
1170 static void
1171term_forward_output(term_T *term)
1172{
1173 VTerm *vterm = term->tl_vterm;
1174 char buf[KEY_BUF_LEN];
1175 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN);
1176
1177 if (curlen > 0)
1178 channel_send(term->tl_job->jv_channel, get_tty_part(term),
1179 (char_u *)buf, (int)curlen, NULL);
1180}
1181
1182/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001183 * Write job output "msg[len]" to the vterm.
1184 */
1185 static void
Bram Moolenaar36968af2021-11-15 17:13:11 +00001186term_write_job_output(term_T *term, char_u *msg_arg, size_t len_arg)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001187{
Bram Moolenaar36968af2021-11-15 17:13:11 +00001188 char_u *msg = msg_arg;
1189 size_t len = len_arg;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001190 VTerm *vterm = term->tl_vterm;
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001191 size_t prevlen = vterm_output_get_buffer_current(vterm);
Bram Moolenaar36968af2021-11-15 17:13:11 +00001192 size_t limit = term->tl_buffer->b_p_twsl * term->tl_cols * 3;
1193
1194 // Limit the length to 'termwinscroll' * cols * 3 bytes. Keep the text at
1195 // the end.
1196 if (len > limit)
1197 {
1198 char_u *p = msg + len - limit;
1199
1200 p -= (*mb_head_off)(msg, p);
1201 len -= p - msg;
1202 msg = p;
1203 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001204
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001205 vterm_input_write(vterm, (char *)msg, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001206
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001207 // flush vterm buffer when vterm responded to control sequence
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001208 if (prevlen != vterm_output_get_buffer_current(vterm))
Bram Moolenaara48d4e42021-12-08 22:13:38 +00001209 term_forward_output(term);
Bram Moolenaarb50773c2018-01-30 22:31:19 +01001210
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001211 // this invokes the damage callbacks
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001212 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
1213}
1214
1215 static void
Bram Moolenaar58806c12023-04-29 14:26:02 +01001216position_cursor(win_T *wp, VTermPos *pos)
1217{
1218 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
1219 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
1220#ifdef FEAT_PROP_POPUP
1221 if (popup_is_popup(wp))
1222 {
1223 wp->w_wrow += popup_top_extra(wp);
1224 wp->w_wcol += popup_left_extra(wp);
1225 wp->w_flags |= WFLAG_WCOL_OFF_ADDED | WFLAG_WROW_OFF_ADDED;
1226 }
1227 else
1228 wp->w_flags &= ~(WFLAG_WCOL_OFF_ADDED | WFLAG_WROW_OFF_ADDED);
1229#endif
1230 wp->w_valid |= (VALID_WCOL|VALID_WROW);
1231}
1232
1233 static void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001234update_cursor(term_T *term, int redraw)
1235{
1236 if (term->tl_normal_mode)
1237 return;
Bram Moolenaar13568252018-03-16 20:46:58 +01001238#ifdef FEAT_GUI
1239 if (term->tl_system)
1240 windgoto(term->tl_cursor_pos.row + term->tl_toprow,
1241 term->tl_cursor_pos.col);
1242 else
1243#endif
Bram Moolenaar58806c12023-04-29 14:26:02 +01001244 if (!term_job_running(term))
1245 // avoid the cursor positioned below the last used line
Bram Moolenaar13568252018-03-16 20:46:58 +01001246 setcursor();
Bram Moolenaar58806c12023-04-29 14:26:02 +01001247 else
1248 {
1249 // do not use the window cursor position
1250 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
1251 windgoto(W_WINROW(curwin) + curwin->w_wrow,
1252 curwin->w_wincol + curwin->w_wcol);
1253 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001254 if (redraw)
1255 {
Shougo Matsushita4ccaedf2022-10-15 11:48:00 +01001256 aco_save_T aco;
1257
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001258 if (term->tl_buffer == curbuf && term->tl_cursor_visible)
1259 cursor_on();
1260 out_flush();
1261#ifdef FEAT_GUI
1262 if (gui.in_use)
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001263 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001264 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar23c1b2b2017-12-05 21:32:33 +01001265 gui_mch_flush();
1266 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001267#endif
Bram Moolenaar24fe33a2022-11-24 00:09:02 +00001268 // Make sure an invoked autocmd doesn't delete the buffer (and the
1269 // terminal) under our fingers.
Shougo Matsushita4ccaedf2022-10-15 11:48:00 +01001270 ++term->tl_buffer->b_locked;
1271
1272 // save and restore curwin and curbuf, in case the autocmd changes them
1273 aucmd_prepbuf(&aco, curbuf);
1274 apply_autocmds(EVENT_TEXTCHANGEDT, NULL, NULL, FALSE, term->tl_buffer);
1275 aucmd_restbuf(&aco);
1276
1277 --term->tl_buffer->b_locked;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001278 }
1279}
1280
1281/*
1282 * Invoked when "msg" output from a job was received. Write it to the terminal
1283 * of "buffer".
1284 */
1285 void
1286write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
1287{
1288 size_t len = STRLEN(msg);
1289 term_T *term = buffer->b_term;
1290
Bram Moolenaar4f974752019-02-17 17:44:42 +01001291#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001292 // Win32: Cannot redirect output of the job, intercept it here and write to
1293 // the file.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02001294 if (term->tl_out_fd != NULL)
1295 {
1296 ch_log(channel, "Writing %d bytes to output file", (int)len);
1297 fwrite(msg, len, 1, term->tl_out_fd);
1298 return;
1299 }
1300#endif
1301
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001302 if (term->tl_vterm == NULL)
1303 {
1304 ch_log(channel, "NOT writing %d bytes to terminal", (int)len);
1305 return;
1306 }
1307 ch_log(channel, "writing %d bytes to terminal", (int)len);
Bram Moolenaarebec3e22020-11-28 20:22:06 +01001308 cursor_off();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001309 term_write_job_output(term, msg, len);
1310
Bram Moolenaar13568252018-03-16 20:46:58 +01001311#ifdef FEAT_GUI
1312 if (term->tl_system)
1313 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001314 // show system output, scrolling up the screen as needed
Bram Moolenaar13568252018-03-16 20:46:58 +01001315 update_system_term(term);
1316 update_cursor(term, TRUE);
1317 }
1318 else
1319#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001320 // In Terminal-Normal mode we are displaying the buffer, not the terminal
1321 // contents, thus no screen update is needed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001322 if (!term->tl_normal_mode)
1323 {
Bram Moolenaar0ce74132018-06-18 22:15:50 +02001324 // Don't use update_screen() when editing the command line, it gets
1325 // cleared.
1326 // TODO: only update once in a while.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001327 ch_log(term->tl_job->jv_channel, "updating screen");
Bram Moolenaar24959102022-05-07 20:01:16 +01001328 if (buffer == curbuf && (State & MODE_CMDLINE) == 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001329 {
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001330 update_screen(UPD_VALID_NO_UPDATE);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001331 // update_screen() can be slow, check the terminal wasn't closed
1332 // already
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02001333 if (buffer == curbuf && curbuf->b_term != NULL)
1334 update_cursor(curbuf->b_term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001335 }
1336 else
Bram Moolenaare5050712021-12-09 10:51:05 +00001337 redraw_after_callback(TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001338 }
1339}
1340
1341/*
1342 * Send a mouse position and click to the vterm
1343 */
1344 static int
1345term_send_mouse(VTerm *vterm, int button, int pressed)
1346{
1347 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01001348 int row = mouse_row - W_WINROW(curwin);
1349 int col = mouse_col - curwin->w_wincol;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001350
Bram Moolenaar219c7d02020-02-01 21:57:29 +01001351#ifdef FEAT_PROP_POPUP
1352 if (popup_is_popup(curwin))
1353 {
1354 row -= popup_top_extra(curwin);
1355 col -= popup_left_extra(curwin);
1356 }
1357#endif
1358 vterm_mouse_move(vterm, row, col, mod);
Bram Moolenaar51b0f372017-11-18 18:52:04 +01001359 if (button != 0)
1360 vterm_mouse_button(vterm, button, pressed, mod);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001361 return TRUE;
1362}
1363
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001364static int enter_mouse_col = -1;
1365static int enter_mouse_row = -1;
1366
1367/*
1368 * Handle a mouse click, drag or release.
1369 * Return TRUE when a mouse event is sent to the terminal.
1370 */
1371 static int
1372term_mouse_click(VTerm *vterm, int key)
1373{
1374#if defined(FEAT_CLIPBOARD)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001375 // For modeless selection mouse drag and release events are ignored, unless
1376 // they are preceded with a mouse down event
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001377 static int ignore_drag_release = TRUE;
1378 VTermMouseState mouse_state;
1379
1380 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state);
1381 if (mouse_state.flags == 0)
1382 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001383 // Terminal is not using the mouse, use modeless selection.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001384 switch (key)
1385 {
1386 case K_LEFTDRAG:
1387 case K_LEFTRELEASE:
1388 case K_RIGHTDRAG:
1389 case K_RIGHTRELEASE:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001390 // Ignore drag and release events when the button-down wasn't
1391 // seen before.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001392 if (ignore_drag_release)
1393 {
1394 int save_mouse_col, save_mouse_row;
1395
1396 if (enter_mouse_col < 0)
1397 break;
1398
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001399 // mouse click in the window gave us focus, handle that
1400 // click now
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001401 save_mouse_col = mouse_col;
1402 save_mouse_row = mouse_row;
1403 mouse_col = enter_mouse_col;
1404 mouse_row = enter_mouse_row;
1405 clip_modeless(MOUSE_LEFT, TRUE, FALSE);
1406 mouse_col = save_mouse_col;
1407 mouse_row = save_mouse_row;
1408 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001409 // FALLTHROUGH
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001410 case K_LEFTMOUSE:
1411 case K_RIGHTMOUSE:
1412 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE)
1413 ignore_drag_release = TRUE;
1414 else
1415 ignore_drag_release = FALSE;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001416 // Should we call mouse_has() here?
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001417 if (clip_star.available)
1418 {
1419 int button, is_click, is_drag;
1420
1421 button = get_mouse_button(KEY2TERMCAP1(key),
1422 &is_click, &is_drag);
1423 if (mouse_model_popup() && button == MOUSE_LEFT
1424 && (mod_mask & MOD_MASK_SHIFT))
1425 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001426 // Translate shift-left to right button.
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001427 button = MOUSE_RIGHT;
1428 mod_mask &= ~MOD_MASK_SHIFT;
1429 }
1430 clip_modeless(button, is_click, is_drag);
1431 }
1432 break;
1433
1434 case K_MIDDLEMOUSE:
1435 if (clip_star.available)
1436 insert_reg('*', TRUE);
1437 break;
1438 }
1439 enter_mouse_col = -1;
1440 return FALSE;
1441 }
1442#endif
1443 enter_mouse_col = -1;
1444
1445 switch (key)
1446 {
1447 case K_LEFTMOUSE:
1448 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break;
1449 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break;
1450 case K_LEFTRELEASE:
1451 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break;
1452 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break;
1453 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break;
1454 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break;
1455 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break;
1456 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break;
1457 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break;
1458 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break;
1459 }
1460 return TRUE;
1461}
1462
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001463/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001464 * Convert typed key "c" with modifiers "modmask" into bytes to send to the
1465 * job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001466 * Return the number of bytes in "buf".
1467 */
1468 static int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001469term_convert_key(term_T *term, int c, int modmask, char *buf)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001470{
1471 VTerm *vterm = term->tl_vterm;
1472 VTermKey key = VTERM_KEY_NONE;
1473 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001474 int other = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001475
1476 switch (c)
1477 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001478 // don't use VTERM_KEY_ENTER, it may do an unwanted conversion
Bram Moolenaar26d205d2017-11-09 17:33:11 +01001479
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001480 // don't use VTERM_KEY_BACKSPACE, it always
1481 // becomes 0x7f DEL
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001482 case K_BS: c = term_backspace_char; break;
1483
1484 case ESC: key = VTERM_KEY_ESCAPE; break;
1485 case K_DEL: key = VTERM_KEY_DEL; break;
1486 case K_DOWN: key = VTERM_KEY_DOWN; break;
1487 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
1488 key = VTERM_KEY_DOWN; break;
1489 case K_END: key = VTERM_KEY_END; break;
1490 case K_S_END: mod = VTERM_MOD_SHIFT;
1491 key = VTERM_KEY_END; break;
1492 case K_C_END: mod = VTERM_MOD_CTRL;
1493 key = VTERM_KEY_END; break;
1494 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
1495 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
1496 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
1497 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
1498 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
1499 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
1500 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
1501 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
1502 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
1503 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
1504 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
1505 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
1506 case K_HOME: key = VTERM_KEY_HOME; break;
1507 case K_S_HOME: mod = VTERM_MOD_SHIFT;
1508 key = VTERM_KEY_HOME; break;
1509 case K_C_HOME: mod = VTERM_MOD_CTRL;
1510 key = VTERM_KEY_HOME; break;
1511 case K_INS: key = VTERM_KEY_INS; break;
1512 case K_K0: key = VTERM_KEY_KP_0; break;
1513 case K_K1: key = VTERM_KEY_KP_1; break;
1514 case K_K2: key = VTERM_KEY_KP_2; break;
1515 case K_K3: key = VTERM_KEY_KP_3; break;
1516 case K_K4: key = VTERM_KEY_KP_4; break;
1517 case K_K5: key = VTERM_KEY_KP_5; break;
1518 case K_K6: key = VTERM_KEY_KP_6; break;
1519 case K_K7: key = VTERM_KEY_KP_7; break;
1520 case K_K8: key = VTERM_KEY_KP_8; break;
1521 case K_K9: key = VTERM_KEY_KP_9; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001522 case K_KDEL: key = VTERM_KEY_DEL; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001523 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001524 case K_KEND: key = VTERM_KEY_KP_1; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001525 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001526 case K_KHOME: key = VTERM_KEY_KP_7; break; // TODO
1527 case K_KINS: key = VTERM_KEY_KP_0; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001528 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
1529 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001530 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; // TODO
1531 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; // TODO
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001532 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
1533 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
1534 case K_LEFT: key = VTERM_KEY_LEFT; break;
1535 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
1536 key = VTERM_KEY_LEFT; break;
1537 case K_C_LEFT: mod = VTERM_MOD_CTRL;
1538 key = VTERM_KEY_LEFT; break;
1539 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
1540 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
1541 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
1542 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
1543 key = VTERM_KEY_RIGHT; break;
1544 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
1545 key = VTERM_KEY_RIGHT; break;
1546 case K_UP: key = VTERM_KEY_UP; break;
1547 case K_S_UP: mod = VTERM_MOD_SHIFT;
1548 key = VTERM_KEY_UP; break;
1549 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaar73cddfd2018-02-16 20:01:04 +01001550 case K_S_TAB: mod = VTERM_MOD_SHIFT;
1551 key = VTERM_KEY_TAB; break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001552
Bram Moolenaara42ad572017-11-16 13:08:04 +01001553 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break;
1554 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02001555 case K_MOUSELEFT: other = term_send_mouse(vterm, 7, 1); break;
1556 case K_MOUSERIGHT: other = term_send_mouse(vterm, 6, 1); break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001557
1558 case K_LEFTMOUSE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001559 case K_LEFTMOUSE_NM:
1560 case K_LEFTDRAG:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001561 case K_LEFTRELEASE:
Bram Moolenaarc48369c2018-03-11 19:30:45 +01001562 case K_LEFTRELEASE_NM:
1563 case K_MOUSEMOVE:
1564 case K_MIDDLEMOUSE:
1565 case K_MIDDLEDRAG:
1566 case K_MIDDLERELEASE:
1567 case K_RIGHTMOUSE:
1568 case K_RIGHTDRAG:
1569 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c))
1570 return 0;
1571 other = TRUE;
1572 break;
1573
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001574 case K_X1MOUSE: /* TODO */ return 0;
1575 case K_X1DRAG: /* TODO */ return 0;
1576 case K_X1RELEASE: /* TODO */ return 0;
1577 case K_X2MOUSE: /* TODO */ return 0;
1578 case K_X2DRAG: /* TODO */ return 0;
1579 case K_X2RELEASE: /* TODO */ return 0;
1580
1581 case K_IGNORE: return 0;
1582 case K_NOP: return 0;
1583 case K_UNDO: return 0;
1584 case K_HELP: return 0;
1585 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
1586 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
1587 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
1588 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
1589 case K_SELECT: return 0;
1590#ifdef FEAT_GUI
1591 case K_VER_SCROLLBAR: return 0;
1592 case K_HOR_SCROLLBAR: return 0;
1593#endif
1594#ifdef FEAT_GUI_TABLINE
1595 case K_TABLINE: return 0;
1596 case K_TABMENU: return 0;
1597#endif
1598#ifdef FEAT_NETBEANS_INTG
1599 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
1600#endif
1601#ifdef FEAT_DND
1602 case K_DROP: return 0;
1603#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001604 case K_CURSORHOLD: return 0;
Bram Moolenaara42ad572017-11-16 13:08:04 +01001605 case K_PS: vterm_keyboard_start_paste(vterm);
1606 other = TRUE;
1607 break;
1608 case K_PE: vterm_keyboard_end_paste(vterm);
1609 other = TRUE;
1610 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001611 }
1612
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001613 // add modifiers for the typed key
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001614 if (modmask & MOD_MASK_SHIFT)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001615 mod |= VTERM_MOD_SHIFT;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001616 if (modmask & MOD_MASK_CTRL)
Bram Moolenaar459fd782019-10-13 16:43:39 +02001617 mod |= VTERM_MOD_CTRL;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01001618 if (modmask & (MOD_MASK_ALT | MOD_MASK_META))
Bram Moolenaar459fd782019-10-13 16:43:39 +02001619 mod |= VTERM_MOD_ALT;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02001620
Bram Moolenaar63a2e362022-11-23 20:20:18 +00001621 // Ctrl-Shift-i may have the key "I" instead of "i", but for the kitty
1622 // keyboard protocol should use "i". Applies to all ascii letters.
1623 if (ASCII_ISUPPER(c)
Bram Moolenaarebed1b02022-11-24 14:05:19 +00001624 && vterm_is_kitty_keyboard(vterm)
Bram Moolenaar63a2e362022-11-23 20:20:18 +00001625 && mod == (VTERM_MOD_CTRL | VTERM_MOD_SHIFT))
1626 c = TOLOWER_ASC(c);
1627
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001628 /*
1629 * Convert special keys to vterm keys:
1630 * - Write keys to vterm: vterm_keyboard_key()
1631 * - Write output to channel.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001632 */
1633 if (key != VTERM_KEY_NONE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001634 // Special key, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001635 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaara42ad572017-11-16 13:08:04 +01001636 else if (!other)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001637 // Normal character, let vterm convert it.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001638 vterm_keyboard_unichar(vterm, c, mod);
1639
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001640 // Read back the converted escape sequence.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001641 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
1642}
1643
1644/*
1645 * Return TRUE if the job for "term" is still running.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001646 * If "check_job_status" is TRUE update the job status.
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001647 * NOTE: "term" may be freed by callbacks.
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001648 */
1649 static int
1650term_job_running_check(term_T *term, int check_job_status)
1651{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001652 // Also consider the job finished when the channel is closed, to avoid a
1653 // race condition when updating the title.
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00001654 if (term == NULL
1655 || term->tl_job == NULL
1656 || !channel_is_open(term->tl_job->jv_channel))
1657 return FALSE;
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01001658
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00001659 job_T *job = term->tl_job;
1660
1661 // Careful: Checking the job status may invoke callbacks, which close
1662 // the buffer and terminate "term". However, "job" will not be freed
1663 // yet.
1664 if (check_job_status)
1665 job_status(job);
1666 return (job->jv_status == JOB_STARTED
1667 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open));
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001668}
1669
1670/*
1671 * Return TRUE if the job for "term" is still running.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001672 */
1673 int
1674term_job_running(term_T *term)
1675{
Bram Moolenaar802bfb12018-04-15 17:28:13 +02001676 return term_job_running_check(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001677}
1678
1679/*
Bram Moolenaar9e636b92022-05-29 22:37:05 +01001680 * Return TRUE if the job for "term" is still running, ignoring the job was
1681 * "NONE".
1682 */
1683 int
1684term_job_running_not_none(term_T *term)
1685{
1686 return term_job_running(term) && !term_none_open(term);
1687}
1688
1689/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001690 * Return TRUE if "term" has an active channel and used ":term NONE".
1691 */
1692 int
1693term_none_open(term_T *term)
1694{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001695 // Also consider the job finished when the channel is closed, to avoid a
1696 // race condition when updating the title.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001697 return term != NULL
1698 && term->tl_job != NULL
1699 && channel_is_open(term->tl_job->jv_channel)
1700 && term->tl_job->jv_channel->ch_keep_open;
1701}
1702
Yee Cheng Chin15b314f2022-10-09 18:53:32 +01001703//
1704// Used to confirm whether we would like to kill a terminal.
1705// Return OK when the user confirms to kill it.
1706// Return FAIL if the user selects otherwise.
1707//
1708 int
1709term_confirm_stop(buf_T *buf)
1710{
1711 char_u buff[DIALOG_MSG_SIZE];
1712 int ret;
1713
1714 dialog_msg(buff, _("Kill job in \"%s\"?"), buf_get_fname(buf));
1715 ret = vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1);
1716 if (ret == VIM_YES)
1717 return OK;
1718 else
1719 return FAIL;
1720}
1721
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001722/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001723 * Used when exiting: kill the job in "buf" if so desired.
1724 * Return OK when the job finished.
1725 * Return FAIL when the job is still running.
1726 */
1727 int
1728term_try_stop_job(buf_T *buf)
1729{
1730 int count;
1731 char *how = (char *)buf->b_term->tl_kill;
1732
1733#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
Bram Moolenaare1004402020-10-24 20:49:43 +02001734 if ((how == NULL || *how == NUL)
1735 && (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)))
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001736 {
Yee Cheng Chin15b314f2022-10-09 18:53:32 +01001737 if (term_confirm_stop(buf) == OK)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001738 how = "kill";
Yee Cheng Chin15b314f2022-10-09 18:53:32 +01001739 else
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001740 return FAIL;
1741 }
1742#endif
1743 if (how == NULL || *how == NUL)
1744 return FAIL;
1745
1746 job_stop(buf->b_term->tl_job, NULL, how);
1747
Bram Moolenaar9172d232019-01-29 23:06:54 +01001748 // wait for up to a second for the job to die
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001749 for (count = 0; count < 100; ++count)
1750 {
Bram Moolenaar9172d232019-01-29 23:06:54 +01001751 job_T *job;
1752
1753 // buffer, terminal and job may be cleaned up while waiting
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001754 if (!buf_valid(buf)
1755 || buf->b_term == NULL
1756 || buf->b_term->tl_job == NULL)
1757 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001758 job = buf->b_term->tl_job;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001759
Bram Moolenaar9172d232019-01-29 23:06:54 +01001760 // Call job_status() to update jv_status. It may cause the job to be
1761 // cleaned up but it won't be freed.
1762 job_status(job);
1763 if (job->jv_status >= JOB_ENDED)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001764 return OK;
Bram Moolenaar9172d232019-01-29 23:06:54 +01001765
Bram Moolenaar8f7ab4b2019-10-23 23:16:45 +02001766 ui_delay(10L, TRUE);
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02001767 term_flush_messages();
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01001768 }
1769 return FAIL;
1770}
1771
1772/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001773 * Add the last line of the scrollback buffer to the buffer in the window.
1774 */
1775 static void
1776add_scrollback_line_to_buffer(term_T *term, char_u *text, int len)
1777{
1778 buf_T *buf = term->tl_buffer;
1779 int empty = (buf->b_ml.ml_flags & ML_EMPTY);
1780 linenr_T lnum = buf->b_ml.ml_line_count;
1781
Bram Moolenaar4f974752019-02-17 17:44:42 +01001782#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001783 if (!enc_utf8 && enc_codepage > 0)
1784 {
1785 WCHAR *ret = NULL;
1786 int length = 0;
1787
1788 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1,
1789 &ret, &length);
1790 if (ret != NULL)
1791 {
1792 WideCharToMultiByte_alloc(enc_codepage, 0,
1793 ret, length, (char **)&text, &len, 0, 0);
1794 vim_free(ret);
1795 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE);
1796 vim_free(text);
1797 }
1798 }
1799 else
1800#endif
1801 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE);
1802 if (empty)
1803 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001804 // Delete the empty line that was in the empty buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001805 curbuf = buf;
Bram Moolenaarca70c072020-05-30 20:30:46 +02001806 ml_delete(1);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001807 curbuf = curwin->w_buffer;
1808 }
1809}
1810
1811 static void
1812cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr)
1813{
1814 attr->width = cell->width;
1815 attr->attrs = cell->attrs;
1816 attr->fg = cell->fg;
1817 attr->bg = cell->bg;
1818}
1819
1820 static int
1821equal_celattr(cellattr_T *a, cellattr_T *b)
1822{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02001823 // We only compare the RGB colors, ignoring the ANSI index and type.
1824 // Thus black set explicitly is equal the background black.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001825 return a->fg.red == b->fg.red
1826 && a->fg.green == b->fg.green
1827 && a->fg.blue == b->fg.blue
1828 && a->bg.red == b->bg.red
1829 && a->bg.green == b->bg.green
1830 && a->bg.blue == b->bg.blue;
1831}
1832
Bram Moolenaard96ff162018-02-18 22:13:29 +01001833/*
1834 * Add an empty scrollback line to "term". When "lnum" is not zero, add the
1835 * line at this position. Otherwise at the end.
1836 */
1837 static int
1838add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum)
1839{
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00001840 if (ga_grow(&term->tl_scrollback, 1) == FAIL)
1841 return FALSE;
1842
1843 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1844 + term->tl_scrollback.ga_len;
1845
1846 if (lnum > 0)
Bram Moolenaard96ff162018-02-18 22:13:29 +01001847 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00001848 int i;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001849
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00001850 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i)
Bram Moolenaard96ff162018-02-18 22:13:29 +01001851 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00001852 *line = *(line - 1);
1853 --line;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001854 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01001855 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00001856 line->sb_cols = 0;
1857 line->sb_cells = NULL;
1858 line->sb_fill_attr = *fill_attr;
1859 ++term->tl_scrollback.ga_len;
1860 return OK;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001861}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001862
1863/*
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001864 * Remove the terminal contents from the scrollback and the buffer.
1865 * Used before adding a new scrollback line or updating the buffer for lines
1866 * displayed in the terminal.
1867 */
1868 static void
1869cleanup_scrollback(term_T *term)
1870{
1871 sb_line_T *line;
1872 garray_T *gap;
1873
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001874 curbuf = term->tl_buffer;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001875 gap = &term->tl_scrollback;
1876 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled
1877 && gap->ga_len > 0)
1878 {
Bram Moolenaarca70c072020-05-30 20:30:46 +02001879 ml_delete(curbuf->b_ml.ml_line_count);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001880 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
1881 vim_free(line->sb_cells);
1882 --gap->ga_len;
1883 }
Bram Moolenaar3f1a53c2018-05-12 16:55:14 +02001884 curbuf = curwin->w_buffer;
1885 if (curbuf == term->tl_buffer)
1886 check_cursor();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001887}
1888
1889/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001890 * Add the current lines of the terminal to scrollback and to the buffer.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001891 */
1892 static void
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001893update_snapshot(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001894{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02001895 VTermScreen *screen;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001896 int len;
1897 int lines_skipped = 0;
1898 VTermPos pos;
1899 VTermScreenCell cell;
1900 cellattr_T fill_attr, new_fill_attr;
1901 cellattr_T *p;
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001902
1903 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel,
1904 "Adding terminal window snapshot to buffer");
1905
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001906 // First remove the lines that were appended before, they might be
1907 // outdated.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02001908 cleanup_scrollback(term);
1909
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001910 screen = vterm_obtain_screen(term->tl_vterm);
1911 fill_attr = new_fill_attr = term->tl_default_color;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001912 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
1913 {
1914 len = 0;
1915 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
1916 if (vterm_screen_get_cell(screen, pos, &cell) != 0
1917 && cell.chars[0] != NUL)
1918 {
1919 len = pos.col + 1;
1920 new_fill_attr = term->tl_default_color;
1921 }
1922 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001923 // Assume the last attr is the filler attr.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001924 cell2cellattr(&cell, &new_fill_attr);
1925
1926 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr))
1927 ++lines_skipped;
1928 else
1929 {
1930 while (lines_skipped > 0)
1931 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01001932 // Line was skipped, add an empty line.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001933 --lines_skipped;
Bram Moolenaard96ff162018-02-18 22:13:29 +01001934 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001935 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001936 }
1937
1938 if (len == 0)
1939 p = NULL;
1940 else
Bram Moolenaarc799fe22019-05-28 23:08:19 +02001941 p = ALLOC_MULT(cellattr_T, len);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001942 if ((p != NULL || len == 0)
1943 && ga_grow(&term->tl_scrollback, 1) == OK)
1944 {
1945 garray_T ga;
1946 int width;
1947 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
1948 + term->tl_scrollback.ga_len;
1949
1950 ga_init2(&ga, 1, 100);
1951 for (pos.col = 0; pos.col < len; pos.col += width)
1952 {
1953 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1954 {
1955 width = 1;
Bram Moolenaara80faa82020-04-12 19:37:17 +02001956 CLEAR_POINTER(p + pos.col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001957 if (ga_grow(&ga, 1) == OK)
1958 ga.ga_len += utf_char2bytes(' ',
1959 (char_u *)ga.ga_data + ga.ga_len);
1960 }
1961 else
1962 {
1963 width = cell.width;
1964
1965 cell2cellattr(&cell, &p[pos.col]);
Bram Moolenaar927495b2020-11-06 17:58:35 +01001966 if (width == 2)
1967 // second cell of double-width character has the
1968 // same attributes.
1969 p[pos.col + 1] = p[pos.col];
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001970
Bram Moolenaara79fd562018-12-20 20:47:32 +01001971 // Each character can be up to 6 bytes.
1972 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02001973 {
1974 int i;
1975 int c;
1976
1977 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i)
1978 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
1979 (char_u *)ga.ga_data + ga.ga_len);
1980 }
1981 }
1982 }
1983 line->sb_cols = len;
1984 line->sb_cells = p;
1985 line->sb_fill_attr = new_fill_attr;
1986 fill_attr = new_fill_attr;
1987 ++term->tl_scrollback.ga_len;
1988
1989 if (ga_grow(&ga, 1) == FAIL)
1990 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
1991 else
1992 {
1993 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
1994 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len);
1995 }
1996 ga_clear(&ga);
1997 }
1998 else
1999 vim_free(p);
2000 }
2001 }
2002
Bram Moolenaarf3aea592018-11-11 22:18:21 +01002003 // Add trailing empty lines.
2004 for (pos.row = term->tl_scrollback.ga_len;
2005 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row;
2006 ++pos.row)
2007 {
2008 if (add_empty_scrollback(term, &fill_attr, 0) == OK)
2009 add_scrollback_line_to_buffer(term, (char_u *)"", 0);
2010 }
2011
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002012 term->tl_dirty_snapshot = FALSE;
2013#ifdef FEAT_TIMERS
2014 term->tl_timer_set = FALSE;
2015#endif
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002016}
2017
2018/*
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002019 * Loop over all windows in the current tab, and also curwin, which is not
2020 * encountered when using a terminal in a popup window.
2021 * Return TRUE if "*wp" was set to the next window.
2022 */
2023 static int
2024for_all_windows_and_curwin(win_T **wp, int *did_curwin)
2025{
2026 if (*wp == NULL)
2027 *wp = firstwin;
2028 else if ((*wp)->w_next != NULL)
2029 *wp = (*wp)->w_next;
2030 else if (!*did_curwin)
2031 *wp = curwin;
2032 else
2033 return FALSE;
2034 if (*wp == curwin)
2035 *did_curwin = TRUE;
2036 return TRUE;
2037}
2038
2039/*
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002040 * If needed, add the current lines of the terminal to scrollback and to the
2041 * buffer. Called after the job has ended and when switching to
2042 * Terminal-Normal mode.
2043 * When "redraw" is TRUE redraw the windows that show the terminal.
2044 */
2045 static void
2046may_move_terminal_to_buffer(term_T *term, int redraw)
2047{
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002048 if (term->tl_vterm == NULL)
2049 return;
2050
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002051 // Update the snapshot only if something changes or the buffer does not
2052 // have all the lines.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002053 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count
2054 <= term->tl_scrollback_scrolled)
2055 update_snapshot(term);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002056
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002057 // Obtain the current background color.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002058 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
2059 &term->tl_default_color.fg, &term->tl_default_color.bg);
2060
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002061 if (redraw)
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002062 {
2063 win_T *wp = NULL;
2064 int did_curwin = FALSE;
2065
2066 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002067 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002068 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002069 {
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002070 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
2071 wp->w_cursor.col = 0;
2072 wp->w_valid = 0;
2073 if (wp->w_cursor.lnum >= wp->w_height)
2074 {
2075 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002076
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002077 if (wp->w_topline < min_topline)
2078 wp->w_topline = min_topline;
2079 }
Bram Moolenaara4d158b2022-08-14 14:17:45 +01002080 redraw_win_later(wp, UPD_NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002081 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002082 }
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002083 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002084}
2085
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002086#if defined(FEAT_TIMERS) || defined(PROTO)
2087/*
2088 * Check if any terminal timer expired. If so, copy text from the terminal to
2089 * the buffer.
2090 * Return the time until the next timer will expire.
2091 */
2092 int
2093term_check_timers(int next_due_arg, proftime_T *now)
2094{
2095 term_T *term;
2096 int next_due = next_due_arg;
2097
Bram Moolenaaraeea7212020-04-02 18:50:46 +02002098 FOR_ALL_TERMS(term)
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002099 {
2100 if (term->tl_timer_set && !term->tl_normal_mode)
2101 {
2102 long this_due = proftime_time_left(&term->tl_timer_due, now);
2103
2104 if (this_due <= 1)
2105 {
2106 term->tl_timer_set = FALSE;
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002107 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002108 }
2109 else if (next_due == -1 || next_due > this_due)
2110 next_due = this_due;
2111 }
2112 }
2113
2114 return next_due;
2115}
2116#endif
2117
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002118/*
2119 * When "normal_mode" is TRUE set the terminal to Terminal-Normal mode,
2120 * otherwise end it.
2121 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002122 static void
2123set_terminal_mode(term_T *term, int normal_mode)
2124{
2125 term->tl_normal_mode = normal_mode;
LemonBoy2bf52dd2022-04-09 18:17:34 +01002126 may_trigger_modechanged();
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002127 if (!normal_mode)
2128 handle_postponed_scrollback(term);
Bram Moolenaard23a8232018-02-10 18:45:26 +01002129 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002130 if (term->tl_buffer == curbuf)
2131 maketitle();
2132}
2133
2134/*
Bram Moolenaare2978022020-04-26 14:47:44 +02002135 * Called after the job is finished and Terminal mode is not active:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002136 * Move the vterm contents into the scrollback buffer and free the vterm.
2137 */
2138 static void
2139cleanup_vterm(term_T *term)
2140{
Bram Moolenaar29ae2232019-02-14 21:22:01 +01002141 set_terminal_mode(term, FALSE);
Bram Moolenaar1dd98332018-03-16 22:54:53 +01002142 if (term->tl_finish != TL_FINISH_CLOSE)
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002143 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002144 term_free_vterm(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002145}
2146
2147/*
2148 * Switch from Terminal-Job mode to Terminal-Normal mode.
2149 * Suspends updating the terminal window.
2150 */
2151 static void
2152term_enter_normal_mode(void)
2153{
2154 term_T *term = curbuf->b_term;
2155
Bram Moolenaar2bc79952018-05-12 20:36:24 +02002156 set_terminal_mode(term, TRUE);
2157
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002158 // Append the current terminal contents to the buffer.
Bram Moolenaar05c4a472018-05-13 15:15:43 +02002159 may_move_terminal_to_buffer(term, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002160
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002161 // Move the window cursor to the position of the cursor in the
2162 // terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002163 curwin->w_cursor.lnum = term->tl_scrollback_scrolled
2164 + term->tl_cursor_pos.row + 1;
2165 check_cursor();
Bram Moolenaar620020e2018-05-13 19:06:12 +02002166 if (coladvance(term->tl_cursor_pos.col) == FAIL)
2167 coladvance(MAXCOL);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002168 curwin->w_set_curswant = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002169
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002170 // Display the same lines as in the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002171 curwin->w_topline = term->tl_scrollback_scrolled + 1;
2172}
2173
2174/*
2175 * Returns TRUE if the current window contains a terminal and we are in
2176 * Terminal-Normal mode.
2177 */
2178 int
2179term_in_normal_mode(void)
2180{
2181 term_T *term = curbuf->b_term;
2182
2183 return term != NULL && term->tl_normal_mode;
2184}
2185
2186/*
2187 * Switch from Terminal-Normal mode to Terminal-Job mode.
2188 * Restores updating the terminal window.
2189 */
2190 void
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00002191term_enter_job_mode(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002192{
2193 term_T *term = curbuf->b_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002194
2195 set_terminal_mode(term, FALSE);
2196
2197 if (term->tl_channel_closed)
2198 cleanup_vterm(term);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01002199 redraw_buf_and_status_later(curbuf, UPD_NOT_VALID);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002200#ifdef FEAT_PROP_POPUP
2201 if (WIN_IS_POPUP(curwin))
Bram Moolenaara4d158b2022-08-14 14:17:45 +01002202 redraw_later(UPD_NOT_VALID);
Bram Moolenaare52e0c82020-02-28 22:20:10 +01002203#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002204}
2205
2206/*
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002207 * When "modify_other_keys" is set then vgetc() should not reduce a key with
2208 * modifiers into a basic key. However, we may only find out after calling
2209 * vgetc(). Therefore vgetorpeek() will call check_no_reduce_keys() to update
2210 * "no_reduce_keys" before using it.
2211 */
2212typedef enum {
2213 NRKS_NONE, // initial value
2214 NRKS_CHECK, // modify_other_keys was off before calling vgetc()
2215 NRKS_SET, // no_reduce_keys was incremented in term_vgetc() or
2216 // check_no_reduce_keys(), must be decremented.
2217} reduce_key_state_T;
2218
2219static reduce_key_state_T no_reduce_key_state = NRKS_NONE;
2220
Bram Moolenaar63a2e362022-11-23 20:20:18 +00002221/*
2222 * Return TRUE if the term is using modifyOtherKeys level 2 or the kitty
2223 * keyboard protocol.
2224 */
2225 static int
2226vterm_using_key_protocol(void)
2227{
2228 return curbuf->b_term != NULL
2229 && curbuf->b_term->tl_vterm != NULL
2230 && (vterm_is_modify_other_keys(curbuf->b_term->tl_vterm)
2231 || vterm_is_kitty_keyboard(curbuf->b_term->tl_vterm));
2232}
2233
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002234 void
2235check_no_reduce_keys(void)
2236{
2237 if (no_reduce_key_state != NRKS_CHECK
2238 || no_reduce_keys >= 1
2239 || curbuf->b_term == NULL
2240 || curbuf->b_term->tl_vterm == NULL)
2241 return;
2242
Bram Moolenaar63a2e362022-11-23 20:20:18 +00002243 if (vterm_using_key_protocol())
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002244 {
Bram Moolenaar63a2e362022-11-23 20:20:18 +00002245 // "modify_other_keys" or kitty keyboard protocol was enabled while
2246 // waiting.
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002247 no_reduce_key_state = NRKS_SET;
2248 ++no_reduce_keys;
2249 }
2250}
2251
2252/*
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002253 * Get a key from the user with terminal mode mappings.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002254 * Note: while waiting a terminal may be closed and freed if the channel is
Bram Moolenaar829c8e82021-12-14 08:41:38 +00002255 * closed and ++close was used. This may even happen before we get here.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002256 */
2257 static int
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00002258term_vgetc(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002259{
2260 int c;
2261 int save_State = State;
2262
Bram Moolenaar24959102022-05-07 20:01:16 +01002263 State = MODE_TERMINAL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002264 got_int = FALSE;
Bram Moolenaar4f974752019-02-17 17:44:42 +01002265#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002266 ctrl_break_was_pressed = FALSE;
2267#endif
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002268
Bram Moolenaar63a2e362022-11-23 20:20:18 +00002269 if (vterm_using_key_protocol())
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002270 {
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002271 ++no_reduce_keys;
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002272 no_reduce_key_state = NRKS_SET;
2273 }
2274 else
2275 {
2276 no_reduce_key_state = NRKS_CHECK;
2277 }
2278
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002279 c = vgetc();
2280 got_int = FALSE;
2281 State = save_State;
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002282
2283 if (no_reduce_key_state == NRKS_SET)
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002284 --no_reduce_keys;
Bram Moolenaarc896adb2022-11-19 19:02:40 +00002285 no_reduce_key_state = NRKS_NONE;
2286
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002287 return c;
2288}
2289
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002290static int mouse_was_outside = FALSE;
2291
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002292/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002293 * Send key "c" with modifiers "modmask" to terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002294 * Return FAIL when the key needs to be handled in Normal mode.
2295 * Return OK when the key was dropped or sent to the terminal.
2296 */
2297 int
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002298send_keys_to_term(term_T *term, int c, int modmask, int typed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002299{
2300 char msg[KEY_BUF_LEN];
2301 size_t len;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002302 int dragging_outside = FALSE;
2303
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002304 // Catch keys that need to be handled as in Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002305 switch (c)
2306 {
2307 case NUL:
2308 case K_ZERO:
2309 if (typed)
2310 stuffcharReadbuff(c);
2311 return FAIL;
2312
Bram Moolenaar231a2db2018-05-06 13:53:50 +02002313 case K_TABLINE:
2314 stuffcharReadbuff(c);
2315 return FAIL;
2316
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002317 case K_IGNORE:
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002318 case K_CANCEL: // used for :normal when running out of chars
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002319 return FAIL;
2320
2321 case K_LEFTDRAG:
2322 case K_MIDDLEDRAG:
2323 case K_RIGHTDRAG:
2324 case K_X1DRAG:
2325 case K_X2DRAG:
2326 dragging_outside = mouse_was_outside;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002327 // FALLTHROUGH
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002328 case K_LEFTMOUSE:
2329 case K_LEFTMOUSE_NM:
2330 case K_LEFTRELEASE:
2331 case K_LEFTRELEASE_NM:
Bram Moolenaar51b0f372017-11-18 18:52:04 +01002332 case K_MOUSEMOVE:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002333 case K_MIDDLEMOUSE:
2334 case K_MIDDLERELEASE:
2335 case K_RIGHTMOUSE:
2336 case K_RIGHTRELEASE:
2337 case K_X1MOUSE:
2338 case K_X1RELEASE:
2339 case K_X2MOUSE:
2340 case K_X2RELEASE:
2341
2342 case K_MOUSEUP:
2343 case K_MOUSEDOWN:
2344 case K_MOUSELEFT:
2345 case K_MOUSERIGHT:
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002346 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002347 int row = mouse_row;
2348 int col = mouse_col;
2349
2350#ifdef FEAT_PROP_POPUP
2351 if (popup_is_popup(curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002352 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002353 row -= popup_top_extra(curwin);
2354 col -= popup_left_extra(curwin);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002355 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01002356#endif
2357 if (row < W_WINROW(curwin)
2358 || row >= (W_WINROW(curwin) + curwin->w_height)
2359 || col < curwin->w_wincol
2360 || col >= W_ENDCOL(curwin)
2361 || dragging_outside)
2362 {
2363 // click or scroll outside the current window or on status
2364 // line or vertical separator
2365 if (typed)
2366 {
2367 stuffcharReadbuff(c);
2368 mouse_was_outside = TRUE;
2369 }
2370 return FAIL;
2371 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002372 }
Bram Moolenaar957cf672020-11-12 14:21:06 +01002373 break;
2374
2375 case K_COMMAND:
Bram Moolenaare32c3c42022-01-15 18:26:04 +00002376 case K_SCRIPT_COMMAND:
2377 return do_cmdkey_command(c, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002378 }
2379 if (typed)
2380 mouse_was_outside = FALSE;
2381
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002382 // Convert the typed key to a sequence of bytes for the job.
2383 len = term_convert_key(term, c, modmask, msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002384 if (len > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002385 // TODO: if FAIL is returned, stop?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002386 channel_send(term->tl_job->jv_channel, get_tty_part(term),
2387 (char_u *)msg, (int)len, NULL);
2388
2389 return OK;
2390}
2391
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002392/*
2393 * Handle CTRL-W "": send register contents to the job.
2394 */
2395 static void
2396term_paste_register(int prev_c UNUSED)
2397{
2398 int c;
2399 list_T *l;
2400 listitem_T *item;
2401 long reglen = 0;
2402 int type;
2403
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002404 if (add_to_showcmd(prev_c))
2405 if (add_to_showcmd('"'))
2406 out_flush();
Martin Tournoijba43e762022-10-13 22:12:15 +01002407
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002408 c = term_vgetc();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002409 clear_showcmd();
Martin Tournoijba43e762022-10-13 22:12:15 +01002410
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002411 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002412 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002413 return;
2414
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002415 // CTRL-W "= prompt for expression to evaluate.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002416 if (c == '=' && get_expr_register() != '=')
2417 return;
2418 if (!term_use_loop())
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002419 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002420 return;
2421
2422 l = (list_T *)get_reg_contents(c, GREG_LIST);
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002423 if (l == NULL)
2424 return;
2425
2426 type = get_reg_type(c, &reglen);
2427 FOR_ALL_LIST_ITEMS(l, item)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002428 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002429 char_u *s = tv_get_string(&item->li_tv);
2430#ifdef MSWIN
2431 char_u *tmp = s;
2432
2433 if (!enc_utf8 && enc_codepage > 0)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002434 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002435 WCHAR *ret = NULL;
2436 int length = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002437
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002438 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s,
2439 (int)STRLEN(s), &ret, &length);
2440 if (ret != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002441 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002442 WideCharToMultiByte_alloc(CP_UTF8, 0,
2443 ret, length, (char **)&s, &length, 0, 0);
2444 vim_free(ret);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002445 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002446 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002447#endif
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002448 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2449 s, (int)STRLEN(s), NULL);
Bram Moolenaar4f974752019-02-17 17:44:42 +01002450#ifdef MSWIN
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002451 if (tmp != s)
2452 vim_free(s);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002453#endif
2454
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002455 if (item->li_next != NULL || type == MLINE)
2456 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
2457 (char_u *)"\r", 1, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002458 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002459 list_free(l);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002460}
2461
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002462/*
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002463 * Return TRUE when waiting for a character in the terminal, the cursor of the
2464 * terminal should be displayed.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002465 */
2466 int
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00002467terminal_is_active(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002468{
2469 return in_terminal_loop != NULL;
2470}
2471
Bram Moolenaar83d47902020-03-26 20:34:00 +01002472/*
dundargocc57b5bc2022-11-02 13:30:51 +00002473 * Return the highlight group ID for the terminal and the window.
Bram Moolenaar83d47902020-03-26 20:34:00 +01002474 */
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002475 static int
2476term_get_highlight_id(term_T *term, win_T *wp)
Bram Moolenaar83d47902020-03-26 20:34:00 +01002477{
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002478 char_u *name;
2479
2480 if (wp != NULL && *wp->w_p_wcr != NUL)
2481 name = wp->w_p_wcr;
2482 else if (term->tl_highlight_name != NULL)
2483 name = term->tl_highlight_name;
2484 else
2485 name = (char_u*)"Terminal";
2486
2487 return syn_name2id(name);
Bram Moolenaar83d47902020-03-26 20:34:00 +01002488}
2489
Bram Moolenaarb2ac14c2018-05-01 18:47:59 +02002490#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002491 cursorentry_T *
2492term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg)
2493{
2494 term_T *term = in_terminal_loop;
2495 static cursorentry_T entry;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002496 int id;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002497 guicolor_T term_fg = INVALCOLOR;
2498 guicolor_T term_bg = INVALCOLOR;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002499
Bram Moolenaara80faa82020-04-12 19:37:17 +02002500 CLEAR_FIELD(entry);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002501 entry.shape = entry.mshape =
2502 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR :
2503 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER :
2504 SHAPE_BLOCK;
2505 entry.percentage = 20;
2506 if (term->tl_cursor_blink)
2507 {
2508 entry.blinkwait = 700;
2509 entry.blinkon = 400;
2510 entry.blinkoff = 250;
2511 }
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002512
Bram Moolenaar83d47902020-03-26 20:34:00 +01002513 // The highlight group overrules the defaults.
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002514 id = term_get_highlight_id(term, curwin);
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002515 if (id != 0)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002516 syn_id2colors(id, &term_fg, &term_bg);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002517 if (term_bg != INVALCOLOR)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002518 *fg = term_bg;
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002519 else
2520 *fg = gui.back_pixel;
2521
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002522 if (term->tl_cursor_color == NULL)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002523 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002524 if (term_fg != INVALCOLOR)
Bram Moolenaar29e7fe52018-10-16 22:13:00 +02002525 *bg = term_fg;
2526 else
2527 *bg = gui.norm_pixel;
2528 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002529 else
2530 *bg = color_name2handle(term->tl_cursor_color);
2531 entry.name = "n";
2532 entry.used_for = SHAPE_CURSOR;
2533
2534 return &entry;
2535}
2536#endif
2537
Bram Moolenaard317b382018-02-08 22:33:31 +01002538 static void
2539may_output_cursor_props(void)
2540{
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002541 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color)
Bram Moolenaard317b382018-02-08 22:33:31 +01002542 || last_set_cursor_shape != desired_cursor_shape
2543 || last_set_cursor_blink != desired_cursor_blink)
2544 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002545 cursor_color_copy(&last_set_cursor_color, desired_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002546 last_set_cursor_shape = desired_cursor_shape;
2547 last_set_cursor_blink = desired_cursor_blink;
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002548 term_cursor_color(cursor_color_get(desired_cursor_color));
Bram Moolenaard317b382018-02-08 22:33:31 +01002549 if (desired_cursor_shape == -1 || desired_cursor_blink == -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002550 // this will restore the initial cursor style, if possible
Bram Moolenaard317b382018-02-08 22:33:31 +01002551 ui_cursor_shape_forced(TRUE);
2552 else
2553 term_cursor_shape(desired_cursor_shape, desired_cursor_blink);
2554 }
2555}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002556
Bram Moolenaard317b382018-02-08 22:33:31 +01002557/*
2558 * Set the cursor color and shape, if not last set to these.
2559 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002560 static void
2561may_set_cursor_props(term_T *term)
2562{
2563#ifdef FEAT_GUI
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002564 // For the GUI the cursor properties are obtained with
2565 // term_get_cursor_shape().
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002566 if (gui.in_use)
2567 return;
2568#endif
2569 if (in_terminal_loop == term)
2570 {
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002571 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color);
Bram Moolenaard317b382018-02-08 22:33:31 +01002572 desired_cursor_shape = term->tl_cursor_shape;
2573 desired_cursor_blink = term->tl_cursor_blink;
2574 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002575 }
2576}
2577
Bram Moolenaard317b382018-02-08 22:33:31 +01002578/*
2579 * Reset the desired cursor properties and restore them when needed.
2580 */
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002581 static void
Bram Moolenaard317b382018-02-08 22:33:31 +01002582prepare_restore_cursor_props(void)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002583{
2584#ifdef FEAT_GUI
2585 if (gui.in_use)
2586 return;
2587#endif
Bram Moolenaar4f7fd562018-05-21 14:55:28 +02002588 cursor_color_copy(&desired_cursor_color, NULL);
Bram Moolenaard317b382018-02-08 22:33:31 +01002589 desired_cursor_shape = -1;
2590 desired_cursor_blink = -1;
2591 may_output_cursor_props();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002592}
2593
2594/*
Bram Moolenaar802bfb12018-04-15 17:28:13 +02002595 * Returns TRUE if the current window contains a terminal and we are sending
2596 * keys to the job.
2597 * If "check_job_status" is TRUE update the job status.
2598 */
2599 static int
2600term_use_loop_check(int check_job_status)
2601{
2602 term_T *term = curbuf->b_term;
2603
2604 return term != NULL
2605 && !term->tl_normal_mode
2606 && term->tl_vterm != NULL
2607 && term_job_running_check(term, check_job_status);
2608}
2609
2610/*
2611 * Returns TRUE if the current window contains a terminal and we are sending
2612 * keys to the job.
2613 */
2614 int
2615term_use_loop(void)
2616{
2617 return term_use_loop_check(FALSE);
2618}
2619
2620/*
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002621 * Called when entering a window with the mouse. If this is a terminal window
2622 * we may want to change state.
2623 */
2624 void
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00002625term_win_entered(void)
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002626{
2627 term_T *term = curbuf->b_term;
2628
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002629 if (term == NULL)
2630 return;
2631
2632 if (term_use_loop_check(TRUE))
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002633 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002634 reset_VIsual_and_resel();
2635 if (State & MODE_INSERT)
2636 stop_insert_mode = TRUE;
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002637 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002638 mouse_was_outside = FALSE;
2639 enter_mouse_col = mouse_col;
2640 enter_mouse_row = mouse_row;
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002641}
2642
Bram Moolenaara48d4e42021-12-08 22:13:38 +00002643 void
2644term_focus_change(int in_focus)
2645{
2646 term_T *term = curbuf->b_term;
2647
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002648 if (term == NULL || term->tl_vterm == NULL)
2649 return;
Bram Moolenaara48d4e42021-12-08 22:13:38 +00002650
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002651 VTermState *state = vterm_obtain_state(term->tl_vterm);
2652
2653 if (in_focus)
2654 vterm_state_focus_in(state);
2655 else
2656 vterm_state_focus_out(state);
2657 term_forward_output(term);
Bram Moolenaara48d4e42021-12-08 22:13:38 +00002658}
2659
Bram Moolenaarc48369c2018-03-11 19:30:45 +01002660/*
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002661 * vgetc() may not include CTRL in the key when modify_other_keys is set.
2662 * Return the Ctrl-key value in that case.
2663 */
2664 static int
2665raw_c_to_ctrl(int c)
2666{
2667 if ((mod_mask & MOD_MASK_CTRL)
2668 && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')))
2669 return c & 0x1f;
2670 return c;
2671}
2672
2673/*
2674 * When modify_other_keys is set then do the reverse of raw_c_to_ctrl().
Bram Moolenaar63a2e362022-11-23 20:20:18 +00002675 * Also when the Kitty keyboard protocol is used.
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002676 * May set "mod_mask".
2677 */
2678 static int
2679ctrl_to_raw_c(int c)
2680{
Bram Moolenaar63a2e362022-11-23 20:20:18 +00002681 if (c < 0x20 && vterm_using_key_protocol())
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002682 {
2683 mod_mask |= MOD_MASK_CTRL;
2684 return c + '@';
2685 }
2686 return c;
2687}
2688
2689/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002690 * Wait for input and send it to the job.
2691 * When "blocking" is TRUE wait for a character to be typed. Otherwise return
2692 * when there is no more typahead.
2693 * Return when the start of a CTRL-W command is typed or anything else that
2694 * should be handled as a Normal mode command.
2695 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
2696 * the terminal was closed.
2697 */
2698 int
2699terminal_loop(int blocking)
2700{
2701 int c;
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002702 int raw_c;
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002703 int termwinkey = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002704 int ret;
Bram Moolenaar12326242017-11-04 20:12:14 +01002705#ifdef UNIX
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002706 int tty_fd = curbuf->b_term->tl_job->jv_channel
2707 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd;
Bram Moolenaar12326242017-11-04 20:12:14 +01002708#endif
Bram Moolenaar73dd1bd2018-05-12 21:16:25 +02002709 int restore_cursor = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002710
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002711 // Remember the terminal we are sending keys to. However, the terminal
2712 // might be closed while waiting for a character, e.g. typing "exit" in a
2713 // shell and ++close was used. Therefore use curbuf->b_term instead of a
2714 // stored reference.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002715 in_terminal_loop = curbuf->b_term;
2716
Bram Moolenaar6d150f72018-04-21 20:03:20 +02002717 if (*curwin->w_p_twk != NUL)
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002718 {
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002719 termwinkey = string_to_key(curwin->w_p_twk, TRUE);
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002720 if (termwinkey == Ctrl_W)
2721 termwinkey = 0;
2722 }
Bram Moolenaarebec3e22020-11-28 20:22:06 +01002723 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002724 may_set_cursor_props(curbuf->b_term);
2725
Bram Moolenaarc8bcfe72018-02-27 16:29:28 +01002726 while (blocking || vpeekc_nomap() != NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002727 {
Bram Moolenaar13568252018-03-16 20:46:58 +01002728#ifdef FEAT_GUI
Bram Moolenaar02764712020-11-14 20:21:55 +01002729 if (curbuf->b_term != NULL && !curbuf->b_term->tl_system)
Bram Moolenaar13568252018-03-16 20:46:58 +01002730#endif
Bram Moolenaar2a4857a2019-01-29 22:29:07 +01002731 // TODO: skip screen update when handling a sequence of keys.
2732 // Repeat redrawing in case a message is received while redrawing.
Bram Moolenaar13568252018-03-16 20:46:58 +01002733 while (must_redraw != 0)
2734 if (update_screen(0) == FAIL)
2735 break;
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002736 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002737 // job finished while redrawing
Bram Moolenaara10ae5e2018-05-11 20:48:29 +02002738 break;
2739
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002740 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaard317b382018-02-08 22:33:31 +01002741 restore_cursor = TRUE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002742
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002743 raw_c = term_vgetc();
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002744 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term)
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002745 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002746 // Job finished while waiting for a character. Push back the
2747 // received character.
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002748 if (raw_c != K_IGNORE)
2749 vungetc(raw_c);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002750 break;
Bram Moolenaara3f7e582017-11-09 13:21:58 +01002751 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002752 if (raw_c == K_IGNORE)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002753 continue;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002754 c = raw_c_to_ctrl(raw_c);
Bram Moolenaar6a0299d2019-10-10 21:14:03 +02002755
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002756#ifdef UNIX
2757 /*
2758 * The shell or another program may change the tty settings. Getting
2759 * them for every typed character is a bit of overhead, but it's needed
2760 * for the first character typed, e.g. when Vim starts in a shell.
2761 */
Bram Moolenaar1ecc5e42019-01-26 15:12:55 +01002762 if (mch_isatty(tty_fd))
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002763 {
2764 ttyinfo_T info;
2765
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002766 // Get the current backspace character of the pty.
Bram Moolenaar26d205d2017-11-09 17:33:11 +01002767 if (get_tty_info(tty_fd, &info) == OK)
2768 term_backspace_char = info.backspace;
2769 }
2770#endif
2771
Bram Moolenaar4f974752019-02-17 17:44:42 +01002772#ifdef MSWIN
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002773 // On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT.
2774 // Use CTRL-BREAK to kill the job.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002775 if (ctrl_break_was_pressed)
2776 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2777#endif
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002778 // Was either CTRL-W (termwinkey) or CTRL-\ pressed?
2779 // Not in a system terminal.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002780 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL)
Bram Moolenaaraf23bad2018-03-16 22:20:49 +01002781#ifdef FEAT_GUI
2782 && !curbuf->b_term->tl_system
2783#endif
2784 )
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002785 {
2786 int prev_c = c;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002787 int prev_raw_c = raw_c;
2788 int prev_mod_mask = mod_mask;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002789
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002790 if (add_to_showcmd(c))
2791 out_flush();
Martin Tournoijba43e762022-10-13 22:12:15 +01002792
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002793 raw_c = term_vgetc();
2794 c = raw_c_to_ctrl(raw_c);
2795
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002796 clear_showcmd();
Martin Tournoijba43e762022-10-13 22:12:15 +01002797
Bram Moolenaar05af9a42018-05-21 18:48:12 +02002798 if (!term_use_loop_check(TRUE)
2799 || in_terminal_loop != curbuf->b_term)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002800 // job finished while waiting for a character
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002801 break;
2802
2803 if (prev_c == Ctrl_BSL)
2804 {
2805 if (c == Ctrl_N)
2806 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002807 // CTRL-\ CTRL-N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002808 term_enter_normal_mode();
2809 ret = FAIL;
2810 goto theend;
2811 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002812 // Send both keys to the terminal, first one here, second one
2813 // below.
2814 send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask,
2815 TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002816 }
2817 else if (c == Ctrl_C)
2818 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002819 // "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002820 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill");
2821 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002822 else if (c == '.')
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002823 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002824 // "CTRL-W .": send CTRL-W to the job
2825 // "'termwinkey' .": send 'termwinkey' to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002826 raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002827 }
Bram Moolenaardcdeaaf2018-06-17 22:19:12 +02002828 else if (c == Ctrl_BSL)
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002829 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002830 // "CTRL-W CTRL-\": send CTRL-\ to the job
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002831 raw_c = ctrl_to_raw_c(Ctrl_BSL);
Bram Moolenaarb59118d2018-04-13 22:11:56 +02002832 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002833 else if (c == 'N')
2834 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002835 // CTRL-W N : go to Terminal-Normal mode.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002836 term_enter_normal_mode();
2837 ret = FAIL;
2838 goto theend;
2839 }
2840 else if (c == '"')
2841 {
2842 term_paste_register(prev_c);
2843 continue;
2844 }
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02002845 else if (termwinkey == 0 || c != termwinkey)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002846 {
Bram Moolenaarf43e7ac2020-09-29 21:23:25 +02002847 // space for CTRL-W, modifier, multi-byte char and NUL
2848 char_u buf[1 + 3 + MB_MAXBYTES + 1];
Bram Moolenaara4b26992019-08-15 20:58:54 +02002849
2850 // Put the command into the typeahead buffer, when using the
2851 // stuff buffer KeyStuffed is set and 'langmap' won't be used.
2852 buf[0] = Ctrl_W;
Bram Moolenaarf43e7ac2020-09-29 21:23:25 +02002853 buf[special_to_buf(c, mod_mask, FALSE, buf + 1) + 1] = NUL;
Bram Moolenaara4b26992019-08-15 20:58:54 +02002854 ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002855 ret = OK;
2856 goto theend;
2857 }
2858 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01002859# ifdef MSWIN
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002860 if (!enc_utf8 && has_mbyte && raw_c >= 0x80)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002861 {
2862 WCHAR wc;
2863 char_u mb[3];
2864
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002865 mb[0] = (unsigned)raw_c >> 8;
2866 mb[1] = raw_c;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002867 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0)
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002868 raw_c = wc;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002869 }
2870# endif
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002871 if (send_keys_to_term(curbuf->b_term, raw_c, mod_mask, TRUE) != OK)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002872 {
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002873 if (raw_c == K_MOUSEMOVE)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002874 // We are sure to come back here, don't reset the cursor color
2875 // and shape to avoid flickering.
Bram Moolenaard317b382018-02-08 22:33:31 +01002876 restore_cursor = FALSE;
2877
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002878 ret = OK;
2879 goto theend;
2880 }
2881 }
2882 ret = FAIL;
2883
2884theend:
2885 in_terminal_loop = NULL;
Bram Moolenaard317b382018-02-08 22:33:31 +01002886 if (restore_cursor)
2887 prepare_restore_cursor_props();
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002888
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002889 // Move a snapshot of the screen contents to the buffer, so that completion
2890 // works in other buffers.
Bram Moolenaar620020e2018-05-13 19:06:12 +02002891 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode)
2892 may_move_terminal_to_buffer(curbuf->b_term, FALSE);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02002893
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002894 return ret;
2895}
2896
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002897 static void
2898may_toggle_cursor(term_T *term)
2899{
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00002900 if (in_terminal_loop != term)
2901 return;
2902
2903 if (term->tl_cursor_visible)
2904 cursor_on();
2905 else
2906 cursor_off();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002907}
2908
2909/*
2910 * Reverse engineer the RGB value into a cterm color index.
Bram Moolenaar46359e12017-11-29 22:33:38 +01002911 * First color is 1. Return 0 if no match found (default color).
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002912 */
2913 static int
2914color2index(VTermColor *color, int fg, int *boldp)
2915{
2916 int red = color->red;
2917 int blue = color->blue;
2918 int green = color->green;
2919
LemonBoyb2b3acb2022-05-20 10:10:34 +01002920 *boldp = FALSE;
2921
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002922 if (VTERM_COLOR_IS_INVALID(color))
Bram Moolenaare5886cc2020-05-21 20:10:04 +02002923 return 0;
LemonBoyb2b3acb2022-05-20 10:10:34 +01002924
Bram Moolenaare5886cc2020-05-21 20:10:04 +02002925 if (VTERM_COLOR_IS_INDEXED(color))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002926 {
LemonBoyb2b3acb2022-05-20 10:10:34 +01002927 // Use the color as-is if possible, give up otherwise.
2928 if (color->index < t_colors)
2929 return color->index + 1;
2930 // 8-color terminals can actually display twice as many colors by
2931 // setting the high-intensity/bold bit.
2932 else if (t_colors == 8 && fg && color->index < 16)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002933 {
LemonBoyb2b3acb2022-05-20 10:10:34 +01002934 *boldp = TRUE;
2935 return (color->index & 7) + 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002936 }
LemonBoyb2b3acb2022-05-20 10:10:34 +01002937 return 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002938 }
Bram Moolenaar46359e12017-11-29 22:33:38 +01002939
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002940 if (t_colors >= 256)
2941 {
2942 if (red == blue && red == green)
2943 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002944 // 24-color greyscale plus white and black
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002945 static int cutoff[23] = {
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002946 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67,
2947 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB,
2948 0xD5, 0xDF, 0xE9};
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002949 int i;
2950
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002951 if (red < 5)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002952 return 17; // 00/00/00
2953 if (red > 245) // ff/ff/ff
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002954 return 232;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002955 for (i = 0; i < 23; ++i)
2956 if (red < cutoff[i])
2957 return i + 233;
2958 return 256;
2959 }
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002960 {
2961 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB};
2962 int ri, gi, bi;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002963
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01002964 // 216-color cube
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02002965 for (ri = 0; ri < 5; ++ri)
2966 if (red < cutoff[ri])
2967 break;
2968 for (gi = 0; gi < 5; ++gi)
2969 if (green < cutoff[gi])
2970 break;
2971 for (bi = 0; bi < 5; ++bi)
2972 if (blue < cutoff[bi])
2973 break;
2974 return 17 + ri * 36 + gi * 6 + bi;
2975 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002976 }
2977 return 0;
2978}
2979
2980/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01002981 * Convert Vterm attributes to highlight flags.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002982 */
2983 static int
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002984vtermAttr2hl(VTermScreenCellAttrs *cellattrs)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002985{
2986 int attr = 0;
2987
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002988 if (cellattrs->bold)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002989 attr |= HL_BOLD;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002990 if (cellattrs->underline)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002991 attr |= HL_UNDERLINE;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002992 if (cellattrs->italic)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002993 attr |= HL_ITALIC;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002994 if (cellattrs->strike)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002995 attr |= HL_STRIKETHROUGH;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00002996 if (cellattrs->reverse)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02002997 attr |= HL_INVERSE;
Bram Moolenaard96ff162018-02-18 22:13:29 +01002998 return attr;
2999}
3000
3001/*
3002 * Store Vterm attributes in "cell" from highlight flags.
3003 */
3004 static void
3005hl2vtermAttr(int attr, cellattr_T *cell)
3006{
Bram Moolenaara80faa82020-04-12 19:37:17 +02003007 CLEAR_FIELD(cell->attrs);
Bram Moolenaard96ff162018-02-18 22:13:29 +01003008 if (attr & HL_BOLD)
3009 cell->attrs.bold = 1;
3010 if (attr & HL_UNDERLINE)
3011 cell->attrs.underline = 1;
3012 if (attr & HL_ITALIC)
3013 cell->attrs.italic = 1;
3014 if (attr & HL_STRIKETHROUGH)
3015 cell->attrs.strike = 1;
3016 if (attr & HL_INVERSE)
3017 cell->attrs.reverse = 1;
3018}
3019
3020/*
3021 * Convert the attributes of a vterm cell into an attribute index.
3022 */
3023 static int
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003024cell2attr(
Bram Moolenaar83d47902020-03-26 20:34:00 +01003025 term_T *term,
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003026 win_T *wp,
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003027 VTermScreenCellAttrs *cellattrs,
3028 VTermColor *cellfg,
3029 VTermColor *cellbg)
Bram Moolenaard96ff162018-02-18 22:13:29 +01003030{
3031 int attr = vtermAttr2hl(cellattrs);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003032 VTermColor *fg = cellfg;
3033 VTermColor *bg = cellbg;
3034 int is_default_fg = VTERM_COLOR_IS_DEFAULT_FG(fg);
3035 int is_default_bg = VTERM_COLOR_IS_DEFAULT_BG(bg);
3036
3037 if (is_default_fg || is_default_bg)
3038 {
3039 if (wp != NULL && *wp->w_p_wcr != NUL)
3040 {
3041 if (is_default_fg)
3042 fg = &wp->w_term_wincolor.fg;
3043 if (is_default_bg)
3044 bg = &wp->w_term_wincolor.bg;
3045 }
3046 else
3047 {
3048 if (is_default_fg)
3049 fg = &term->tl_default_color.fg;
3050 if (is_default_bg)
3051 bg = &term->tl_default_color.bg;
3052 }
3053 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003054
3055#ifdef FEAT_GUI
3056 if (gui.in_use)
3057 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003058 guicolor_T guifg = gui_mch_get_rgb_color(fg->red, fg->green, fg->blue);
3059 guicolor_T guibg = gui_mch_get_rgb_color(bg->red, bg->green, bg->blue);
3060 return get_gui_attr_idx(attr, guifg, guibg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003061 }
3062 else
3063#endif
3064#ifdef FEAT_TERMGUICOLORS
3065 if (p_tgc)
3066 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003067 guicolor_T tgcfg = VTERM_COLOR_IS_INVALID(fg)
3068 ? INVALCOLOR
3069 : gui_get_rgb_color_cmn(fg->red, fg->green, fg->blue);
3070 guicolor_T tgcbg = VTERM_COLOR_IS_INVALID(bg)
3071 ? INVALCOLOR
3072 : gui_get_rgb_color_cmn(bg->red, bg->green, bg->blue);
3073 return get_tgc_attr_idx(attr, tgcfg, tgcbg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003074 }
3075 else
3076#endif
3077 {
3078 int bold = MAYBE;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003079 int ctermfg = color2index(fg, TRUE, &bold);
3080 int ctermbg = color2index(bg, FALSE, &bold);
Bram Moolenaar76bb7192017-11-30 22:07:07 +01003081
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003082 // with 8 colors set the bold attribute to get a bright foreground
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003083 if (bold == TRUE)
3084 attr |= HL_BOLD;
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003085
3086 return get_cterm_attr_idx(attr, ctermfg, ctermbg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003087 }
3088 return 0;
3089}
3090
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003091 static void
3092set_dirty_snapshot(term_T *term)
3093{
3094 term->tl_dirty_snapshot = TRUE;
3095#ifdef FEAT_TIMERS
3096 if (!term->tl_normal_mode)
3097 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003098 // Update the snapshot after 100 msec of not getting updates.
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003099 profile_setlimit(100L, &term->tl_timer_due);
3100 term->tl_timer_set = TRUE;
3101 }
3102#endif
3103}
3104
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003105 static int
3106handle_damage(VTermRect rect, void *user)
3107{
3108 term_T *term = (term_T *)user;
3109
3110 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
3111 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003112 set_dirty_snapshot(term);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003113 redraw_buf_later(term->tl_buffer, UPD_SOME_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003114 return 1;
3115}
3116
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003117 static void
3118term_scroll_up(term_T *term, int start_row, int count)
3119{
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003120 win_T *wp = NULL;
3121 int did_curwin = FALSE;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003122 VTermColor fg, bg;
3123 VTermScreenCellAttrs attr;
3124 int clear_attr;
3125
Bram Moolenaara80faa82020-04-12 19:37:17 +02003126 CLEAR_FIELD(attr);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003127
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003128 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003129 {
3130 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003131 {
3132 // Set the color to clear lines with.
3133 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm),
3134 &fg, &bg);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003135 clear_attr = cell2attr(term, wp, &attr, &fg, &bg);
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003136 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr);
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003137 }
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003138 }
3139}
3140
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003141 static int
3142handle_moverect(VTermRect dest, VTermRect src, void *user)
3143{
3144 term_T *term = (term_T *)user;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003145 int count = src.start_row - dest.start_row;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003146
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003147 // Scrolling up is done much more efficiently by deleting lines instead of
3148 // redrawing the text. But avoid doing this multiple times, postpone until
3149 // the redraw happens.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003150 if (dest.start_col == src.start_col
3151 && dest.end_col == src.end_col
3152 && dest.start_row < src.start_row)
3153 {
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003154 if (dest.start_row == 0)
3155 term->tl_postponed_scroll += count;
3156 else
3157 term_scroll_up(term, dest.start_row, count);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003158 }
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003159
3160 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row);
3161 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row);
Bram Moolenaar56bc8e22018-05-10 18:05:56 +02003162 set_dirty_snapshot(term);
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003163
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003164 // Note sure if the scrolling will work correctly, let's do a complete
3165 // redraw later.
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003166 redraw_buf_later(term->tl_buffer, UPD_NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003167 return 1;
3168}
3169
3170 static int
3171handle_movecursor(
3172 VTermPos pos,
3173 VTermPos oldpos UNUSED,
3174 int visible,
3175 void *user)
3176{
3177 term_T *term = (term_T *)user;
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003178 win_T *wp = NULL;
3179 int did_curwin = FALSE;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003180
3181 term->tl_cursor_pos = pos;
3182 term->tl_cursor_visible = visible;
3183
Bram Moolenaare52e0c82020-02-28 22:20:10 +01003184 while (for_all_windows_and_curwin(&wp, &did_curwin))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003185 {
3186 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaarebec3e22020-11-28 20:22:06 +01003187 position_cursor(wp, &pos);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003188 }
3189 if (term->tl_buffer == curbuf && !term->tl_normal_mode)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003190 update_cursor(term, term->tl_cursor_visible);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003191
3192 return 1;
3193}
3194
3195 static int
3196handle_settermprop(
3197 VTermProp prop,
3198 VTermValue *value,
3199 void *user)
3200{
3201 term_T *term = (term_T *)user;
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003202 char_u *strval = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003203
3204 switch (prop)
3205 {
3206 case VTERM_PROP_TITLE:
ichizokae1bd872022-01-20 14:57:29 +00003207 if (disable_vterm_title_for_testing)
3208 break;
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003209 strval = vim_strnsave((char_u *)value->string.str,
Bram Moolenaar71ccd032020-06-12 22:59:11 +02003210 value->string.len);
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003211 if (strval == NULL)
3212 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003213 vim_free(term->tl_title);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003214 // a blank title isn't useful, make it empty, so that "running" is
3215 // displayed
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003216 if (*skipwhite(strval) == NUL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003217 term->tl_title = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003218 // Same as blank
3219 else if (term->tl_arg0_cmd != NULL
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003220 && STRNCMP(term->tl_arg0_cmd, strval,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003221 (int)STRLEN(term->tl_arg0_cmd)) == 0)
3222 term->tl_title = NULL;
3223 // Empty corrupted data of winpty
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003224 else if (STRNCMP(" - ", strval, 4) == 0)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01003225 term->tl_title = NULL;
Bram Moolenaar4f974752019-02-17 17:44:42 +01003226#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003227 else if (!enc_utf8 && enc_codepage > 0)
3228 {
3229 WCHAR *ret = NULL;
3230 int length = 0;
3231
3232 MultiByteToWideChar_alloc(CP_UTF8, 0,
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003233 (char*)value->string.str,
3234 (int)value->string.len, &ret, &length);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003235 if (ret != NULL)
3236 {
3237 WideCharToMultiByte_alloc(enc_codepage, 0,
3238 ret, length, (char**)&term->tl_title,
3239 &length, 0, 0);
3240 vim_free(ret);
3241 }
3242 }
3243#endif
3244 else
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003245 {
Bram Moolenaar98f16712020-05-22 13:34:01 +02003246 term->tl_title = strval;
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003247 strval = NULL;
3248 }
Bram Moolenaard23a8232018-02-10 18:45:26 +01003249 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003250 if (term == curbuf->b_term)
LemonBoy327e6dd2022-06-04 19:57:59 +01003251 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003252 maketitle();
LemonBoy327e6dd2022-06-04 19:57:59 +01003253 curwin->w_redr_status = TRUE;
3254 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003255 break;
3256
3257 case VTERM_PROP_CURSORVISIBLE:
3258 term->tl_cursor_visible = value->boolean;
3259 may_toggle_cursor(term);
3260 out_flush();
3261 break;
3262
3263 case VTERM_PROP_CURSORBLINK:
3264 term->tl_cursor_blink = value->boolean;
3265 may_set_cursor_props(term);
3266 break;
3267
3268 case VTERM_PROP_CURSORSHAPE:
3269 term->tl_cursor_shape = value->number;
3270 may_set_cursor_props(term);
3271 break;
3272
3273 case VTERM_PROP_CURSORCOLOR:
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003274 strval = vim_strnsave((char_u *)value->string.str,
Bram Moolenaar71ccd032020-06-12 22:59:11 +02003275 value->string.len);
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003276 if (strval == NULL)
3277 break;
3278 cursor_color_copy(&term->tl_cursor_color, strval);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003279 may_set_cursor_props(term);
3280 break;
3281
3282 case VTERM_PROP_ALTSCREEN:
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003283 // TODO: do anything else?
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003284 term->tl_using_altscreen = value->boolean;
3285 break;
3286
3287 default:
3288 break;
3289 }
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02003290 vim_free(strval);
3291
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003292 // Always return 1, otherwise vterm doesn't store the value internally.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003293 return 1;
3294}
3295
3296/*
3297 * The job running in the terminal resized the terminal.
3298 */
3299 static int
3300handle_resize(int rows, int cols, void *user)
3301{
3302 term_T *term = (term_T *)user;
3303 win_T *wp;
3304
3305 term->tl_rows = rows;
3306 term->tl_cols = cols;
3307 if (term->tl_vterm_size_changed)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003308 // Size was set by vterm_set_size(), don't set the window size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003309 term->tl_vterm_size_changed = FALSE;
3310 else
3311 {
3312 FOR_ALL_WINDOWS(wp)
3313 {
3314 if (wp->w_buffer == term->tl_buffer)
3315 {
3316 win_setheight_win(rows, wp);
3317 win_setwidth_win(cols, wp);
3318 }
3319 }
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003320 redraw_buf_later(term->tl_buffer, UPD_NOT_VALID);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003321 }
3322 return 1;
3323}
3324
3325/*
Bram Moolenaarf39d9e92023-04-22 22:54:40 +01003326 * If the number of lines that are stored goes over 'termwinscroll' then
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003327 * delete the first 10%.
3328 * "gap" points to tl_scrollback or tl_scrollback_postponed.
3329 * "update_buffer" is TRUE when the buffer should be updated.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003330 */
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003331 static void
3332limit_scrollback(term_T *term, garray_T *gap, int update_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003333{
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003334 if (gap->ga_len < term->tl_buffer->b_p_twsl)
3335 return;
3336
3337 int todo = term->tl_buffer->b_p_twsl / 10;
3338 int i;
3339
3340 curbuf = term->tl_buffer;
3341 for (i = 0; i < todo; ++i)
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003342 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003343 vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells);
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003344 if (update_buffer)
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003345 ml_delete(1);
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003346 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003347 curbuf = curwin->w_buffer;
3348
3349 gap->ga_len -= todo;
3350 mch_memmove(gap->ga_data,
3351 (sb_line_T *)gap->ga_data + todo,
3352 sizeof(sb_line_T) * gap->ga_len);
3353 if (update_buffer)
3354 term->tl_scrollback_scrolled -= todo;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003355}
3356
3357/*
3358 * Handle a line that is pushed off the top of the screen.
3359 */
3360 static int
3361handle_pushline(int cols, const VTermScreenCell *cells, void *user)
3362{
3363 term_T *term = (term_T *)user;
3364 garray_T *gap;
3365 int update_buffer;
3366
3367 if (term->tl_normal_mode)
3368 {
3369 // In Terminal-Normal mode the user interacts with the buffer, thus we
3370 // must not change it. Postpone adding the scrollback lines.
3371 gap = &term->tl_scrollback_postponed;
3372 update_buffer = FALSE;
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003373 }
3374 else
3375 {
3376 // First remove the lines that were appended before, the pushed line
3377 // goes above it.
3378 cleanup_scrollback(term);
3379 gap = &term->tl_scrollback;
3380 update_buffer = TRUE;
Bram Moolenaar8c041b62018-04-14 18:14:06 +02003381 }
3382
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003383 limit_scrollback(term, gap, update_buffer);
3384
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003385 if (ga_grow(gap, 1) == FAIL)
3386 return 0;
3387
3388 cellattr_T *p = NULL;
3389 int len = 0;
3390 int i;
3391 int c;
3392 int col;
3393 int text_len;
3394 char_u *text;
3395 sb_line_T *line;
3396 garray_T ga;
3397 cellattr_T fill_attr = term->tl_default_color;
3398
3399 // do not store empty cells at the end
3400 for (i = 0; i < cols; ++i)
3401 if (cells[i].chars[0] != 0)
3402 len = i + 1;
3403 else
3404 cell2cellattr(&cells[i], &fill_attr);
3405
3406 ga_init2(&ga, 1, 100);
3407 if (len > 0)
3408 p = ALLOC_MULT(cellattr_T, len);
3409 if (p != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003410 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003411 for (col = 0; col < len; col += cells[col].width)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003412 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003413 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003414 {
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003415 ga.ga_len = 0;
3416 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003417 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003418 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i)
3419 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c,
3420 (char_u *)ga.ga_data + ga.ga_len);
3421 cell2cellattr(&cells[col], &p[col]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003422 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003423 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003424 if (ga_grow(&ga, 1) == FAIL)
3425 {
3426 if (update_buffer)
3427 text = (char_u *)"";
3428 else
3429 text = vim_strsave((char_u *)"");
3430 text_len = 0;
3431 }
3432 else
3433 {
3434 text = ga.ga_data;
3435 text_len = ga.ga_len;
3436 *(text + text_len) = NUL;
3437 }
3438 if (update_buffer)
3439 add_scrollback_line_to_buffer(term, text, text_len);
3440
3441 line = (sb_line_T *)gap->ga_data + gap->ga_len;
3442 line->sb_cols = len;
3443 line->sb_cells = p;
3444 line->sb_fill_attr = fill_attr;
3445 if (update_buffer)
3446 {
3447 line->sb_text = NULL;
3448 ++term->tl_scrollback_scrolled;
3449 ga_clear(&ga); // free the text
3450 }
3451 else
3452 {
3453 line->sb_text = text;
3454 ga_init(&ga); // text is kept in tl_scrollback_postponed
3455 }
3456 ++gap->ga_len;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003457 return 0; // ignored
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003458}
3459
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003460/*
3461 * Called when leaving Terminal-Normal mode: deal with any scrollback that was
3462 * received and stored in tl_scrollback_postponed.
3463 */
3464 static void
3465handle_postponed_scrollback(term_T *term)
3466{
3467 int i;
3468
Bram Moolenaar8376c3d2019-03-19 20:50:43 +01003469 if (term->tl_scrollback_postponed.ga_len == 0)
3470 return;
3471 ch_log(NULL, "Moving postponed scrollback to scrollback");
3472
Bram Moolenaar29ae2232019-02-14 21:22:01 +01003473 // First remove the lines that were appended before, the pushed lines go
3474 // above it.
3475 cleanup_scrollback(term);
3476
3477 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i)
3478 {
3479 char_u *text;
3480 sb_line_T *pp_line;
3481 sb_line_T *line;
3482
3483 if (ga_grow(&term->tl_scrollback, 1) == FAIL)
3484 break;
3485 pp_line = (sb_line_T *)term->tl_scrollback_postponed.ga_data + i;
3486
3487 text = pp_line->sb_text;
3488 if (text == NULL)
3489 text = (char_u *)"";
3490 add_scrollback_line_to_buffer(term, text, (int)STRLEN(text));
3491 vim_free(pp_line->sb_text);
3492
3493 line = (sb_line_T *)term->tl_scrollback.ga_data
3494 + term->tl_scrollback.ga_len;
3495 line->sb_cols = pp_line->sb_cols;
3496 line->sb_cells = pp_line->sb_cells;
3497 line->sb_fill_attr = pp_line->sb_fill_attr;
3498 line->sb_text = NULL;
3499 ++term->tl_scrollback_scrolled;
3500 ++term->tl_scrollback.ga_len;
3501 }
3502
3503 ga_clear(&term->tl_scrollback_postponed);
3504 limit_scrollback(term, &term->tl_scrollback, TRUE);
3505}
3506
LemonBoy77771d32022-04-13 11:47:25 +01003507/*
3508 * Called when the terminal wants to ring the system bell.
3509 */
3510 static int
3511handle_bell(void *user UNUSED)
3512{
Bram Moolenaaraae97622022-04-13 14:28:07 +01003513 vim_beep(BO_TERM);
LemonBoy77771d32022-04-13 11:47:25 +01003514 return 0;
3515}
3516
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003517static VTermScreenCallbacks screen_callbacks = {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003518 handle_damage, // damage
3519 handle_moverect, // moverect
3520 handle_movecursor, // movecursor
3521 handle_settermprop, // settermprop
LemonBoy77771d32022-04-13 11:47:25 +01003522 handle_bell, // bell
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003523 handle_resize, // resize
3524 handle_pushline, // sb_pushline
Bram Moolenaar6a12d262022-10-16 19:26:52 +01003525 NULL, // sb_popline
3526 NULL // sb_clear
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003527};
3528
3529/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003530 * Do the work after the channel of a terminal was closed.
3531 * Must be called only when updating_screen is FALSE.
3532 * Returns TRUE when a buffer was closed (list of terminals may have changed).
3533 */
3534 static int
3535term_after_channel_closed(term_T *term)
3536{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003537 // Unless in Terminal-Normal mode: clear the vterm.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003538 if (!term->tl_normal_mode)
3539 {
3540 int fnum = term->tl_buffer->b_fnum;
3541
3542 cleanup_vterm(term);
3543
3544 if (term->tl_finish == TL_FINISH_CLOSE)
3545 {
3546 aco_save_T aco;
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003547 int do_set_w_closing = term->tl_buffer->b_nwindows == 0;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003548#ifdef FEAT_PROP_POPUP
3549 win_T *pwin = NULL;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003550
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003551 // If this was a terminal in a popup window, go back to the
3552 // previous window.
3553 if (popup_is_popup(curwin) && curbuf == term->tl_buffer)
3554 {
3555 pwin = curwin;
3556 if (win_valid(prevwin))
3557 win_enter(prevwin, FALSE);
3558 }
3559 else
3560#endif
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003561 // If this is the last normal window: exit Vim.
3562 if (term->tl_buffer->b_nwindows > 0 && only_one_window())
3563 {
3564 exarg_T ea;
3565
Bram Moolenaara80faa82020-04-12 19:37:17 +02003566 CLEAR_FIELD(ea);
Bram Moolenaar4d14bac2019-10-20 21:15:15 +02003567 ex_quit(&ea);
3568 return TRUE;
3569 }
3570
Bram Moolenaar5db7eec2018-08-07 16:33:18 +02003571 // ++close or term_finish == "close"
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003572 ch_log(NULL, "terminal job finished, closing window");
3573 aucmd_prepbuf(&aco, term->tl_buffer);
Bram Moolenaare76062c2022-11-28 18:51:43 +00003574 if (curbuf == term->tl_buffer)
3575 {
3576 // Avoid closing the window if we temporarily use it.
3577 if (is_aucmd_win(curwin))
3578 do_set_w_closing = TRUE;
3579 if (do_set_w_closing)
3580 curwin->w_closing = TRUE;
3581 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
3582 if (do_set_w_closing)
3583 curwin->w_closing = FALSE;
3584 aucmd_restbuf(&aco);
3585 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003586#ifdef FEAT_PROP_POPUP
3587 if (pwin != NULL)
3588 popup_close_with_retval(pwin, 0);
3589#endif
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003590 return TRUE;
3591 }
3592 if (term->tl_finish == TL_FINISH_OPEN
3593 && term->tl_buffer->b_nwindows == 0)
3594 {
Bram Moolenaar47c5ea42020-11-12 15:12:15 +01003595 char *cmd = term->tl_opencmd == NULL
3596 ? "botright sbuf %d"
3597 : (char *)term->tl_opencmd;
3598 size_t len = strlen(cmd) + 50;
3599 char *buf = alloc(len);
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003600
Bram Moolenaar47c5ea42020-11-12 15:12:15 +01003601 if (buf != NULL)
3602 {
3603 ch_log(NULL, "terminal job finished, opening window");
3604 vim_snprintf(buf, len, cmd, fnum);
3605 do_cmdline_cmd((char_u *)buf);
3606 vim_free(buf);
3607 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003608 }
3609 else
3610 ch_log(NULL, "terminal job finished");
3611 }
3612
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003613 redraw_buf_and_status_later(term->tl_buffer, UPD_NOT_VALID);
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003614 return FALSE;
3615}
3616
Bram Moolenaard98c0b62020-02-02 15:25:16 +01003617#if defined(FEAT_PROP_POPUP) || defined(PROTO)
3618/*
3619 * If the current window is a terminal in a popup window and the job has
3620 * finished, close the popup window and to back to the previous window.
3621 * Otherwise return FAIL.
3622 */
3623 int
3624may_close_term_popup(void)
3625{
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003626 if (!popup_is_popup(curwin) || curbuf->b_term == NULL
3627 || term_job_running_not_none(curbuf->b_term))
3628 return FAIL;
Bram Moolenaard98c0b62020-02-02 15:25:16 +01003629
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00003630 win_T *pwin = curwin;
3631
3632 if (win_valid(prevwin))
3633 win_enter(prevwin, FALSE);
3634 popup_close_with_retval(pwin, 0);
3635 return OK;
Bram Moolenaard98c0b62020-02-02 15:25:16 +01003636}
3637#endif
3638
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003639/*
Bram Moolenaareea32af2021-11-21 14:51:13 +00003640 * Called when a channel is going to be closed, before invoking the close
3641 * callback.
3642 */
3643 void
3644term_channel_closing(channel_T *ch)
3645{
3646 term_T *term;
3647
3648 for (term = first_term; term != NULL; term = term->tl_next)
3649 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
3650 term->tl_channel_closing = TRUE;
3651}
3652
3653/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003654 * Called when a channel has been closed.
3655 * If this was a channel for a terminal window then finish it up.
3656 */
3657 void
3658term_channel_closed(channel_T *ch)
3659{
3660 term_T *term;
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003661 term_T *next_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003662 int did_one = FALSE;
3663
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003664 for (term = first_term; term != NULL; term = next_term)
3665 {
3666 next_term = term->tl_next;
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02003667 if (term->tl_job == ch->ch_job && !term->tl_channel_closed)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003668 {
3669 term->tl_channel_closed = TRUE;
3670 did_one = TRUE;
3671
Bram Moolenaard23a8232018-02-10 18:45:26 +01003672 VIM_CLEAR(term->tl_title);
3673 VIM_CLEAR(term->tl_status_text);
Bram Moolenaar4f974752019-02-17 17:44:42 +01003674#ifdef MSWIN
Bram Moolenaar402c8392018-05-06 22:01:42 +02003675 if (term->tl_out_fd != NULL)
3676 {
3677 fclose(term->tl_out_fd);
3678 term->tl_out_fd = NULL;
3679 }
3680#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003681
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003682 if (updating_screen)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003683 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003684 // Cannot open or close windows now. Can happen when
3685 // 'lazyredraw' is set.
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003686 term->tl_channel_recently_closed = TRUE;
3687 continue;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003688 }
3689
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003690 if (term_after_channel_closed(term))
3691 next_term = first_term;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003692 }
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003693 }
3694
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003695 if (did_one)
3696 {
3697 redraw_statuslines();
3698
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003699 // Need to break out of vgetc().
Bram Moolenaarb42c0d52020-05-29 22:41:41 +02003700 ins_char_typebuf(K_IGNORE, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003701 typebuf_was_filled = TRUE;
3702
3703 term = curbuf->b_term;
3704 if (term != NULL)
3705 {
3706 if (term->tl_job == ch->ch_job)
3707 maketitle();
3708 update_cursor(term, term->tl_cursor_visible);
3709 }
3710 }
3711}
3712
3713/*
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003714 * To be called after resetting updating_screen: handle any terminal where the
3715 * channel was closed.
3716 */
3717 void
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00003718term_check_channel_closed_recently(void)
Bram Moolenaar0cb8ac72018-05-11 22:01:51 +02003719{
3720 term_T *term;
3721 term_T *next_term;
3722
3723 for (term = first_term; term != NULL; term = next_term)
3724 {
3725 next_term = term->tl_next;
3726 if (term->tl_channel_recently_closed)
3727 {
3728 term->tl_channel_recently_closed = FALSE;
3729 if (term_after_channel_closed(term))
3730 // start over, the list may have changed
3731 next_term = first_term;
3732 }
3733 }
3734}
3735
3736/*
Bram Moolenaar13568252018-03-16 20:46:58 +01003737 * Fill one screen line from a line of the terminal.
3738 * Advances "pos" to past the last column.
3739 */
3740 static void
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003741term_line2screenline(
Bram Moolenaar83d47902020-03-26 20:34:00 +01003742 term_T *term,
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003743 win_T *wp,
3744 VTermScreen *screen,
3745 VTermPos *pos,
3746 int max_col)
Bram Moolenaar13568252018-03-16 20:46:58 +01003747{
3748 int off = screen_get_current_line_off();
3749
3750 for (pos->col = 0; pos->col < max_col; )
3751 {
3752 VTermScreenCell cell;
3753 int c;
3754
3755 if (vterm_screen_get_cell(screen, *pos, &cell) == 0)
Bram Moolenaara80faa82020-04-12 19:37:17 +02003756 CLEAR_FIELD(cell);
Bram Moolenaar13568252018-03-16 20:46:58 +01003757
3758 c = cell.chars[0];
3759 if (c == NUL)
3760 {
3761 ScreenLines[off] = ' ';
3762 if (enc_utf8)
3763 ScreenLinesUC[off] = NUL;
3764 }
3765 else
3766 {
3767 if (enc_utf8)
3768 {
3769 int i;
3770
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003771 // composing chars
Bram Moolenaar13568252018-03-16 20:46:58 +01003772 for (i = 0; i < Screen_mco
3773 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i)
3774 {
3775 ScreenLinesC[i][off] = cell.chars[i + 1];
3776 if (cell.chars[i + 1] == 0)
3777 break;
3778 }
3779 if (c >= 0x80 || (Screen_mco > 0
3780 && ScreenLinesC[0][off] != 0))
3781 {
3782 ScreenLines[off] = ' ';
3783 ScreenLinesUC[off] = c;
3784 }
3785 else
3786 {
3787 ScreenLines[off] = c;
3788 ScreenLinesUC[off] = NUL;
3789 }
3790 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01003791#ifdef MSWIN
Bram Moolenaar13568252018-03-16 20:46:58 +01003792 else if (has_mbyte && c >= 0x80)
3793 {
3794 char_u mb[MB_MAXBYTES+1];
3795 WCHAR wc = c;
3796
3797 if (WideCharToMultiByte(GetACP(), 0, &wc, 1,
3798 (char*)mb, 2, 0, 0) > 1)
3799 {
3800 ScreenLines[off] = mb[0];
3801 ScreenLines[off + 1] = mb[1];
3802 cell.width = mb_ptr2cells(mb);
3803 }
3804 else
3805 ScreenLines[off] = c;
3806 }
3807#endif
3808 else
Bram Moolenaar927495b2020-11-06 17:58:35 +01003809 // This will only store the lower byte of "c".
Bram Moolenaar13568252018-03-16 20:46:58 +01003810 ScreenLines[off] = c;
3811 }
Bram Moolenaar87fd0922021-11-20 13:47:45 +00003812 ScreenAttrs[off] = cell2attr(term, wp, &cell.attrs, &cell.fg,
3813 &cell.bg);
Bram Moolenaar13568252018-03-16 20:46:58 +01003814
3815 ++pos->col;
3816 ++off;
3817 if (cell.width == 2)
3818 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003819 // don't set the second byte to NUL for a DBCS encoding, it
3820 // has been set above
Bram Moolenaar927495b2020-11-06 17:58:35 +01003821 if (enc_utf8)
3822 {
3823 ScreenLinesUC[off] = NUL;
Bram Moolenaar13568252018-03-16 20:46:58 +01003824 ScreenLines[off] = NUL;
Bram Moolenaar927495b2020-11-06 17:58:35 +01003825 }
3826 else if (!has_mbyte)
3827 {
3828 // Can't show a double-width character with a single-byte
3829 // 'encoding', just use a space.
3830 ScreenLines[off] = ' ';
3831 ScreenAttrs[off] = ScreenAttrs[off - 1];
3832 }
Bram Moolenaar13568252018-03-16 20:46:58 +01003833
3834 ++pos->col;
3835 ++off;
3836 }
3837 }
3838}
3839
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003840#if defined(FEAT_GUI)
Bram Moolenaar13568252018-03-16 20:46:58 +01003841 static void
3842update_system_term(term_T *term)
3843{
3844 VTermPos pos;
3845 VTermScreen *screen;
3846
3847 if (term->tl_vterm == NULL)
3848 return;
3849 screen = vterm_obtain_screen(term->tl_vterm);
3850
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003851 // Scroll up to make more room for terminal lines if needed.
Bram Moolenaar13568252018-03-16 20:46:58 +01003852 while (term->tl_toprow > 0
3853 && (Rows - term->tl_toprow) < term->tl_dirty_row_end)
3854 {
3855 int save_p_more = p_more;
3856
3857 p_more = FALSE;
3858 msg_row = Rows - 1;
Bram Moolenaar113e1072019-01-20 15:30:40 +01003859 msg_puts("\n");
Bram Moolenaar13568252018-03-16 20:46:58 +01003860 p_more = save_p_more;
3861 --term->tl_toprow;
3862 }
3863
3864 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3865 && pos.row < Rows; ++pos.row)
3866 {
3867 if (pos.row < term->tl_rows)
3868 {
3869 int max_col = MIN(Columns, term->tl_cols);
3870
Bram Moolenaar83d47902020-03-26 20:34:00 +01003871 term_line2screenline(term, NULL, screen, &pos, max_col);
Bram Moolenaar13568252018-03-16 20:46:58 +01003872 }
3873 else
3874 pos.col = 0;
3875
Bram Moolenaar510f0372022-07-04 17:46:22 +01003876 screen_line(curwin, term->tl_toprow + pos.row, 0, pos.col, Columns, 0);
Bram Moolenaar13568252018-03-16 20:46:58 +01003877 }
3878
3879 term->tl_dirty_row_start = MAX_ROW;
3880 term->tl_dirty_row_end = 0;
Bram Moolenaar13568252018-03-16 20:46:58 +01003881}
Bram Moolenaar4ac31ee2018-03-16 21:34:25 +01003882#endif
Bram Moolenaar13568252018-03-16 20:46:58 +01003883
3884/*
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003885 * Return TRUE if window "wp" is to be redrawn with term_update_window().
3886 * Returns FALSE when there is no terminal running in this window or it is in
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003887 * Terminal-Normal mode.
3888 */
3889 int
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003890term_do_update_window(win_T *wp)
3891{
3892 term_T *term = wp->w_buffer->b_term;
3893
3894 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode;
3895}
3896
3897/*
3898 * Called to update a window that contains an active terminal.
3899 */
3900 void
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003901term_update_window(win_T *wp)
3902{
3903 term_T *term = wp->w_buffer->b_term;
3904 VTerm *vterm;
3905 VTermScreen *screen;
3906 VTermState *state;
3907 VTermPos pos;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003908 int rows, cols;
3909 int newrows, newcols;
3910 int minsize;
3911 win_T *twp;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003912
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003913 vterm = term->tl_vterm;
3914 screen = vterm_obtain_screen(vterm);
3915 state = vterm_obtain_state(vterm);
3916
Bram Moolenaara4d158b2022-08-14 14:17:45 +01003917 // We use UPD_NOT_VALID on a resize or scroll, redraw everything then.
3918 // With UPD_SOME_VALID only redraw what was marked dirty.
3919 if (wp->w_redr_type > UPD_SOME_VALID)
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003920 {
3921 term->tl_dirty_row_start = 0;
3922 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003923
3924 if (term->tl_postponed_scroll > 0
3925 && term->tl_postponed_scroll < term->tl_rows / 3)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003926 // Scrolling is usually faster than redrawing, when there are only
3927 // a few lines to scroll.
Bram Moolenaar6eddadf2018-05-06 16:40:16 +02003928 term_scroll_up(term, 0, term->tl_postponed_scroll);
3929 term->tl_postponed_scroll = 0;
Bram Moolenaar19a3d682017-10-02 21:54:59 +02003930 }
3931
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003932 /*
3933 * If the window was resized a redraw will be triggered and we get here.
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003934 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003935 */
Bram Moolenaarb833c1e2018-05-05 16:36:06 +02003936 minsize = parse_termwinsize(wp, &rows, &cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003937
Bram Moolenaar498c2562018-04-15 23:45:15 +02003938 newrows = 99999;
3939 newcols = 99999;
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003940 for (twp = firstwin; ; twp = twp->w_next)
Bram Moolenaar498c2562018-04-15 23:45:15 +02003941 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003942 // Always use curwin, it may be a popup window.
3943 win_T *wwp = twp == NULL ? curwin : twp;
3944
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003945 // When more than one window shows the same terminal, use the
3946 // smallest size.
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003947 if (wwp->w_buffer == term->tl_buffer)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003948 {
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003949 newrows = MIN(newrows, wwp->w_height);
3950 newcols = MIN(newcols, wwp->w_width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003951 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003952 if (twp == NULL)
3953 break;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003954 }
Bram Moolenaare0d749a2019-09-25 22:14:48 +02003955 if (newrows == 99999 || newcols == 99999)
3956 return; // safety exit
Bram Moolenaar498c2562018-04-15 23:45:15 +02003957 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows;
3958 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols;
3959
Bram Moolenaareba13e42021-02-23 17:47:23 +01003960 // If no cell is visible there is no point in resizing. Also, vterm can't
3961 // handle a zero height.
3962 if (newrows == 0 || newcols == 0)
3963 return;
3964
Bram Moolenaar498c2562018-04-15 23:45:15 +02003965 if (term->tl_rows != newrows || term->tl_cols != newcols)
3966 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003967 term->tl_vterm_size_changed = TRUE;
Bram Moolenaar498c2562018-04-15 23:45:15 +02003968 vterm_set_size(vterm, newrows, newcols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003969 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines",
Bram Moolenaar498c2562018-04-15 23:45:15 +02003970 newrows);
3971 term_report_winsize(term, newrows, newcols);
Bram Moolenaar875cf872018-07-08 20:49:07 +02003972
3973 // Updating the terminal size will cause the snapshot to be cleared.
3974 // When not in terminal_loop() we need to restore it.
3975 if (term != in_terminal_loop)
3976 may_move_terminal_to_buffer(term, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003977 }
3978
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01003979 // The cursor may have been moved when resizing.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003980 vterm_state_get_cursorpos(state, &pos);
Bram Moolenaarebec3e22020-11-28 20:22:06 +01003981 position_cursor(wp, &pos);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003982
Bram Moolenaar3a497e12017-09-30 20:40:27 +02003983 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end
3984 && pos.row < wp->w_height; ++pos.row)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003985 {
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003986 if (pos.row < term->tl_rows)
3987 {
Bram Moolenaar13568252018-03-16 20:46:58 +01003988 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003989
Bram Moolenaar83d47902020-03-26 20:34:00 +01003990 term_line2screenline(term, wp, screen, &pos, max_col);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02003991 }
3992 else
3993 pos.col = 0;
3994
Bram Moolenaar510f0372022-07-04 17:46:22 +01003995 screen_line(wp, wp->w_winrow + pos.row
Bram Moolenaarf118d482018-03-13 13:14:00 +01003996#ifdef FEAT_MENU
3997 + winbar_height(wp)
3998#endif
Bram Moolenaar219c7d02020-02-01 21:57:29 +01003999 , wp->w_wincol, pos.col, wp->w_width,
4000#ifdef FEAT_PROP_POPUP
4001 popup_is_popup(wp) ? SLF_POPUP :
4002#endif
4003 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004004 }
Bram Moolenaar3194e5b2021-12-13 21:59:09 +00004005}
4006
4007/*
4008 * Called after updating all windows: may reset dirty rows.
4009 */
4010 void
4011term_did_update_window(win_T *wp)
4012{
4013 term_T *term = wp->w_buffer->b_term;
4014
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00004015 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode
4016 || wp->w_redr_type != 0)
4017 return;
4018
4019 term->tl_dirty_row_start = MAX_ROW;
4020 term->tl_dirty_row_end = 0;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004021}
4022
4023/*
4024 * Return TRUE if "wp" is a terminal window where the job has finished.
4025 */
4026 int
4027term_is_finished(buf_T *buf)
4028{
4029 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
4030}
4031
4032/*
4033 * Return TRUE if "wp" is a terminal window where the job has finished or we
4034 * are in Terminal-Normal mode, thus we show the buffer contents.
4035 */
4036 int
4037term_show_buffer(buf_T *buf)
4038{
4039 term_T *term = buf->b_term;
4040
4041 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode);
4042}
4043
4044/*
4045 * The current buffer is going to be changed. If there is terminal
4046 * highlighting remove it now.
4047 */
4048 void
4049term_change_in_curbuf(void)
4050{
4051 term_T *term = curbuf->b_term;
4052
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00004053 if (!term_is_finished(curbuf) || term->tl_scrollback.ga_len <= 0)
4054 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004055
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00004056 free_scrollback(term);
4057 redraw_buf_later(term->tl_buffer, UPD_NOT_VALID);
4058
4059 // The buffer is now like a normal buffer, it cannot be easily
4060 // abandoned when changed.
4061 set_string_option_direct((char_u *)"buftype", -1,
4062 (char_u *)"", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004063}
4064
4065/*
4066 * Get the screen attribute for a position in the buffer.
4067 * Use a negative "col" to get the filler background color.
4068 */
4069 int
Bram Moolenaar219c7d02020-02-01 21:57:29 +01004070term_get_attr(win_T *wp, linenr_T lnum, int col)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004071{
Bram Moolenaar219c7d02020-02-01 21:57:29 +01004072 buf_T *buf = wp->w_buffer;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004073 term_T *term = buf->b_term;
4074 sb_line_T *line;
4075 cellattr_T *cellattr;
4076
4077 if (lnum > term->tl_scrollback.ga_len)
4078 cellattr = &term->tl_default_color;
4079 else
4080 {
4081 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
4082 if (col < 0 || col >= line->sb_cols)
4083 cellattr = &line->sb_fill_attr;
4084 else
4085 cellattr = line->sb_cells + col;
4086 }
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004087 return cell2attr(term, wp, &cellattr->attrs, &cellattr->fg, &cellattr->bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004088}
4089
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004090/*
4091 * Convert a cterm color number 0 - 255 to RGB.
Bram Moolenaara8fc0d32017-09-26 13:59:47 +02004092 * This is compatible with xterm.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004093 */
4094 static void
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02004095cterm_color2vterm(int nr, VTermColor *rgb)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004096{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004097 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->index);
4098 if (rgb->index == 0)
4099 rgb->type = VTERM_COLOR_RGB;
4100 else
4101 {
4102 rgb->type = VTERM_COLOR_INDEXED;
4103 --rgb->index;
4104 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004105}
4106
4107/*
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004108 * Initialize vterm color from the synID.
4109 * Returns TRUE if color is set to "fg" and "bg".
4110 * Otherwise returns FALSE.
4111 */
4112 static int
4113get_vterm_color_from_synid(int id, VTermColor *fg, VTermColor *bg)
4114{
4115#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
4116 // Use the actual color for the GUI and when 'termguicolors' is set.
4117 if (0
4118# ifdef FEAT_GUI
4119 || gui.in_use
4120# endif
4121# ifdef FEAT_TERMGUICOLORS
4122 || p_tgc
4123# ifdef FEAT_VTP
4124 // Finally get INVALCOLOR on this execution path
4125 || (!p_tgc && t_colors >= 256)
4126# endif
4127# endif
4128 )
4129 {
4130 guicolor_T fg_rgb = INVALCOLOR;
4131 guicolor_T bg_rgb = INVALCOLOR;
4132
4133 if (id > 0)
4134 syn_id2colors(id, &fg_rgb, &bg_rgb);
4135
4136 if (fg_rgb != INVALCOLOR)
4137 {
4138 long_u rgb = GUI_MCH_GET_RGB(fg_rgb);
4139 fg->red = (unsigned)(rgb >> 16);
4140 fg->green = (unsigned)(rgb >> 8) & 255;
4141 fg->blue = (unsigned)rgb & 255;
4142 fg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_FG;
4143 }
4144 else
4145 fg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
4146
4147 if (bg_rgb != INVALCOLOR)
4148 {
4149 long_u rgb = GUI_MCH_GET_RGB(bg_rgb);
4150 bg->red = (unsigned)(rgb >> 16);
4151 bg->green = (unsigned)(rgb >> 8) & 255;
4152 bg->blue = (unsigned)rgb & 255;
4153 bg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_BG;
4154 }
4155 else
4156 bg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
4157
4158 return TRUE;
4159 }
4160 else
4161#endif
4162 if (t_colors >= 16)
4163 {
4164 int cterm_fg = -1;
4165 int cterm_bg = -1;
4166
4167 if (id > 0)
4168 syn_id2cterm_bg(id, &cterm_fg, &cterm_bg);
4169
4170 if (cterm_fg >= 0)
4171 {
4172 cterm_color2vterm(cterm_fg, fg);
4173 fg->type |= VTERM_COLOR_DEFAULT_FG;
4174 }
4175 else
4176 fg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
4177
4178 if (cterm_bg >= 0)
4179 {
4180 cterm_color2vterm(cterm_bg, bg);
4181 bg->type |= VTERM_COLOR_DEFAULT_BG;
4182 }
4183 else
4184 bg->type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
4185
4186 return TRUE;
4187 }
4188
4189 return FALSE;
4190}
4191
4192 void
4193term_reset_wincolor(win_T *wp)
4194{
4195 wp->w_term_wincolor.fg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
4196 wp->w_term_wincolor.bg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
4197}
4198
4199/*
4200 * Cache the color of 'wincolor'.
4201 */
4202 void
4203term_update_wincolor(win_T *wp)
4204{
4205 int id = 0;
4206
4207 if (*wp->w_p_wcr != NUL)
4208 id = syn_name2id(wp->w_p_wcr);
4209 if (id == 0 || !get_vterm_color_from_synid(id, &wp->w_term_wincolor.fg,
4210 &wp->w_term_wincolor.bg))
4211 term_reset_wincolor(wp);
4212}
4213
4214/*
4215 * Called when option 'termguicolors' was set,
4216 * or when any highlight is changed.
4217 */
4218 void
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00004219term_update_wincolor_all(void)
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004220{
4221 win_T *wp = NULL;
4222 int did_curwin = FALSE;
4223
4224 while (for_all_windows_and_curwin(&wp, &did_curwin))
4225 term_update_wincolor(wp);
4226}
4227
4228/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004229 * Initialize term->tl_default_color from the environment.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004230 */
4231 static void
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004232init_default_colors(term_T *term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004233{
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004234 VTermColor *fg, *bg;
4235 int fgval, bgval;
4236 int id;
4237
Bram Moolenaara80faa82020-04-12 19:37:17 +02004238 CLEAR_FIELD(term->tl_default_color.attrs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004239 term->tl_default_color.width = 1;
4240 fg = &term->tl_default_color.fg;
4241 bg = &term->tl_default_color.bg;
4242
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004243 // Vterm uses a default black background. Set it to white when
4244 // 'background' is "light".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004245 if (*p_bg == 'l')
4246 {
4247 fgval = 0;
4248 bgval = 255;
4249 }
4250 else
4251 {
4252 fgval = 255;
4253 bgval = 0;
4254 }
4255 fg->red = fg->green = fg->blue = fgval;
4256 bg->red = bg->green = bg->blue = bgval;
Bram Moolenaare5886cc2020-05-21 20:10:04 +02004257 fg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_FG;
4258 bg->type = VTERM_COLOR_RGB | VTERM_COLOR_DEFAULT_BG;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004259
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004260 // The highlight group overrules the defaults.
4261 id = term_get_highlight_id(term, NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004262
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004263 if (!get_vterm_color_from_synid(id, fg, bg))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004264 {
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004265#if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004266 int tmp;
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004267#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004268
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004269 // In an MS-Windows console we know the normal colors.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004270 if (cterm_normal_fg_color > 0)
4271 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02004272 cterm_color2vterm(cterm_normal_fg_color - 1, fg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004273# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
4274# ifdef VIMDLL
4275 if (!gui.in_use)
4276# endif
4277 {
4278 tmp = fg->red;
4279 fg->red = fg->blue;
4280 fg->blue = tmp;
4281 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004282# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004283 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02004284# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004285 else
4286 term_get_fg_color(&fg->red, &fg->green, &fg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02004287# endif
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004288
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004289 if (cterm_normal_bg_color > 0)
4290 {
Bram Moolenaarc5cd8852018-05-01 15:47:38 +02004291 cterm_color2vterm(cterm_normal_bg_color - 1, bg);
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004292# if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL))
4293# ifdef VIMDLL
4294 if (!gui.in_use)
4295# endif
4296 {
4297 tmp = fg->red;
4298 fg->red = fg->blue;
4299 fg->blue = tmp;
4300 }
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004301# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004302 }
Bram Moolenaar9377df32017-10-15 13:22:01 +02004303# ifdef FEAT_TERMRESPONSE
Bram Moolenaar65e4c4f2017-10-14 23:24:25 +02004304 else
4305 term_get_bg_color(&bg->red, &bg->green, &bg->blue);
Bram Moolenaar9377df32017-10-15 13:22:01 +02004306# endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004307 }
Bram Moolenaar52acb112018-03-18 19:20:22 +01004308}
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004309
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004310#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
4311/*
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004312 * Return TRUE if the user-defined palette (either g:terminal_ansi_colors or the
4313 * "ansi_colors" argument in term_start()) shall be applied.
4314 */
4315 static int
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00004316term_use_palette(void)
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004317{
4318 if (0
4319#ifdef FEAT_GUI
4320 || gui.in_use
4321#endif
4322#ifdef FEAT_TERMGUICOLORS
4323 || p_tgc
4324#endif
4325 )
4326 return TRUE;
4327 return FALSE;
4328}
4329
4330/*
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004331 * Set the 16 ANSI colors from array of RGB values
4332 */
4333 static void
4334set_vterm_palette(VTerm *vterm, long_u *rgb)
4335{
4336 int index = 0;
4337 VTermState *state = vterm_obtain_state(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004338
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004339 for (; index < 16; index++)
4340 {
4341 VTermColor color;
Bram Moolenaaref8c83c2019-04-11 11:40:13 +02004342
Millyb771b6b2021-11-23 12:07:25 +00004343 color.type = VTERM_COLOR_RGB;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004344 color.red = (unsigned)(rgb[index] >> 16);
4345 color.green = (unsigned)(rgb[index] >> 8) & 255;
4346 color.blue = (unsigned)rgb[index] & 255;
Bram Moolenaar68afde42022-02-25 20:48:26 +00004347 color.index = 0;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004348 vterm_state_set_palette_color(state, index, &color);
4349 }
4350}
4351
4352/*
4353 * Set the ANSI color palette from a list of colors
4354 */
4355 static int
4356set_ansi_colors_list(VTerm *vterm, list_T *list)
4357{
4358 int n = 0;
4359 long_u rgb[16];
Bram Moolenaarb0992022020-01-30 14:55:42 +01004360 listitem_T *li;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004361
Bram Moolenaarb0992022020-01-30 14:55:42 +01004362 for (li = list->lv_first; li != NULL && n < 16; li = li->li_next, n++)
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004363 {
4364 char_u *color_name;
4365 guicolor_T guicolor;
4366
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004367 color_name = tv_get_string_chk(&li->li_tv);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004368 if (color_name == NULL)
4369 return FAIL;
4370
4371 guicolor = GUI_GET_COLOR(color_name);
4372 if (guicolor == INVALCOLOR)
4373 return FAIL;
4374
4375 rgb[n] = GUI_MCH_GET_RGB(guicolor);
4376 }
4377
4378 if (n != 16 || li != NULL)
4379 return FAIL;
4380
4381 set_vterm_palette(vterm, rgb);
4382
4383 return OK;
4384}
4385
4386/*
4387 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15]
4388 */
4389 static void
4390init_vterm_ansi_colors(VTerm *vterm)
4391{
4392 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE);
4393
LemonBoyb2b3acb2022-05-20 10:10:34 +01004394 if (var == NULL)
4395 return;
4396
4397 if (var->di_tv.v_type != VAR_LIST
4398 || var->di_tv.vval.v_list == NULL
4399 || var->di_tv.vval.v_list->lv_first == &range_list_item
4400 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL)
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00004401 semsg(_(e_invalid_argument_str), "g:terminal_ansi_colors");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004402}
4403#endif
4404
Bram Moolenaar52acb112018-03-18 19:20:22 +01004405/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004406 * Handles a "drop" command from the job in the terminal.
4407 * "item" is the file name, "item->li_next" may have options.
4408 */
4409 static void
4410handle_drop_command(listitem_T *item)
4411{
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004412 char_u *fname = tv_get_string(&item->li_tv);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004413 listitem_T *opt_item = item->li_next;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004414 int bufnr;
4415 win_T *wp;
4416 tabpage_T *tp;
4417 exarg_T ea;
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004418 char_u *tofree = NULL;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004419
4420 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT);
4421 FOR_ALL_TAB_WINDOWS(tp, wp)
4422 {
4423 if (wp->w_buffer->b_fnum == bufnr)
4424 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004425 // buffer is in a window already, go there
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004426 goto_tabpage_win(tp, wp);
4427 return;
4428 }
4429 }
4430
Bram Moolenaara80faa82020-04-12 19:37:17 +02004431 CLEAR_FIELD(ea);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004432
4433 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT
4434 && opt_item->li_tv.vval.v_dict != NULL)
4435 {
4436 dict_T *dict = opt_item->li_tv.vval.v_dict;
4437 char_u *p;
4438
Bram Moolenaard61efa52022-07-23 09:52:04 +01004439 p = dict_get_string(dict, "ff", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004440 if (p == NULL)
Bram Moolenaard61efa52022-07-23 09:52:04 +01004441 p = dict_get_string(dict, "fileformat", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004442 if (p != NULL)
4443 {
4444 if (check_ff_value(p) == FAIL)
4445 ch_log(NULL, "Invalid ff argument to drop: %s", p);
4446 else
4447 ea.force_ff = *p;
4448 }
Bram Moolenaard61efa52022-07-23 09:52:04 +01004449 p = dict_get_string(dict, "enc", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004450 if (p == NULL)
Bram Moolenaard61efa52022-07-23 09:52:04 +01004451 p = dict_get_string(dict, "encoding", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004452 if (p != NULL)
4453 {
Bram Moolenaar51e14382019-05-25 20:21:28 +02004454 ea.cmd = alloc(STRLEN(p) + 12);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004455 if (ea.cmd != NULL)
4456 {
4457 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p);
4458 ea.force_enc = 11;
4459 tofree = ea.cmd;
4460 }
4461 }
4462
Bram Moolenaard61efa52022-07-23 09:52:04 +01004463 p = dict_get_string(dict, "bad", FALSE);
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004464 if (p != NULL)
4465 get_bad_opt(p, &ea);
4466
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004467 if (dict_has_key(dict, "bin"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004468 ea.force_bin = FORCE_BIN;
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004469 if (dict_has_key(dict, "binary"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004470 ea.force_bin = FORCE_BIN;
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004471 if (dict_has_key(dict, "nobin"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004472 ea.force_bin = FORCE_NOBIN;
Yegappan Lakshmanan4829c1c2022-04-04 15:16:54 +01004473 if (dict_has_key(dict, "nobinary"))
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004474 ea.force_bin = FORCE_NOBIN;
4475 }
4476
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004477 // open in new window, like ":split fname"
Bram Moolenaar333b80a2018-04-04 22:57:29 +02004478 if (ea.cmd == NULL)
4479 ea.cmd = (char_u *)"split";
4480 ea.arg = fname;
4481 ea.cmdidx = CMD_split;
4482 ex_splitview(&ea);
4483
4484 vim_free(tofree);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004485}
4486
4487/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004488 * Return TRUE if "func" starts with "pat" and "pat" isn't empty.
4489 */
4490 static int
4491is_permitted_term_api(char_u *func, char_u *pat)
4492{
4493 return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0;
4494}
4495
4496/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004497 * Handles a function call from the job running in a terminal.
4498 * "item" is the function name, "item->li_next" has the arguments.
4499 */
4500 static void
4501handle_call_command(term_T *term, channel_T *channel, listitem_T *item)
4502{
4503 char_u *func;
4504 typval_T argvars[2];
4505 typval_T rettv;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02004506 funcexe_T funcexe;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004507
4508 if (item->li_next == NULL)
4509 {
4510 ch_log(channel, "Missing function arguments for call");
4511 return;
4512 }
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004513 func = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004514
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004515 if (!is_permitted_term_api(func, term->tl_api))
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004516 {
Bram Moolenaard2842ea2019-09-26 23:08:54 +02004517 ch_log(channel, "Unpermitted function: %s", func);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004518 return;
4519 }
4520
4521 argvars[0].v_type = VAR_NUMBER;
4522 argvars[0].vval.v_number = term->tl_buffer->b_fnum;
4523 argvars[1] = item->li_next->li_tv;
Bram Moolenaara80faa82020-04-12 19:37:17 +02004524 CLEAR_FIELD(funcexe);
Bram Moolenaar851f86b2021-12-13 14:26:44 +00004525 funcexe.fe_firstline = 1L;
4526 funcexe.fe_lastline = 1L;
4527 funcexe.fe_evaluate = TRUE;
Bram Moolenaarc6538bc2019-08-03 18:17:11 +02004528 if (call_func(func, -1, &rettv, 2, argvars, &funcexe) == OK)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004529 {
4530 clear_tv(&rettv);
4531 ch_log(channel, "Function %s called", func);
4532 }
4533 else
4534 ch_log(channel, "Calling function %s failed", func);
4535}
4536
4537/*
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004538 * URL decoding (also know as Percent-encoding).
4539 *
4540 * Note this function currently is only used for decoding shell's
4541 * OSC 7 escape sequence which we can assume all bytes are valid
4542 * UTF-8 bytes. Thus we don't need to deal with invalid UTF-8
4543 * encoding bytes like 0xfe, 0xff.
4544 */
4545 static size_t
4546url_decode(const char *src, const size_t len, char_u *dst)
4547{
4548 size_t i = 0, j = 0;
4549
4550 while (i < len)
4551 {
4552 if (src[i] == '%' && i + 2 < len)
4553 {
4554 dst[j] = hexhex2nr((char_u *)&src[i + 1]);
4555 j++;
4556 i += 3;
4557 }
4558 else
4559 {
4560 dst[j] = src[i];
4561 i++;
4562 j++;
4563 }
4564 }
4565 dst[j] = '\0';
4566 return j;
4567}
4568
4569/*
4570 * Sync terminal buffer's cwd with shell's pwd with the help of OSC 7.
4571 *
4572 * The OSC 7 sequence has the format of
4573 * "\033]7;file://HOSTNAME/CURRENT/DIR\033\\"
4574 * and what VTerm provides via VTermStringFragment is
4575 * "file://HOSTNAME/CURRENT/DIR"
4576 */
4577 static void
Bram Moolenaar474ad392022-08-21 11:37:17 +01004578sync_shell_dir(garray_T *gap)
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004579{
Bram Moolenaar474ad392022-08-21 11:37:17 +01004580 int offset = 7; // len of "file://" is 7
4581 char *pos = (char *)gap->ga_data + offset;
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004582 char_u *new_dir;
4583
4584 // remove HOSTNAME to get PWD
Bram Moolenaar474ad392022-08-21 11:37:17 +01004585 while (offset < (int)gap->ga_len && *pos != '/' )
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004586 {
Bram Moolenaar474ad392022-08-21 11:37:17 +01004587 ++offset;
4588 ++pos;
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004589 }
4590
Bram Moolenaar474ad392022-08-21 11:37:17 +01004591 if (offset >= (int)gap->ga_len)
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004592 {
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004593 semsg(_(e_failed_to_extract_pwd_from_str_check_your_shell_config),
Bram Moolenaar474ad392022-08-21 11:37:17 +01004594 gap->ga_data);
Bram Moolenaar6ed545e2022-05-09 20:09:23 +01004595 return;
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004596 }
4597
Bram Moolenaar474ad392022-08-21 11:37:17 +01004598 new_dir = alloc(gap->ga_len - offset + 1);
4599 url_decode(pos, gap->ga_len-offset, new_dir);
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004600 changedir_func(new_dir, TRUE, CDSCOPE_WINDOW);
4601 vim_free(new_dir);
4602}
4603
4604/*
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004605 * Called by libvterm when it cannot recognize an OSC sequence.
4606 * We recognize a terminal API command.
4607 */
4608 static int
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02004609parse_osc(int command, VTermStringFragment frag, void *user)
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004610{
4611 term_T *term = (term_T *)user;
4612 js_read_T reader;
4613 typval_T tv;
4614 channel_T *channel = term->tl_job == NULL ? NULL
4615 : term->tl_job->jv_channel;
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004616 garray_T *gap = &term->tl_osc_buf;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004617
Bram Moolenaar8b9abfd2021-03-29 20:49:05 +02004618 // We recognize only OSC 5 1 ; {command} and OSC 7 ; {command}
Bram Moolenaar474ad392022-08-21 11:37:17 +01004619 if (command != 51 && (command != 7 || !p_asd))
Bram Moolenaarbe593bf2020-05-19 21:20:04 +02004620 return 0;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004621
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004622 // Concatenate what was received until the final piece is found.
4623 if (ga_grow(gap, (int)frag.len + 1) == FAIL)
4624 {
4625 ga_clear(gap);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004626 return 1;
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004627 }
4628 mch_memmove((char *)gap->ga_data + gap->ga_len, frag.str, frag.len);
Bram Moolenaarf4b68e92020-05-27 21:22:14 +02004629 gap->ga_len += (int)frag.len;
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004630 if (!frag.final)
4631 return 1;
4632
4633 ((char *)gap->ga_data)[gap->ga_len] = 0;
Bram Moolenaar474ad392022-08-21 11:37:17 +01004634
4635 if (command == 7)
4636 {
4637 sync_shell_dir(gap);
4638 ga_clear(gap);
4639 return 1;
4640 }
4641
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004642 reader.js_buf = gap->ga_data;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004643 reader.js_fill = NULL;
4644 reader.js_used = 0;
4645 if (json_decode(&reader, &tv, 0) == OK
4646 && tv.v_type == VAR_LIST
4647 && tv.vval.v_list != NULL)
4648 {
4649 listitem_T *item = tv.vval.v_list->lv_first;
4650
4651 if (item == NULL)
4652 ch_log(channel, "Missing command");
4653 else
4654 {
Bram Moolenaard155d7a2018-12-21 16:04:21 +01004655 char_u *cmd = tv_get_string(&item->li_tv);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004656
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004657 // Make sure an invoked command doesn't delete the buffer (and the
4658 // terminal) under our fingers.
Bram Moolenaara997b452018-04-17 23:24:06 +02004659 ++term->tl_buffer->b_locked;
4660
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004661 item = item->li_next;
4662 if (item == NULL)
4663 ch_log(channel, "Missing argument for %s", cmd);
4664 else if (STRCMP(cmd, "drop") == 0)
4665 handle_drop_command(item);
4666 else if (STRCMP(cmd, "call") == 0)
4667 handle_call_command(term, channel, item);
4668 else
4669 ch_log(channel, "Invalid command received: %s", cmd);
Bram Moolenaara997b452018-04-17 23:24:06 +02004670 --term->tl_buffer->b_locked;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004671 }
4672 }
4673 else
4674 ch_log(channel, "Invalid JSON received");
4675
Bram Moolenaareaa3e0d2020-05-19 23:11:00 +02004676 ga_clear(gap);
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004677 clear_tv(&tv);
4678 return 1;
4679}
4680
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004681/*
4682 * Called by libvterm when it cannot recognize a CSI sequence.
4683 * We recognize the window position report.
4684 */
4685 static int
4686parse_csi(
4687 const char *leader UNUSED,
4688 const long args[],
4689 int argcount,
4690 const char *intermed UNUSED,
4691 char command,
4692 void *user)
4693{
4694 term_T *term = (term_T *)user;
4695 char buf[100];
4696 int len;
4697 int x = 0;
4698 int y = 0;
4699 win_T *wp;
4700
4701 // We recognize only CSI 13 t
4702 if (command != 't' || argcount != 1 || args[0] != 13)
4703 return 0; // not handled
4704
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004705 // When getting the window position is not possible or it fails it results
4706 // in zero/zero.
Bram Moolenaar16c34c32019-04-06 22:01:24 +02004707#if defined(FEAT_GUI) \
4708 || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \
4709 || defined(MSWIN)
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004710 (void)ui_get_winpos(&x, &y, (varnumber_T)100);
Bram Moolenaar6bc93052019-04-06 20:00:19 +02004711#endif
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004712
4713 FOR_ALL_WINDOWS(wp)
4714 if (wp->w_buffer == term->tl_buffer)
4715 break;
4716 if (wp != NULL)
4717 {
4718#ifdef FEAT_GUI
4719 if (gui.in_use)
4720 {
4721 x += wp->w_wincol * gui.char_width;
4722 y += W_WINROW(wp) * gui.char_height;
4723 }
4724 else
4725#endif
4726 {
4727 // We roughly estimate the position of the terminal window inside
Bram Moolenaarafde13b2019-04-28 19:46:49 +02004728 // the Vim window by assuming a 10 x 7 character cell.
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004729 x += wp->w_wincol * 7;
4730 y += W_WINROW(wp) * 10;
4731 }
4732 }
4733
4734 len = vim_snprintf(buf, 100, "\x1b[3;%d;%dt", x, y);
4735 channel_send(term->tl_job->jv_channel, get_tty_part(term),
4736 (char_u *)buf, len, NULL);
4737 return 1;
4738}
4739
Bram Moolenaard8637282020-05-20 18:41:41 +02004740static VTermStateFallbacks state_fallbacks = {
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004741 NULL, // control
Bram Moolenaarfa1e90c2019-04-06 17:47:40 +02004742 parse_csi, // csi
4743 parse_osc, // osc
Bram Moolenaar7da34152021-11-24 19:30:55 +00004744 NULL, // dcs
4745 NULL, // apc
4746 NULL, // pm
4747 NULL // sos
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004748};
4749
4750/*
Bram Moolenaar756ef112018-04-10 12:04:27 +02004751 * Use Vim's allocation functions for vterm so profiling works.
4752 */
4753 static void *
4754vterm_malloc(size_t size, void *data UNUSED)
4755{
Bram Moolenaar88137392021-11-12 16:01:15 +00004756 // make sure that the length is not zero
4757 return alloc_clear(size == 0 ? 1L : size);
Bram Moolenaar756ef112018-04-10 12:04:27 +02004758}
4759
4760 static void
4761vterm_memfree(void *ptr, void *data UNUSED)
4762{
4763 vim_free(ptr);
4764}
4765
4766static VTermAllocatorFunctions vterm_allocator = {
4767 &vterm_malloc,
4768 &vterm_memfree
4769};
4770
4771/*
Bram Moolenaar52acb112018-03-18 19:20:22 +01004772 * Create a new vterm and initialize it.
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004773 * Return FAIL when out of memory.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004774 */
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004775 static int
Bram Moolenaar52acb112018-03-18 19:20:22 +01004776create_vterm(term_T *term, int rows, int cols)
4777{
4778 VTerm *vterm;
4779 VTermScreen *screen;
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004780 VTermState *state;
Bram Moolenaar52acb112018-03-18 19:20:22 +01004781 VTermValue value;
4782
Bram Moolenaar756ef112018-04-10 12:04:27 +02004783 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004784 term->tl_vterm = vterm;
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004785 if (vterm == NULL)
4786 return FAIL;
4787
4788 // Allocate screen and state here, so we can bail out if that fails.
4789 state = vterm_obtain_state(vterm);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004790 screen = vterm_obtain_screen(vterm);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004791 if (state == NULL || screen == NULL)
4792 {
4793 vterm_free(vterm);
4794 return FAIL;
4795 }
4796
Bram Moolenaar52acb112018-03-18 19:20:22 +01004797 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004798 // TODO: depends on 'encoding'.
Bram Moolenaar52acb112018-03-18 19:20:22 +01004799 vterm_set_utf8(vterm, 1);
4800
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004801 init_default_colors(term);
Bram Moolenaar52acb112018-03-18 19:20:22 +01004802
4803 vterm_state_set_default_colors(
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004804 state,
Bram Moolenaar52acb112018-03-18 19:20:22 +01004805 &term->tl_default_color.fg,
4806 &term->tl_default_color.bg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004807
Bram Moolenaar9e587872019-05-13 20:27:23 +02004808 if (t_colors < 16)
4809 // Less than 16 colors: assume that bold means using a bright color for
4810 // the foreground color.
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02004811 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1);
4812
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004813 // Required to initialize most things.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004814 vterm_screen_reset(screen, 1 /* hard */);
4815
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004816 // Allow using alternate screen.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004817 vterm_screen_enable_altscreen(screen, 1);
4818
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01004819 // For unix do not use a blinking cursor. In an xterm this causes the
4820 // cursor to blink if it's blinking in the xterm.
4821 // For Windows we respect the system wide setting.
Bram Moolenaar4f974752019-02-17 17:44:42 +01004822#ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004823 if (GetCaretBlinkTime() == INFINITE)
4824 value.boolean = 0;
4825 else
4826 value.boolean = 1;
4827#else
4828 value.boolean = 0;
4829#endif
Bram Moolenaar8fbaeb12018-03-25 18:20:17 +02004830 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value);
Bram Moolenaard8637282020-05-20 18:41:41 +02004831 vterm_state_set_unrecognised_fallbacks(state, &state_fallbacks, term);
Bram Moolenaarcd929f72018-12-24 21:38:45 +01004832
4833 return OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004834}
4835
4836/*
LemonBoyb2b3acb2022-05-20 10:10:34 +01004837 * Reset the terminal palette to its default value.
4838 */
4839 static void
4840term_reset_palette(VTerm *vterm)
4841{
4842 VTermState *state = vterm_obtain_state(vterm);
4843 int index;
4844
4845 for (index = 0; index < 16; index++)
4846 {
4847 VTermColor color;
4848
4849 color.type = VTERM_COLOR_INDEXED;
4850 ansi_color2rgb(index, &color.red, &color.green, &color.blue,
4851 &color.index);
4852 // The first valid index starts at 1.
4853 color.index -= 1;
4854
4855 vterm_state_set_palette_color(state, index, &color);
4856 }
4857}
4858
4859 static void
4860term_update_palette(term_T *term)
4861{
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004862#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01004863 if (term_use_palette()
4864 && (term->tl_palette != NULL
4865 || find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE)
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004866 != NULL))
LemonBoyb2b3acb2022-05-20 10:10:34 +01004867 {
4868 if (term->tl_palette != NULL)
4869 set_vterm_palette(term->tl_vterm, term->tl_palette);
4870 else
4871 init_vterm_ansi_colors(term->tl_vterm);
4872 }
4873 else
Bram Moolenaar30b9a412022-05-26 14:06:37 +01004874#endif
LemonBoyb2b3acb2022-05-20 10:10:34 +01004875 term_reset_palette(term->tl_vterm);
4876}
4877
4878/*
4879 * Called when option 'termguicolors' is changed.
4880 */
4881 void
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00004882term_update_palette_all(void)
LemonBoyb2b3acb2022-05-20 10:10:34 +01004883{
4884 term_T *term;
4885
4886 FOR_ALL_TERMS(term)
4887 {
4888 if (term->tl_vterm == NULL)
4889 continue;
4890 term_update_palette(term);
4891 }
4892}
4893
4894/*
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004895 * Called when option 'background' or 'termguicolors' was set,
4896 * or when any highlight is changed.
Bram Moolenaarad431992021-05-03 20:40:38 +02004897 */
4898 void
4899term_update_colors_all(void)
4900{
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004901 term_T *term;
Bram Moolenaarad431992021-05-03 20:40:38 +02004902
Bram Moolenaar87fd0922021-11-20 13:47:45 +00004903 FOR_ALL_TERMS(term)
4904 {
4905 if (term->tl_vterm == NULL)
4906 continue;
4907 init_default_colors(term);
4908 vterm_state_set_default_colors(
4909 vterm_obtain_state(term->tl_vterm),
4910 &term->tl_default_color.fg,
4911 &term->tl_default_color.bg);
4912 }
Bram Moolenaar219c7d02020-02-01 21:57:29 +01004913}
4914
4915/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004916 * Return the text to show for the buffer name and status.
4917 */
4918 char_u *
4919term_get_status_text(term_T *term)
4920{
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00004921 if (term->tl_status_text != NULL)
4922 return term->tl_status_text;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004923
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00004924 char_u *txt;
4925 size_t len;
4926 char_u *fname;
4927
4928 if (term->tl_normal_mode)
4929 {
4930 if (term_job_running(term))
4931 txt = (char_u *)_("Terminal");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004932 else
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00004933 txt = (char_u *)_("Terminal-finished");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004934 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00004935 else if (term->tl_title != NULL)
4936 txt = term->tl_title;
4937 else if (term_none_open(term))
4938 txt = (char_u *)_("active");
4939 else if (term_job_running(term))
4940 txt = (char_u *)_("running");
4941 else
4942 txt = (char_u *)_("finished");
4943 fname = buf_get_fname(term->tl_buffer);
4944 len = 9 + STRLEN(fname) + STRLEN(txt);
4945 term->tl_status_text = alloc(len);
4946 if (term->tl_status_text != NULL)
4947 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
4948 fname, txt);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004949 return term->tl_status_text;
4950}
4951
4952/*
Bram Moolenaar3ad69532021-11-19 17:01:08 +00004953 * Clear the cached value of the status text.
4954 */
4955 void
4956term_clear_status_text(term_T *term)
4957{
4958 VIM_CLEAR(term->tl_status_text);
4959}
4960
4961/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004962 * Mark references in jobs of terminals.
4963 */
4964 int
4965set_ref_in_term(int copyID)
4966{
4967 int abort = FALSE;
4968 term_T *term;
4969 typval_T tv;
4970
Bram Moolenaar75a1a942019-06-20 03:45:36 +02004971 for (term = first_term; !abort && term != NULL; term = term->tl_next)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004972 if (term->tl_job != NULL)
4973 {
4974 tv.v_type = VAR_JOB;
4975 tv.vval.v_job = term->tl_job;
4976 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
4977 }
4978 return abort;
4979}
4980
4981/*
4982 * Get the buffer from the first argument in "argvars".
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004983 * Returns NULL when the buffer is not for a terminal window and logs a message
4984 * with "where".
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004985 */
4986 static buf_T *
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004987term_get_buf(typval_T *argvars, char *where)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004988{
4989 buf_T *buf;
4990
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004991 ++emsg_off;
Bram Moolenaarf2d79fa2019-01-03 22:19:27 +01004992 buf = tv_get_buf(&argvars[0], FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004993 --emsg_off;
4994 if (buf == NULL || buf->b_term == NULL)
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004995 {
Bram Moolenaar4d05af02020-11-27 20:55:00 +01004996 (void)tv_get_number(&argvars[0]); // issue errmsg if type error
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004997 ch_log(NULL, "%s: invalid buffer argument", where);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02004998 return NULL;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01004999 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005000 return buf;
5001}
5002
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005003 static void
5004clear_cell(VTermScreenCell *cell)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005005{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005006 CLEAR_FIELD(*cell);
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005007 cell->fg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_FG;
5008 cell->bg.type = VTERM_COLOR_INVALID | VTERM_COLOR_DEFAULT_BG;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005009}
5010
5011 static void
5012dump_term_color(FILE *fd, VTermColor *color)
5013{
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005014 int index;
5015
5016 if (VTERM_COLOR_IS_INDEXED(color))
5017 index = color->index + 1;
5018 else if (color->type == 0)
5019 // use RGB values
5020 index = 255;
5021 else
5022 // default color
5023 index = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005024 fprintf(fd, "%02x%02x%02x%d",
5025 (int)color->red, (int)color->green, (int)color->blue,
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005026 index);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005027}
5028
5029/*
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005030 * "term_dumpwrite(buf, filename, options)" function
Bram Moolenaard96ff162018-02-18 22:13:29 +01005031 *
5032 * Each screen cell in full is:
5033 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx}
5034 * {characters} is a space for an empty cell
5035 * For a double-width character "+" is changed to "*" and the next cell is
5036 * skipped.
5037 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc.
5038 * when "&" use the same as the previous cell.
5039 * {fg-color} is hex RGB, when "&" use the same as the previous cell.
5040 * {bg-color} is hex RGB, when "&" use the same as the previous cell.
5041 * {color-idx} is a number from 0 to 255
5042 *
5043 * Screen cell with same width, attributes and color as the previous one:
5044 * |{characters}
5045 *
5046 * To use the color of the previous cell, use "&" instead of {color}-{idx}.
5047 *
5048 * Repeating the previous screen cell:
5049 * @{count}
5050 */
5051 void
5052f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED)
5053{
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02005054 buf_T *buf;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005055 term_T *term;
5056 char_u *fname;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005057 int max_height = 0;
5058 int max_width = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005059 stat_T st;
5060 FILE *fd;
5061 VTermPos pos;
5062 VTermScreen *screen;
5063 VTermScreenCell prev_cell;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005064 VTermState *state;
5065 VTermPos cursor_pos;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005066
5067 if (check_restricted() || check_secure())
5068 return;
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02005069
5070 if (in_vim9script()
5071 && (check_for_buffer_arg(argvars, 0) == FAIL
5072 || check_for_string_arg(argvars, 1) == FAIL
5073 || check_for_opt_dict_arg(argvars, 2) == FAIL))
5074 return;
5075
5076 buf = term_get_buf(argvars, "term_dumpwrite()");
Bram Moolenaard96ff162018-02-18 22:13:29 +01005077 if (buf == NULL)
5078 return;
5079 term = buf->b_term;
Bram Moolenaara5c48c22018-09-09 19:56:07 +02005080 if (term->tl_vterm == NULL)
5081 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00005082 emsg(_(e_job_already_finished));
Bram Moolenaara5c48c22018-09-09 19:56:07 +02005083 return;
5084 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005085
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005086 if (argvars[2].v_type != VAR_UNKNOWN)
5087 {
5088 dict_T *d;
5089
Yegappan Lakshmanan04c4c572022-08-30 19:48:24 +01005090 if (check_for_dict_arg(argvars, 2) == FAIL)
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005091 return;
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005092 d = argvars[2].vval.v_dict;
5093 if (d != NULL)
5094 {
Bram Moolenaard61efa52022-07-23 09:52:04 +01005095 max_height = dict_get_number(d, "rows");
5096 max_width = dict_get_number(d, "columns");
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005097 }
5098 }
5099
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005100 fname = tv_get_string_chk(&argvars[1]);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005101 if (fname == NULL)
5102 return;
5103 if (mch_stat((char *)fname, &st) >= 0)
5104 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00005105 semsg(_(e_file_exists_str), fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005106 return;
5107 }
5108
Bram Moolenaard96ff162018-02-18 22:13:29 +01005109 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL)
5110 {
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00005111 semsg(_(e_cant_create_file_str), *fname == NUL ? (char_u *)_("<empty>") : fname);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005112 return;
5113 }
5114
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005115 clear_cell(&prev_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005116
5117 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005118 state = vterm_obtain_state(term->tl_vterm);
5119 vterm_state_get_cursorpos(state, &cursor_pos);
5120
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005121 for (pos.row = 0; (max_height == 0 || pos.row < max_height)
5122 && pos.row < term->tl_rows; ++pos.row)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005123 {
5124 int repeat = 0;
5125
Bram Moolenaar6bb2cdf2018-02-24 19:53:53 +01005126 for (pos.col = 0; (max_width == 0 || pos.col < max_width)
5127 && pos.col < term->tl_cols; ++pos.col)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005128 {
5129 VTermScreenCell cell;
5130 int same_attr;
5131 int same_chars = TRUE;
5132 int i;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005133 int is_cursor_pos = (pos.col == cursor_pos.col
5134 && pos.row == cursor_pos.row);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005135
5136 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005137 clear_cell(&cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005138
5139 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
5140 {
Bram Moolenaar47015b82018-03-23 22:10:34 +01005141 int c = cell.chars[i];
5142 int pc = prev_cell.chars[i];
Bram Moolenaar9c24cd12020-10-23 15:40:39 +02005143 int should_break = c == NUL || pc == NUL;
Bram Moolenaar47015b82018-03-23 22:10:34 +01005144
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005145 // For the first character NUL is the same as space.
Bram Moolenaar47015b82018-03-23 22:10:34 +01005146 if (i == 0)
5147 {
5148 c = (c == NUL) ? ' ' : c;
5149 pc = (pc == NUL) ? ' ' : pc;
5150 }
Bram Moolenaar98fc8d72018-08-24 21:30:28 +02005151 if (c != pc)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005152 same_chars = FALSE;
Bram Moolenaar9c24cd12020-10-23 15:40:39 +02005153 if (should_break)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005154 break;
5155 }
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005156 same_attr = vtermAttr2hl(&cell.attrs)
5157 == vtermAttr2hl(&prev_cell.attrs)
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005158 && vterm_color_is_equal(&cell.fg, &prev_cell.fg)
5159 && vterm_color_is_equal(&cell.bg, &prev_cell.bg);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005160 if (same_chars && cell.width == prev_cell.width && same_attr
5161 && !is_cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005162 {
5163 ++repeat;
5164 }
5165 else
5166 {
5167 if (repeat > 0)
5168 {
5169 fprintf(fd, "@%d", repeat);
5170 repeat = 0;
5171 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01005172 fputs(is_cursor_pos ? ">" : "|", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005173
5174 if (cell.chars[0] == NUL)
5175 fputs(" ", fd);
5176 else
5177 {
5178 char_u charbuf[10];
5179 int len;
5180
5181 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL
5182 && cell.chars[i] != NUL; ++i)
5183 {
Bram Moolenaarf06b0b62018-03-29 17:22:24 +02005184 len = utf_char2bytes(cell.chars[i], charbuf);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005185 fwrite(charbuf, len, 1, fd);
5186 }
5187 }
5188
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005189 // When only the characters differ we don't write anything, the
5190 // following "|", "@" or NL will indicate using the same
5191 // attributes.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005192 if (cell.width != prev_cell.width || !same_attr)
5193 {
5194 if (cell.width == 2)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005195 fputs("*", fd);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005196 else
5197 fputs("+", fd);
5198
5199 if (same_attr)
5200 {
5201 fputs("&", fd);
5202 }
5203 else
5204 {
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005205 fprintf(fd, "%d", vtermAttr2hl(&cell.attrs));
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005206 if (vterm_color_is_equal(&cell.fg, &prev_cell.fg))
Bram Moolenaard96ff162018-02-18 22:13:29 +01005207 fputs("&", fd);
5208 else
5209 {
5210 fputs("#", fd);
5211 dump_term_color(fd, &cell.fg);
5212 }
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005213 if (vterm_color_is_equal(&cell.bg, &prev_cell.bg))
Bram Moolenaard96ff162018-02-18 22:13:29 +01005214 fputs("&", fd);
5215 else
5216 {
5217 fputs("#", fd);
5218 dump_term_color(fd, &cell.bg);
5219 }
5220 }
5221 }
5222
5223 prev_cell = cell;
5224 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005225
5226 if (cell.width == 2)
5227 ++pos.col;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005228 }
5229 if (repeat > 0)
5230 fprintf(fd, "@%d", repeat);
5231 fputs("\n", fd);
5232 }
5233
5234 fclose(fd);
5235}
5236
5237/*
5238 * Called when a dump is corrupted. Put a breakpoint here when debugging.
5239 */
5240 static void
5241dump_is_corrupt(garray_T *gap)
5242{
5243 ga_concat(gap, (char_u *)"CORRUPT");
5244}
5245
5246 static void
5247append_cell(garray_T *gap, cellattr_T *cell)
5248{
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00005249 if (ga_grow(gap, 1) == FAIL)
5250 return;
5251
5252 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell;
5253 ++gap->ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005254}
5255
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005256 static void
5257clear_cellattr(cellattr_T *cell)
5258{
5259 CLEAR_FIELD(*cell);
5260 cell->fg.type = VTERM_COLOR_DEFAULT_FG;
5261 cell->bg.type = VTERM_COLOR_DEFAULT_BG;
5262}
5263
Bram Moolenaard96ff162018-02-18 22:13:29 +01005264/*
5265 * Read the dump file from "fd" and append lines to the current buffer.
5266 * Return the cell width of the longest line.
5267 */
5268 static int
Bram Moolenaar9271d052018-02-25 21:39:46 +01005269read_dump_file(FILE *fd, VTermPos *cursor_pos)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005270{
5271 int c;
5272 garray_T ga_text;
5273 garray_T ga_cell;
5274 char_u *prev_char = NULL;
5275 int attr = 0;
5276 cellattr_T cell;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005277 cellattr_T empty_cell;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005278 term_T *term = curbuf->b_term;
5279 int max_cells = 0;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005280 int start_row = term->tl_scrollback.ga_len;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005281
5282 ga_init2(&ga_text, 1, 90);
5283 ga_init2(&ga_cell, sizeof(cellattr_T), 90);
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005284 clear_cellattr(&cell);
5285 clear_cellattr(&empty_cell);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005286 cursor_pos->row = -1;
5287 cursor_pos->col = -1;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005288
5289 c = fgetc(fd);
5290 for (;;)
5291 {
5292 if (c == EOF)
5293 break;
Bram Moolenaar0fd6be72018-10-23 21:42:59 +02005294 if (c == '\r')
5295 {
5296 // DOS line endings? Ignore.
5297 c = fgetc(fd);
5298 }
5299 else if (c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01005300 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005301 // End of a line: append it to the buffer.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005302 if (ga_text.ga_data == NULL)
5303 dump_is_corrupt(&ga_text);
5304 if (ga_grow(&term->tl_scrollback, 1) == OK)
5305 {
5306 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
5307 + term->tl_scrollback.ga_len;
5308
5309 if (max_cells < ga_cell.ga_len)
5310 max_cells = ga_cell.ga_len;
5311 line->sb_cols = ga_cell.ga_len;
5312 line->sb_cells = ga_cell.ga_data;
5313 line->sb_fill_attr = term->tl_default_color;
5314 ++term->tl_scrollback.ga_len;
5315 ga_init(&ga_cell);
5316
5317 ga_append(&ga_text, NUL);
5318 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
5319 ga_text.ga_len, FALSE);
5320 }
5321 else
5322 ga_clear(&ga_cell);
5323 ga_text.ga_len = 0;
5324
5325 c = fgetc(fd);
5326 }
Bram Moolenaar9271d052018-02-25 21:39:46 +01005327 else if (c == '|' || c == '>')
Bram Moolenaard96ff162018-02-18 22:13:29 +01005328 {
5329 int prev_len = ga_text.ga_len;
5330
Bram Moolenaar9271d052018-02-25 21:39:46 +01005331 if (c == '>')
5332 {
5333 if (cursor_pos->row != -1)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005334 dump_is_corrupt(&ga_text); // duplicate cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01005335 cursor_pos->row = term->tl_scrollback.ga_len - start_row;
5336 cursor_pos->col = ga_cell.ga_len;
5337 }
5338
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005339 // normal character(s) followed by "+", "*", "|", "@" or NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01005340 c = fgetc(fd);
5341 if (c != EOF)
5342 ga_append(&ga_text, c);
5343 for (;;)
5344 {
5345 c = fgetc(fd);
Bram Moolenaar9271d052018-02-25 21:39:46 +01005346 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@'
Bram Moolenaard96ff162018-02-18 22:13:29 +01005347 || c == EOF || c == '\n')
5348 break;
5349 ga_append(&ga_text, c);
5350 }
5351
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005352 // save the character for repeating it
Bram Moolenaard96ff162018-02-18 22:13:29 +01005353 vim_free(prev_char);
5354 if (ga_text.ga_data != NULL)
5355 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len,
5356 ga_text.ga_len - prev_len);
5357
Bram Moolenaar9271d052018-02-25 21:39:46 +01005358 if (c == '@' || c == '|' || c == '>' || c == '\n')
Bram Moolenaard96ff162018-02-18 22:13:29 +01005359 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005360 // use all attributes from previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01005361 }
5362 else if (c == '+' || c == '*')
5363 {
5364 int is_bg;
5365
5366 cell.width = c == '+' ? 1 : 2;
5367
5368 c = fgetc(fd);
5369 if (c == '&')
5370 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005371 // use same attr as previous cell
Bram Moolenaard96ff162018-02-18 22:13:29 +01005372 c = fgetc(fd);
5373 }
5374 else if (isdigit(c))
5375 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005376 // get the decimal attribute
Bram Moolenaard96ff162018-02-18 22:13:29 +01005377 attr = 0;
5378 while (isdigit(c))
5379 {
5380 attr = attr * 10 + (c - '0');
5381 c = fgetc(fd);
5382 }
5383 hl2vtermAttr(attr, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005384
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005385 // is_bg == 0: fg, is_bg == 1: bg
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005386 for (is_bg = 0; is_bg <= 1; ++is_bg)
5387 {
5388 if (c == '&')
5389 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005390 // use same color as previous cell
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005391 c = fgetc(fd);
5392 }
5393 else if (c == '#')
5394 {
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005395 int red, green, blue, index = 0, type;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005396
5397 c = fgetc(fd);
5398 red = hex2nr(c);
5399 c = fgetc(fd);
5400 red = (red << 4) + hex2nr(c);
5401 c = fgetc(fd);
5402 green = hex2nr(c);
5403 c = fgetc(fd);
5404 green = (green << 4) + hex2nr(c);
5405 c = fgetc(fd);
5406 blue = hex2nr(c);
5407 c = fgetc(fd);
5408 blue = (blue << 4) + hex2nr(c);
5409 c = fgetc(fd);
5410 if (!isdigit(c))
5411 dump_is_corrupt(&ga_text);
5412 while (isdigit(c))
5413 {
5414 index = index * 10 + (c - '0');
5415 c = fgetc(fd);
5416 }
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005417 if (index == 0 || index == 255)
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005418 {
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005419 type = VTERM_COLOR_RGB;
5420 if (index == 0)
5421 {
5422 if (is_bg)
5423 type |= VTERM_COLOR_DEFAULT_BG;
5424 else
5425 type |= VTERM_COLOR_DEFAULT_FG;
5426 }
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005427 }
5428 else
5429 {
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005430 type = VTERM_COLOR_INDEXED;
5431 index -= 1;
5432 }
5433 if (is_bg)
5434 {
5435 cell.bg.type = type;
5436 cell.bg.red = red;
5437 cell.bg.green = green;
5438 cell.bg.blue = blue;
5439 cell.bg.index = index;
5440 }
5441 else
5442 {
5443 cell.fg.type = type;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005444 cell.fg.red = red;
5445 cell.fg.green = green;
5446 cell.fg.blue = blue;
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005447 cell.fg.index = index;
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005448 }
5449 }
5450 else
5451 dump_is_corrupt(&ga_text);
5452 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005453 }
5454 else
5455 dump_is_corrupt(&ga_text);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005456 }
5457 else
5458 dump_is_corrupt(&ga_text);
5459
5460 append_cell(&ga_cell, &cell);
Bram Moolenaar617d7ef2019-01-17 13:04:30 +01005461 if (cell.width == 2)
5462 append_cell(&ga_cell, &empty_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005463 }
5464 else if (c == '@')
5465 {
5466 if (prev_char == NULL)
5467 dump_is_corrupt(&ga_text);
5468 else
5469 {
5470 int count = 0;
5471
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005472 // repeat previous character, get the count
Bram Moolenaard96ff162018-02-18 22:13:29 +01005473 for (;;)
5474 {
5475 c = fgetc(fd);
5476 if (!isdigit(c))
5477 break;
5478 count = count * 10 + (c - '0');
5479 }
5480
5481 while (count-- > 0)
5482 {
5483 ga_concat(&ga_text, prev_char);
5484 append_cell(&ga_cell, &cell);
5485 }
5486 }
5487 }
5488 else
5489 {
5490 dump_is_corrupt(&ga_text);
5491 c = fgetc(fd);
5492 }
5493 }
5494
5495 if (ga_text.ga_len > 0)
5496 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005497 // trailing characters after last NL
Bram Moolenaard96ff162018-02-18 22:13:29 +01005498 dump_is_corrupt(&ga_text);
5499 ga_append(&ga_text, NUL);
5500 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data,
5501 ga_text.ga_len, FALSE);
5502 }
5503
5504 ga_clear(&ga_text);
Bram Moolenaar86173482019-10-01 17:02:16 +02005505 ga_clear(&ga_cell);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005506 vim_free(prev_char);
5507
5508 return max_cells;
5509}
5510
5511/*
Bram Moolenaar4a696342018-04-05 18:45:26 +02005512 * Return an allocated string with at least "text_width" "=" characters and
5513 * "fname" inserted in the middle.
5514 */
5515 static char_u *
5516get_separator(int text_width, char_u *fname)
5517{
5518 int width = MAX(text_width, curwin->w_width);
5519 char_u *textline;
5520 int fname_size;
5521 char_u *p = fname;
5522 int i;
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02005523 size_t off;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005524
Bram Moolenaard6b4f2d2018-04-10 18:26:27 +02005525 textline = alloc(width + (int)STRLEN(fname) + 1);
Bram Moolenaar4a696342018-04-05 18:45:26 +02005526 if (textline == NULL)
5527 return NULL;
5528
5529 fname_size = vim_strsize(fname);
5530 if (fname_size < width - 8)
5531 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005532 // enough room, don't use the full window width
Bram Moolenaar4a696342018-04-05 18:45:26 +02005533 width = MAX(text_width, fname_size + 8);
5534 }
5535 else if (fname_size > width - 8)
5536 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005537 // full name doesn't fit, use only the tail
Bram Moolenaar4a696342018-04-05 18:45:26 +02005538 p = gettail(fname);
5539 fname_size = vim_strsize(p);
5540 }
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005541 // skip characters until the name fits
Bram Moolenaar4a696342018-04-05 18:45:26 +02005542 while (fname_size > width - 8)
5543 {
5544 p += (*mb_ptr2len)(p);
5545 fname_size = vim_strsize(p);
5546 }
5547
5548 for (i = 0; i < (width - fname_size) / 2 - 1; ++i)
5549 textline[i] = '=';
5550 textline[i++] = ' ';
5551
5552 STRCPY(textline + i, p);
5553 off = STRLEN(textline);
5554 textline[off] = ' ';
5555 for (i = 1; i < (width - fname_size) / 2; ++i)
5556 textline[off + i] = '=';
5557 textline[off + i] = NUL;
5558
5559 return textline;
5560}
5561
5562/*
Bram Moolenaard96ff162018-02-18 22:13:29 +01005563 * Common for "term_dumpdiff()" and "term_dumpload()".
5564 */
5565 static void
5566term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff)
5567{
5568 jobopt_T opt;
Bram Moolenaar87abab92019-06-03 21:14:59 +02005569 buf_T *buf = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005570 char_u buf1[NUMBUFLEN];
5571 char_u buf2[NUMBUFLEN];
5572 char_u *fname1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005573 char_u *fname2 = NULL;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005574 char_u *fname_tofree = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005575 FILE *fd1;
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005576 FILE *fd2 = NULL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005577 char_u *textline = NULL;
5578
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005579 // First open the files. If this fails bail out.
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005580 fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005581 if (do_diff)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01005582 fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005583 if (fname1 == NULL || (do_diff && fname2 == NULL))
5584 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00005585 emsg(_(e_invalid_argument));
Bram Moolenaard96ff162018-02-18 22:13:29 +01005586 return;
5587 }
5588 fd1 = mch_fopen((char *)fname1, READBIN);
5589 if (fd1 == NULL)
5590 {
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00005591 semsg(_(e_cant_read_file_str), fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005592 return;
5593 }
5594 if (do_diff)
5595 {
5596 fd2 = mch_fopen((char *)fname2, READBIN);
5597 if (fd2 == NULL)
5598 {
5599 fclose(fd1);
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00005600 semsg(_(e_cant_read_file_str), fname2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005601 return;
5602 }
5603 }
5604
5605 init_job_options(&opt);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005606 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN
5607 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0,
5608 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS
5609 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL)
5610 goto theend;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005611
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005612 if (opt.jo_term_name == NULL)
5613 {
Bram Moolenaarb571c632018-03-21 22:27:59 +01005614 size_t len = STRLEN(fname1) + 12;
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005615
Bram Moolenaar51e14382019-05-25 20:21:28 +02005616 fname_tofree = alloc(len);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005617 if (fname_tofree != NULL)
5618 {
5619 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1);
5620 opt.jo_term_name = fname_tofree;
5621 }
5622 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005623
Bram Moolenaar87abab92019-06-03 21:14:59 +02005624 if (opt.jo_bufnr_buf != NULL)
5625 {
5626 win_T *wp = buf_jump_open_win(opt.jo_bufnr_buf);
5627
5628 // With "bufnr" argument: enter the window with this buffer and make it
5629 // empty.
5630 if (wp == NULL)
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00005631 semsg(_(e_invalid_argument_str), "bufnr");
Bram Moolenaar87abab92019-06-03 21:14:59 +02005632 else
5633 {
5634 buf = curbuf;
5635 while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
Bram Moolenaarca70c072020-05-30 20:30:46 +02005636 ml_delete((linenr_T)1);
Bram Moolenaar86173482019-10-01 17:02:16 +02005637 free_scrollback(curbuf->b_term);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01005638 redraw_later(UPD_NOT_VALID);
Bram Moolenaar87abab92019-06-03 21:14:59 +02005639 }
5640 }
5641 else
5642 // Create a new terminal window.
5643 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB);
5644
Bram Moolenaard96ff162018-02-18 22:13:29 +01005645 if (buf != NULL && buf->b_term != NULL)
5646 {
5647 int i;
5648 linenr_T bot_lnum;
5649 linenr_T lnum;
5650 term_T *term = buf->b_term;
5651 int width;
5652 int width2;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005653 VTermPos cursor_pos1;
5654 VTermPos cursor_pos2;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005655
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005656 init_default_colors(term);
Bram Moolenaar52acb112018-03-18 19:20:22 +01005657
Bram Moolenaard96ff162018-02-18 22:13:29 +01005658 rettv->vval.v_number = buf->b_fnum;
5659
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005660 // read the files, fill the buffer with the diff
Bram Moolenaar9271d052018-02-25 21:39:46 +01005661 width = read_dump_file(fd1, &cursor_pos1);
5662
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005663 // position the cursor
Bram Moolenaar9271d052018-02-25 21:39:46 +01005664 if (cursor_pos1.row >= 0)
5665 {
5666 curwin->w_cursor.lnum = cursor_pos1.row + 1;
5667 coladvance(cursor_pos1.col);
5668 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005669
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005670 // Delete the empty line that was in the empty buffer.
Bram Moolenaarca70c072020-05-30 20:30:46 +02005671 ml_delete(1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005672
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005673 // For term_dumpload() we are done here.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005674 if (!do_diff)
5675 goto theend;
5676
5677 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count;
5678
Bram Moolenaar4a696342018-04-05 18:45:26 +02005679 textline = get_separator(width, fname1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005680 if (textline == NULL)
5681 goto theend;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005682 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
5683 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
5684 vim_free(textline);
5685
5686 textline = get_separator(width, fname2);
5687 if (textline == NULL)
5688 goto theend;
5689 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK)
5690 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005691 textline[width] = NUL;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005692
5693 bot_lnum = curbuf->b_ml.ml_line_count;
Bram Moolenaar9271d052018-02-25 21:39:46 +01005694 width2 = read_dump_file(fd2, &cursor_pos2);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005695 if (width2 > width)
5696 {
5697 vim_free(textline);
5698 textline = alloc(width2 + 1);
5699 if (textline == NULL)
5700 goto theend;
5701 width = width2;
5702 textline[width] = NUL;
5703 }
5704 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum;
5705
5706 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum)
5707 {
5708 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count)
5709 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005710 // bottom part has fewer rows, fill with "-"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005711 for (i = 0; i < width; ++i)
5712 textline[i] = '-';
5713 }
5714 else
5715 {
5716 char_u *line1;
5717 char_u *line2;
5718 char_u *p1;
5719 char_u *p2;
5720 int col;
5721 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5722 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells;
5723 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1)
5724 ->sb_cells;
5725
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005726 // Make a copy, getting the second line will invalidate it.
Bram Moolenaard96ff162018-02-18 22:13:29 +01005727 line1 = vim_strsave(ml_get(lnum));
5728 if (line1 == NULL)
5729 break;
5730 p1 = line1;
5731
5732 line2 = ml_get(lnum + bot_lnum);
5733 p2 = line2;
5734 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col)
5735 {
5736 int len1 = utfc_ptr2len(p1);
5737 int len2 = utfc_ptr2len(p2);
5738
5739 textline[col] = ' ';
5740 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005741 // text differs
Bram Moolenaard96ff162018-02-18 22:13:29 +01005742 textline[col] = 'X';
Bram Moolenaar9271d052018-02-25 21:39:46 +01005743 else if (lnum == cursor_pos1.row + 1
5744 && col == cursor_pos1.col
5745 && (cursor_pos1.row != cursor_pos2.row
5746 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005747 // cursor in first but not in second
Bram Moolenaar9271d052018-02-25 21:39:46 +01005748 textline[col] = '>';
5749 else if (lnum == cursor_pos2.row + 1
5750 && col == cursor_pos2.col
5751 && (cursor_pos1.row != cursor_pos2.row
5752 || cursor_pos1.col != cursor_pos2.col))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005753 // cursor in second but not in first
Bram Moolenaar9271d052018-02-25 21:39:46 +01005754 textline[col] = '<';
Bram Moolenaard96ff162018-02-18 22:13:29 +01005755 else if (cellattr1 != NULL && cellattr2 != NULL)
5756 {
5757 if ((cellattr1 + col)->width
5758 != (cellattr2 + col)->width)
5759 textline[col] = 'w';
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005760 else if (!vterm_color_is_equal(&(cellattr1 + col)->fg,
Bram Moolenaard96ff162018-02-18 22:13:29 +01005761 &(cellattr2 + col)->fg))
5762 textline[col] = 'f';
Bram Moolenaare5886cc2020-05-21 20:10:04 +02005763 else if (!vterm_color_is_equal(&(cellattr1 + col)->bg,
Bram Moolenaard96ff162018-02-18 22:13:29 +01005764 &(cellattr2 + col)->bg))
5765 textline[col] = 'b';
Bram Moolenaar87fd0922021-11-20 13:47:45 +00005766 else if (vtermAttr2hl(&(cellattr1 + col)->attrs)
5767 != vtermAttr2hl(&((cellattr2 + col)->attrs)))
Bram Moolenaard96ff162018-02-18 22:13:29 +01005768 textline[col] = 'a';
5769 }
5770 p1 += len1;
5771 p2 += len2;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005772 // TODO: handle different width
Bram Moolenaard96ff162018-02-18 22:13:29 +01005773 }
Bram Moolenaard96ff162018-02-18 22:13:29 +01005774
5775 while (col < width)
5776 {
5777 if (*p1 == NUL && *p2 == NUL)
5778 textline[col] = '?';
5779 else if (*p1 == NUL)
5780 {
5781 textline[col] = '+';
5782 p2 += utfc_ptr2len(p2);
5783 }
5784 else
5785 {
5786 textline[col] = '-';
5787 p1 += utfc_ptr2len(p1);
5788 }
5789 ++col;
5790 }
Bram Moolenaar81aa0f52019-02-14 23:23:19 +01005791
5792 vim_free(line1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005793 }
5794 if (add_empty_scrollback(term, &term->tl_default_color,
5795 term->tl_top_diff_rows) == OK)
5796 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5797 ++bot_lnum;
5798 }
5799
5800 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count)
5801 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005802 // bottom part has more rows, fill with "+"
Bram Moolenaard96ff162018-02-18 22:13:29 +01005803 for (i = 0; i < width; ++i)
5804 textline[i] = '+';
5805 if (add_empty_scrollback(term, &term->tl_default_color,
5806 term->tl_top_diff_rows) == OK)
5807 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE);
5808 ++lnum;
5809 ++bot_lnum;
5810 }
5811
5812 term->tl_cols = width;
Bram Moolenaar4a696342018-04-05 18:45:26 +02005813
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005814 // looks better without wrapping
Bram Moolenaar4a696342018-04-05 18:45:26 +02005815 curwin->w_p_wrap = 0;
Bram Moolenaard96ff162018-02-18 22:13:29 +01005816 }
5817
5818theend:
5819 vim_free(textline);
Bram Moolenaar5a3a49e2018-03-20 18:35:53 +01005820 vim_free(fname_tofree);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005821 fclose(fd1);
Bram Moolenaar9c8816b2018-02-19 21:50:42 +01005822 if (fd2 != NULL)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005823 fclose(fd2);
5824}
5825
5826/*
5827 * If the current buffer shows the output of term_dumpdiff(), swap the top and
5828 * bottom files.
5829 * Return FAIL when this is not possible.
5830 */
5831 int
Yegappan Lakshmanana23a11b2023-02-21 14:27:41 +00005832term_swap_diff(void)
Bram Moolenaard96ff162018-02-18 22:13:29 +01005833{
5834 term_T *term = curbuf->b_term;
5835 linenr_T line_count;
5836 linenr_T top_rows;
5837 linenr_T bot_rows;
5838 linenr_T bot_start;
5839 linenr_T lnum;
5840 char_u *p;
5841 sb_line_T *sb_line;
5842
5843 if (term == NULL
5844 || !term_is_finished(curbuf)
5845 || term->tl_top_diff_rows == 0
5846 || term->tl_scrollback.ga_len == 0)
5847 return FAIL;
5848
5849 line_count = curbuf->b_ml.ml_line_count;
5850 top_rows = term->tl_top_diff_rows;
5851 bot_rows = term->tl_bot_diff_rows;
5852 bot_start = line_count - bot_rows;
5853 sb_line = (sb_line_T *)term->tl_scrollback.ga_data;
5854
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005855 // move lines from top to above the bottom part
Bram Moolenaard96ff162018-02-18 22:13:29 +01005856 for (lnum = 1; lnum <= top_rows; ++lnum)
5857 {
5858 p = vim_strsave(ml_get(1));
5859 if (p == NULL)
5860 return OK;
5861 ml_append(bot_start, p, 0, FALSE);
Bram Moolenaarca70c072020-05-30 20:30:46 +02005862 ml_delete(1);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005863 vim_free(p);
5864 }
5865
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005866 // move lines from bottom to the top
Bram Moolenaard96ff162018-02-18 22:13:29 +01005867 for (lnum = 1; lnum <= bot_rows; ++lnum)
5868 {
5869 p = vim_strsave(ml_get(bot_start + lnum));
5870 if (p == NULL)
5871 return OK;
Bram Moolenaarca70c072020-05-30 20:30:46 +02005872 ml_delete(bot_start + lnum);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005873 ml_append(lnum - 1, p, 0, FALSE);
5874 vim_free(p);
5875 }
5876
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005877 // move top title to bottom
5878 p = vim_strsave(ml_get(bot_rows + 1));
5879 if (p == NULL)
5880 return OK;
5881 ml_append(line_count - top_rows - 1, p, 0, FALSE);
Bram Moolenaarca70c072020-05-30 20:30:46 +02005882 ml_delete(bot_rows + 1);
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005883 vim_free(p);
5884
5885 // move bottom title to top
5886 p = vim_strsave(ml_get(line_count - top_rows));
5887 if (p == NULL)
5888 return OK;
Bram Moolenaarca70c072020-05-30 20:30:46 +02005889 ml_delete(line_count - top_rows);
Bram Moolenaarc3ef8962019-02-15 00:16:13 +01005890 ml_append(bot_rows, p, 0, FALSE);
5891 vim_free(p);
5892
Bram Moolenaard96ff162018-02-18 22:13:29 +01005893 if (top_rows == bot_rows)
5894 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005895 // rows counts are equal, can swap cell properties
Bram Moolenaard96ff162018-02-18 22:13:29 +01005896 for (lnum = 0; lnum < top_rows; ++lnum)
5897 {
5898 sb_line_T temp;
5899
5900 temp = *(sb_line + lnum);
5901 *(sb_line + lnum) = *(sb_line + bot_start + lnum);
5902 *(sb_line + bot_start + lnum) = temp;
5903 }
5904 }
5905 else
5906 {
5907 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len;
Bram Moolenaarc799fe22019-05-28 23:08:19 +02005908 sb_line_T *temp = alloc(size);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005909
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01005910 // need to copy cell properties into temp memory
Bram Moolenaard96ff162018-02-18 22:13:29 +01005911 if (temp != NULL)
5912 {
5913 mch_memmove(temp, term->tl_scrollback.ga_data, size);
5914 mch_memmove(term->tl_scrollback.ga_data,
5915 temp + bot_start,
5916 sizeof(sb_line_T) * bot_rows);
5917 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows,
5918 temp + top_rows,
5919 sizeof(sb_line_T) * (line_count - top_rows - bot_rows));
5920 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data
5921 + line_count - top_rows,
5922 temp,
5923 sizeof(sb_line_T) * top_rows);
5924 vim_free(temp);
5925 }
5926 }
5927
5928 term->tl_top_diff_rows = bot_rows;
5929 term->tl_bot_diff_rows = top_rows;
5930
Bram Moolenaara4d158b2022-08-14 14:17:45 +01005931 update_screen(UPD_NOT_VALID);
Bram Moolenaard96ff162018-02-18 22:13:29 +01005932 return OK;
5933}
5934
5935/*
5936 * "term_dumpdiff(filename, filename, options)" function
5937 */
5938 void
5939f_term_dumpdiff(typval_T *argvars, typval_T *rettv)
5940{
Yegappan Lakshmanan0ad871d2021-07-23 20:37:56 +02005941 if (in_vim9script()
5942 && (check_for_string_arg(argvars, 0) == FAIL
5943 || check_for_string_arg(argvars, 1) == FAIL
5944 || check_for_opt_dict_arg(argvars, 2) == FAIL))
5945 return;
5946
Bram Moolenaard96ff162018-02-18 22:13:29 +01005947 term_load_dump(argvars, rettv, TRUE);
5948}
5949
5950/*
5951 * "term_dumpload(filename, options)" function
5952 */
5953 void
5954f_term_dumpload(typval_T *argvars, typval_T *rettv)
5955{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005956 if (in_vim9script()
5957 && (check_for_string_arg(argvars, 0) == FAIL
Yegappan Lakshmananfc3b7752021-09-08 14:57:42 +02005958 || check_for_opt_dict_arg(argvars, 1) == FAIL))
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005959 return;
5960
Bram Moolenaard96ff162018-02-18 22:13:29 +01005961 term_load_dump(argvars, rettv, FALSE);
5962}
5963
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005964/*
5965 * "term_getaltscreen(buf)" function
5966 */
5967 void
5968f_term_getaltscreen(typval_T *argvars, typval_T *rettv)
5969{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005970 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005971
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02005972 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
5973 return;
5974
5975 buf = term_get_buf(argvars, "term_getaltscreen()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02005976 if (buf == NULL)
5977 return;
5978 rettv->vval.v_number = buf->b_term->tl_using_altscreen;
5979}
5980
5981/*
5982 * "term_getattr(attr, name)" function
5983 */
5984 void
5985f_term_getattr(typval_T *argvars, typval_T *rettv)
5986{
5987 int attr;
5988 size_t i;
5989 char_u *name;
5990
5991 static struct {
5992 char *name;
5993 int attr;
5994 } attrs[] = {
5995 {"bold", HL_BOLD},
5996 {"italic", HL_ITALIC},
5997 {"underline", HL_UNDERLINE},
5998 {"strike", HL_STRIKETHROUGH},
5999 {"reverse", HL_INVERSE},
6000 };
6001
Yegappan Lakshmanan1a71d312021-07-15 12:49:58 +02006002 if (in_vim9script()
6003 && (check_for_number_arg(argvars, 0) == FAIL
6004 || check_for_string_arg(argvars, 1) == FAIL))
6005 return;
6006
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006007 attr = tv_get_number(&argvars[0]);
6008 name = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006009 if (name == NULL)
6010 return;
6011
Bram Moolenaar7ee80f72019-09-08 20:55:06 +02006012 if (attr > HL_ALL)
6013 attr = syn_attr2attr(attr);
K.Takataeeec2542021-06-02 13:28:16 +02006014 for (i = 0; i < ARRAY_LENGTH(attrs); ++i)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006015 if (STRCMP(name, attrs[i].name) == 0)
6016 {
6017 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
6018 break;
6019 }
6020}
6021
6022/*
6023 * "term_getcursor(buf)" function
6024 */
6025 void
6026f_term_getcursor(typval_T *argvars, typval_T *rettv)
6027{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006028 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006029 term_T *term;
6030 list_T *l;
6031 dict_T *d;
6032
6033 if (rettv_list_alloc(rettv) == FAIL)
6034 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006035
6036 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6037 return;
6038
6039 buf = term_get_buf(argvars, "term_getcursor()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006040 if (buf == NULL)
6041 return;
6042 term = buf->b_term;
6043
6044 l = rettv->vval.v_list;
6045 list_append_number(l, term->tl_cursor_pos.row + 1);
6046 list_append_number(l, term->tl_cursor_pos.col + 1);
6047
6048 d = dict_alloc();
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00006049 if (d == NULL)
6050 return;
6051
6052 dict_add_number(d, "visible", term->tl_cursor_visible);
6053 dict_add_number(d, "blink", blink_state_is_inverted()
6054 ? !term->tl_cursor_blink : term->tl_cursor_blink);
6055 dict_add_number(d, "shape", term->tl_cursor_shape);
6056 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color));
6057 list_append_dict(l, d);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006058}
6059
6060/*
6061 * "term_getjob(buf)" function
6062 */
6063 void
6064f_term_getjob(typval_T *argvars, typval_T *rettv)
6065{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006066 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006067
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006068 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6069 return;
6070
6071 buf = term_get_buf(argvars, "term_getjob()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006072 if (buf == NULL)
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01006073 {
6074 rettv->v_type = VAR_SPECIAL;
6075 rettv->vval.v_number = VVAL_NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006076 return;
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01006077 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006078
Bram Moolenaar528ccfb2018-12-21 20:55:22 +01006079 rettv->v_type = VAR_JOB;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006080 rettv->vval.v_job = buf->b_term->tl_job;
6081 if (rettv->vval.v_job != NULL)
6082 ++rettv->vval.v_job->jv_refcount;
6083}
6084
6085 static int
6086get_row_number(typval_T *tv, term_T *term)
6087{
6088 if (tv->v_type == VAR_STRING
6089 && tv->vval.v_string != NULL
6090 && STRCMP(tv->vval.v_string, ".") == 0)
6091 return term->tl_cursor_pos.row;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006092 return (int)tv_get_number(tv) - 1;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006093}
6094
6095/*
6096 * "term_getline(buf, row)" function
6097 */
6098 void
6099f_term_getline(typval_T *argvars, typval_T *rettv)
6100{
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006101 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006102 term_T *term;
6103 int row;
6104
6105 rettv->v_type = VAR_STRING;
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006106
6107 if (in_vim9script()
6108 && (check_for_buffer_arg(argvars, 0) == FAIL
6109 || check_for_lnum_arg(argvars, 1) == FAIL))
6110 return;
6111
6112 buf = term_get_buf(argvars, "term_getline()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006113 if (buf == NULL)
6114 return;
6115 term = buf->b_term;
6116 row = get_row_number(&argvars[1], term);
6117
6118 if (term->tl_vterm == NULL)
6119 {
6120 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
6121
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006122 // vterm is finished, get the text from the buffer
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006123 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
6124 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
6125 }
6126 else
6127 {
6128 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
6129 VTermRect rect;
6130 int len;
6131 char_u *p;
6132
6133 if (row < 0 || row >= term->tl_rows)
6134 return;
6135 len = term->tl_cols * MB_MAXBYTES + 1;
6136 p = alloc(len);
6137 if (p == NULL)
6138 return;
6139 rettv->vval.v_string = p;
6140
6141 rect.start_col = 0;
6142 rect.end_col = term->tl_cols;
6143 rect.start_row = row;
6144 rect.end_row = row + 1;
6145 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
6146 }
6147}
6148
6149/*
6150 * "term_getscrolled(buf)" function
6151 */
6152 void
6153f_term_getscrolled(typval_T *argvars, typval_T *rettv)
6154{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006155 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006156
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006157 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6158 return;
6159
6160 buf = term_get_buf(argvars, "term_getscrolled()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006161 if (buf == NULL)
6162 return;
6163 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled;
6164}
6165
6166/*
6167 * "term_getsize(buf)" function
6168 */
6169 void
6170f_term_getsize(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 list_T *l;
6174
6175 if (rettv_list_alloc(rettv) == FAIL)
6176 return;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006177
6178 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6179 return;
6180
6181 buf = term_get_buf(argvars, "term_getsize()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006182 if (buf == NULL)
6183 return;
6184
6185 l = rettv->vval.v_list;
6186 list_append_number(l, buf->b_term->tl_rows);
6187 list_append_number(l, buf->b_term->tl_cols);
6188}
6189
6190/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02006191 * "term_setsize(buf, rows, cols)" function
6192 */
6193 void
6194f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
6195{
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006196 buf_T *buf;
Bram Moolenaara42d3632018-04-14 17:05:38 +02006197 term_T *term;
6198 varnumber_T rows, cols;
6199
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006200 if (in_vim9script()
6201 && (check_for_buffer_arg(argvars, 0) == FAIL
6202 || check_for_number_arg(argvars, 1) == FAIL
6203 || check_for_number_arg(argvars, 2) == FAIL))
6204 return;
6205
6206 buf = term_get_buf(argvars, "term_setsize()");
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02006207 if (buf == NULL)
6208 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00006209 emsg(_(e_not_terminal_buffer));
Bram Moolenaar6e72cd02018-04-14 21:31:35 +02006210 return;
6211 }
6212 if (buf->b_term->tl_vterm == NULL)
Bram Moolenaara42d3632018-04-14 17:05:38 +02006213 return;
6214 term = buf->b_term;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006215 rows = tv_get_number(&argvars[1]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02006216 rows = rows <= 0 ? term->tl_rows : rows;
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006217 cols = tv_get_number(&argvars[2]);
Bram Moolenaara42d3632018-04-14 17:05:38 +02006218 cols = cols <= 0 ? term->tl_cols : cols;
6219 vterm_set_size(term->tl_vterm, rows, cols);
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006220 // handle_resize() will resize the windows
Bram Moolenaara42d3632018-04-14 17:05:38 +02006221
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006222 // Get and remember the size we ended up with. Update the pty.
Bram Moolenaara42d3632018-04-14 17:05:38 +02006223 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
6224 term_report_winsize(term, term->tl_rows, term->tl_cols);
6225}
6226
6227/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006228 * "term_getstatus(buf)" function
6229 */
6230 void
6231f_term_getstatus(typval_T *argvars, typval_T *rettv)
6232{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006233 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006234 term_T *term;
6235 char_u val[100];
6236
6237 rettv->v_type = VAR_STRING;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006238
6239 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6240 return;
6241
6242 buf = term_get_buf(argvars, "term_getstatus()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006243 if (buf == NULL)
6244 return;
6245 term = buf->b_term;
6246
6247 if (term_job_running(term))
6248 STRCPY(val, "running");
6249 else
6250 STRCPY(val, "finished");
6251 if (term->tl_normal_mode)
6252 STRCAT(val, ",normal");
6253 rettv->vval.v_string = vim_strsave(val);
6254}
6255
6256/*
6257 * "term_gettitle(buf)" function
6258 */
6259 void
6260f_term_gettitle(typval_T *argvars, typval_T *rettv)
6261{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006262 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006263
6264 rettv->v_type = VAR_STRING;
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006265
6266 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6267 return;
6268
6269 buf = term_get_buf(argvars, "term_gettitle()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006270 if (buf == NULL)
6271 return;
6272
6273 if (buf->b_term->tl_title != NULL)
6274 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
6275}
6276
6277/*
6278 * "term_gettty(buf)" function
6279 */
6280 void
6281f_term_gettty(typval_T *argvars, typval_T *rettv)
6282{
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006283 buf_T *buf;
Bram Moolenaar9b50f362018-05-07 20:10:17 +02006284 char_u *p = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006285 int num = 0;
6286
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006287 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006288 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006289 || check_for_opt_bool_arg(argvars, 1) == FAIL))
6290 return;
6291
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006292 rettv->v_type = VAR_STRING;
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006293 buf = term_get_buf(argvars, "term_gettty()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006294 if (buf == NULL)
6295 return;
6296 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaarad304702020-09-06 18:22:53 +02006297 num = tv_get_bool(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006298
6299 switch (num)
6300 {
6301 case 0:
6302 if (buf->b_term->tl_job != NULL)
6303 p = buf->b_term->tl_job->jv_tty_out;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006304 break;
6305 case 1:
6306 if (buf->b_term->tl_job != NULL)
6307 p = buf->b_term->tl_job->jv_tty_in;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006308 break;
6309 default:
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00006310 semsg(_(e_invalid_argument_str), tv_get_string(&argvars[1]));
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006311 return;
6312 }
6313 if (p != NULL)
6314 rettv->vval.v_string = vim_strsave(p);
6315}
6316
6317/*
6318 * "term_list()" function
6319 */
6320 void
6321f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
6322{
6323 term_T *tp;
6324 list_T *l;
6325
6326 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
6327 return;
6328
6329 l = rettv->vval.v_list;
Bram Moolenaaraeea7212020-04-02 18:50:46 +02006330 FOR_ALL_TERMS(tp)
Bram Moolenaarad431992021-05-03 20:40:38 +02006331 if (tp->tl_buffer != NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006332 if (list_append_number(l,
6333 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
6334 return;
6335}
6336
6337/*
6338 * "term_scrape(buf, row)" function
6339 */
6340 void
6341f_term_scrape(typval_T *argvars, typval_T *rettv)
6342{
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006343 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006344 VTermScreen *screen = NULL;
6345 VTermPos pos;
6346 list_T *l;
6347 term_T *term;
6348 char_u *p;
6349 sb_line_T *line;
6350
6351 if (rettv_list_alloc(rettv) == FAIL)
6352 return;
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006353
6354 if (in_vim9script()
6355 && (check_for_buffer_arg(argvars, 0) == FAIL
6356 || check_for_lnum_arg(argvars, 1) == FAIL))
6357 return;
6358
6359 buf = term_get_buf(argvars, "term_scrape()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006360 if (buf == NULL)
6361 return;
6362 term = buf->b_term;
6363
6364 l = rettv->vval.v_list;
6365 pos.row = get_row_number(&argvars[1], term);
6366
6367 if (term->tl_vterm != NULL)
6368 {
6369 screen = vterm_obtain_screen(term->tl_vterm);
Bram Moolenaar06d62602018-12-27 21:27:03 +01006370 if (screen == NULL) // can't really happen
6371 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006372 p = NULL;
6373 line = NULL;
6374 }
6375 else
6376 {
6377 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
6378
6379 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
6380 return;
6381 p = ml_get_buf(buf, lnum + 1, FALSE);
6382 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
6383 }
6384
6385 for (pos.col = 0; pos.col < term->tl_cols; )
6386 {
6387 dict_T *dcell;
6388 int width;
6389 VTermScreenCellAttrs attrs;
6390 VTermColor fg, bg;
6391 char_u rgb[8];
6392 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
6393 int off = 0;
6394 int i;
6395
6396 if (screen == NULL)
6397 {
6398 cellattr_T *cellattr;
6399 int len;
6400
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006401 // vterm has finished, get the cell from scrollback
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006402 if (pos.col >= line->sb_cols)
6403 break;
6404 cellattr = line->sb_cells + pos.col;
6405 width = cellattr->width;
6406 attrs = cellattr->attrs;
6407 fg = cellattr->fg;
6408 bg = cellattr->bg;
Bram Moolenaar1614a142019-10-06 22:00:13 +02006409 len = mb_ptr2len(p);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006410 mch_memmove(mbs, p, len);
6411 mbs[len] = NUL;
6412 p += len;
6413 }
6414 else
6415 {
6416 VTermScreenCell cell;
Bram Moolenaare5886cc2020-05-21 20:10:04 +02006417
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006418 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
6419 break;
6420 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
6421 {
6422 if (cell.chars[i] == 0)
6423 break;
6424 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
6425 }
6426 mbs[off] = NUL;
6427 width = cell.width;
6428 attrs = cell.attrs;
6429 fg = cell.fg;
6430 bg = cell.bg;
6431 }
6432 dcell = dict_alloc();
Bram Moolenaar4b7e7be2018-02-11 14:53:30 +01006433 if (dcell == NULL)
6434 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006435 list_append_dict(l, dcell);
6436
Bram Moolenaare0be1672018-07-08 16:50:37 +02006437 dict_add_string(dcell, "chars", mbs);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006438
6439 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
6440 fg.red, fg.green, fg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02006441 dict_add_string(dcell, "fg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006442 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
6443 bg.red, bg.green, bg.blue);
Bram Moolenaare0be1672018-07-08 16:50:37 +02006444 dict_add_string(dcell, "bg", rgb);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006445
Bram Moolenaar87fd0922021-11-20 13:47:45 +00006446 dict_add_number(dcell, "attr",
6447 cell2attr(term, NULL, &attrs, &fg, &bg));
Bram Moolenaare0be1672018-07-08 16:50:37 +02006448 dict_add_number(dcell, "width", width);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006449
6450 ++pos.col;
6451 if (width == 2)
6452 ++pos.col;
6453 }
6454}
6455
6456/*
6457 * "term_sendkeys(buf, keys)" function
6458 */
6459 void
Bram Moolenaar3a05ce62020-03-11 19:30:01 +01006460f_term_sendkeys(typval_T *argvars, typval_T *rettv UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006461{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006462 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006463 char_u *msg;
6464 term_T *term;
6465
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006466 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006467 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006468 || check_for_string_arg(argvars, 1) == FAIL))
6469 return;
6470
6471 buf = term_get_buf(argvars, "term_sendkeys()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006472 if (buf == NULL)
6473 return;
6474
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006475 msg = tv_get_string_chk(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006476 if (msg == NULL)
6477 return;
6478 term = buf->b_term;
6479 if (term->tl_vterm == NULL)
6480 return;
6481
6482 while (*msg != NUL)
6483 {
Bram Moolenaar6b810d92018-06-04 17:28:44 +02006484 int c;
6485
6486 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL)
6487 {
6488 c = TO_SPECIAL(msg[1], msg[2]);
6489 msg += 3;
6490 }
6491 else
6492 {
6493 c = PTR2CHAR(msg);
6494 msg += MB_CPTR2LEN(msg);
6495 }
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01006496 send_keys_to_term(term, c, 0, FALSE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006497 }
6498}
6499
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006500#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO)
6501/*
6502 * "term_getansicolors(buf)" function
6503 */
6504 void
6505f_term_getansicolors(typval_T *argvars, typval_T *rettv)
6506{
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006507 buf_T *buf;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006508 term_T *term;
6509 VTermState *state;
6510 VTermColor color;
6511 char_u hexbuf[10];
6512 int index;
6513 list_T *list;
6514
6515 if (rettv_list_alloc(rettv) == FAIL)
6516 return;
6517
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006518 if (in_vim9script() && check_for_buffer_arg(argvars, 0) == FAIL)
6519 return;
6520
6521 buf = term_get_buf(argvars, "term_getansicolors()");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006522 if (buf == NULL)
6523 return;
6524 term = buf->b_term;
6525 if (term->tl_vterm == NULL)
6526 return;
6527
6528 list = rettv->vval.v_list;
6529 state = vterm_obtain_state(term->tl_vterm);
6530 for (index = 0; index < 16; index++)
6531 {
6532 vterm_state_get_palette_color(state, index, &color);
6533 sprintf((char *)hexbuf, "#%02x%02x%02x",
6534 color.red, color.green, color.blue);
6535 if (list_append_string(list, hexbuf, 7) == FAIL)
6536 return;
6537 }
6538}
6539
6540/*
6541 * "term_setansicolors(buf, list)" function
6542 */
6543 void
6544f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED)
6545{
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006546 buf_T *buf;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006547 term_T *term;
LemonBoyb2b3acb2022-05-20 10:10:34 +01006548 listitem_T *li;
6549 int n = 0;
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006550
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006551 if (in_vim9script()
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02006552 && (check_for_buffer_arg(argvars, 0) == FAIL
6553 || check_for_list_arg(argvars, 1) == FAIL))
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006554 return;
6555
6556 buf = term_get_buf(argvars, "term_setansicolors()");
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006557 if (buf == NULL)
6558 return;
6559 term = buf->b_term;
6560 if (term->tl_vterm == NULL)
6561 return;
6562
Bram Moolenaard83392a2022-09-01 12:22:46 +01006563 if (check_for_nonnull_list_arg(argvars, 1) == FAIL)
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006564 return;
Bram Moolenaard83392a2022-09-01 12:22:46 +01006565
LemonBoyb2b3acb2022-05-20 10:10:34 +01006566 if (argvars[1].vval.v_list->lv_first == &range_list_item
6567 || argvars[1].vval.v_list->lv_len != 16)
6568 {
Bram Moolenaar23919542023-05-05 22:12:22 +01006569 semsg(_(e_invalid_value_for_argument_str), "\"colors\"");
LemonBoyb2b3acb2022-05-20 10:10:34 +01006570 return;
6571 }
6572
6573 if (term->tl_palette == NULL)
6574 term->tl_palette = ALLOC_MULT(long_u, 16);
6575 if (term->tl_palette == NULL)
6576 return;
6577
6578 FOR_ALL_LIST_ITEMS(argvars[1].vval.v_list, li)
6579 {
6580 char_u *color_name;
6581 guicolor_T guicolor;
6582
6583 color_name = tv_get_string_chk(&li->li_tv);
6584 if (color_name == NULL)
6585 return;
6586
6587 guicolor = GUI_GET_COLOR(color_name);
6588 if (guicolor == INVALCOLOR)
6589 {
6590 semsg(_(e_cannot_allocate_color_str), color_name);
6591 return;
6592 }
6593
6594 term->tl_palette[n++] = GUI_MCH_GET_RGB(guicolor);
6595 }
6596
6597 term_update_palette(term);
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02006598}
6599#endif
6600
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006601/*
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006602 * "term_setapi(buf, api)" function
6603 */
6604 void
6605f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED)
6606{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006607 buf_T *buf;
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006608 term_T *term;
6609 char_u *api;
6610
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006611 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006612 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006613 || check_for_string_arg(argvars, 1) == FAIL))
6614 return;
6615
6616 buf = term_get_buf(argvars, "term_setapi()");
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006617 if (buf == NULL)
6618 return;
6619 term = buf->b_term;
6620 vim_free(term->tl_api);
6621 api = tv_get_string_chk(&argvars[1]);
6622 if (api != NULL)
6623 term->tl_api = vim_strsave(api);
6624 else
6625 term->tl_api = NULL;
6626}
6627
6628/*
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006629 * "term_setrestore(buf, command)" function
6630 */
6631 void
6632f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
6633{
6634#if defined(FEAT_SESSION)
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006635 buf_T *buf;
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006636 term_T *term;
6637 char_u *cmd;
6638
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006639 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006640 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006641 || check_for_string_arg(argvars, 1) == FAIL))
6642 return;
6643
6644 buf = term_get_buf(argvars, "term_setrestore()");
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006645 if (buf == NULL)
6646 return;
6647 term = buf->b_term;
6648 vim_free(term->tl_command);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006649 cmd = tv_get_string_chk(&argvars[1]);
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006650 if (cmd != NULL)
6651 term->tl_command = vim_strsave(cmd);
6652 else
6653 term->tl_command = NULL;
6654#endif
6655}
6656
6657/*
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006658 * "term_setkill(buf, how)" function
6659 */
6660 void
6661f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
6662{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006663 buf_T *buf;
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006664 term_T *term;
6665 char_u *how;
6666
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006667 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006668 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006669 || check_for_string_arg(argvars, 1) == FAIL))
6670 return;
6671
6672 buf = term_get_buf(argvars, "term_setkill()");
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006673 if (buf == NULL)
6674 return;
6675 term = buf->b_term;
6676 vim_free(term->tl_kill);
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006677 how = tv_get_string_chk(&argvars[1]);
Bram Moolenaar25cdd9c2018-03-10 20:28:12 +01006678 if (how != NULL)
6679 term->tl_kill = vim_strsave(how);
6680 else
6681 term->tl_kill = NULL;
6682}
6683
6684/*
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006685 * "term_start(command, options)" function
6686 */
6687 void
6688f_term_start(typval_T *argvars, typval_T *rettv)
6689{
6690 jobopt_T opt;
6691 buf_T *buf;
6692
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006693 if (in_vim9script()
6694 && (check_for_string_or_list_arg(argvars, 0) == FAIL
6695 || check_for_opt_dict_arg(argvars, 1) == FAIL))
6696 return;
6697
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006698 init_job_options(&opt);
6699 if (argvars[1].v_type != VAR_UNKNOWN
6700 && get_job_options(&argvars[1], &opt,
6701 JO_TIMEOUT_ALL + JO_STOPONEXIT
6702 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK
6703 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO,
6704 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD
6705 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN
Bram Moolenaar4d8bac82018-03-09 21:33:34 +01006706 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS
Bram Moolenaar83d47902020-03-26 20:34:00 +01006707 + JO2_NORESTORE + JO2_TERM_KILL + JO2_TERM_HIGHLIGHT
Bram Moolenaard2842ea2019-09-26 23:08:54 +02006708 + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006709 return;
6710
Bram Moolenaar13568252018-03-16 20:46:58 +01006711 buf = term_start(&argvars[0], NULL, &opt, 0);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006712
6713 if (buf != NULL && buf->b_term != NULL)
6714 rettv->vval.v_number = buf->b_fnum;
6715}
6716
6717/*
6718 * "term_wait" function
6719 */
6720 void
6721f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
6722{
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006723 buf_T *buf;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006724
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006725 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02006726 && (check_for_buffer_arg(argvars, 0) == FAIL
Yegappan Lakshmanan83494b42021-07-20 17:51:51 +02006727 || check_for_opt_number_arg(argvars, 1) == FAIL))
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02006728 return;
6729
6730 buf = term_get_buf(argvars, "term_wait()");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006731 if (buf == NULL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006732 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006733 if (buf->b_term->tl_job == NULL)
6734 {
6735 ch_log(NULL, "term_wait(): no job to wait for");
6736 return;
6737 }
6738 if (buf->b_term->tl_job->jv_channel == NULL)
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006739 // channel is closed, nothing to do
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006740 return;
6741
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006742 // Get the job status, this will detect a job that finished.
Bram Moolenaara15ef452018-02-09 16:46:00 +01006743 if (!buf->b_term->tl_job->jv_channel->ch_keep_open
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006744 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0)
6745 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006746 // The job is dead, keep reading channel I/O until the channel is
6747 // closed. buf->b_term may become NULL if the terminal was closed while
6748 // waiting.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006749 ch_log(NULL, "term_wait(): waiting for channel to close");
6750 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed)
6751 {
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006752 term_flush_messages();
6753
Bram Moolenaard45aa552018-05-21 22:50:29 +02006754 ui_delay(10L, FALSE);
Bram Moolenaare5182262017-11-19 15:05:44 +01006755 if (!buf_valid(buf))
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006756 // If the terminal is closed when the channel is closed the
6757 // buffer disappears.
Bram Moolenaare5182262017-11-19 15:05:44 +01006758 break;
Bram Moolenaareea32af2021-11-21 14:51:13 +00006759 if (buf->b_term == NULL || buf->b_term->tl_channel_closing)
6760 // came here from a close callback, only wait one time
6761 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006762 }
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006763
6764 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006765 }
6766 else
6767 {
6768 long wait = 10L;
6769
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006770 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006771
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006772 // Wait for some time for any channel I/O.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006773 if (argvars[1].v_type != VAR_UNKNOWN)
Bram Moolenaard155d7a2018-12-21 16:04:21 +01006774 wait = tv_get_number(&argvars[1]);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006775 ui_delay(wait, TRUE);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006776
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006777 // Flushing messages on channels is hopefully sufficient.
6778 // TODO: is there a better way?
Bram Moolenaar5c381eb2019-06-25 06:50:31 +02006779 term_flush_messages();
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006780 }
6781}
6782
6783/*
6784 * Called when a channel has sent all the lines to a terminal.
6785 * Send a CTRL-D to mark the end of the text.
6786 */
6787 void
6788term_send_eof(channel_T *ch)
6789{
6790 term_T *term;
6791
Bram Moolenaaraeea7212020-04-02 18:50:46 +02006792 FOR_ALL_TERMS(term)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006793 if (term->tl_job == ch->ch_job)
6794 {
6795 if (term->tl_eof_chars != NULL)
6796 {
6797 channel_send(ch, PART_IN, term->tl_eof_chars,
6798 (int)STRLEN(term->tl_eof_chars), NULL);
6799 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL);
6800 }
Bram Moolenaar4f974752019-02-17 17:44:42 +01006801# ifdef MSWIN
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006802 else
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006803 // Default: CTRL-D
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006804 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL);
6805# endif
6806 }
6807}
6808
Bram Moolenaar113e1072019-01-20 15:30:40 +01006809#if defined(FEAT_GUI) || defined(PROTO)
Bram Moolenaarf9c38832018-06-19 19:59:20 +02006810 job_T *
6811term_getjob(term_T *term)
6812{
6813 return term != NULL ? term->tl_job : NULL;
6814}
Bram Moolenaar113e1072019-01-20 15:30:40 +01006815#endif
Bram Moolenaarf9c38832018-06-19 19:59:20 +02006816
Bram Moolenaar4f974752019-02-17 17:44:42 +01006817# if defined(MSWIN) || defined(PROTO)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006818
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006819///////////////////////////////////////
6820// 2. MS-Windows implementation.
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006821#ifdef PROTO
6822typedef int COORD;
6823typedef int DWORD;
6824typedef int HANDLE;
6825typedef int *DWORD_PTR;
6826typedef int HPCON;
6827typedef int HRESULT;
6828typedef int LPPROC_THREAD_ATTRIBUTE_LIST;
Bram Moolenaarad3ec762019-04-21 00:00:13 +02006829typedef int SIZE_T;
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006830typedef int PSIZE_T;
6831typedef int PVOID;
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01006832typedef int BOOL;
6833# define WINAPI
Bram Moolenaarb9cdb372019-04-17 18:24:35 +02006834#endif
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02006835
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006836HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
6837HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
6838HRESULT (WINAPI *pClosePseudoConsole)(HPCON);
Bram Moolenaar48773f12019-02-12 21:46:46 +01006839BOOL (WINAPI *pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
6840BOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
6841void (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006842
6843 static int
6844dyn_conpty_init(int verbose)
6845{
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006846 static HMODULE hKerneldll = NULL;
6847 int i;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006848 static struct
6849 {
6850 char *name;
6851 FARPROC *ptr;
6852 } conpty_entry[] =
6853 {
6854 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole},
6855 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole},
6856 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole},
6857 {"InitializeProcThreadAttributeList",
6858 (FARPROC*)&pInitializeProcThreadAttributeList},
6859 {"UpdateProcThreadAttribute",
6860 (FARPROC*)&pUpdateProcThreadAttribute},
6861 {"DeleteProcThreadAttributeList",
6862 (FARPROC*)&pDeleteProcThreadAttributeList},
6863 {NULL, NULL}
6864 };
6865
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01006866 if (!has_conpty_working())
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006867 {
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006868 if (verbose)
Bram Moolenaard82a47d2022-01-05 20:24:39 +00006869 emsg(_(e_conpty_is_not_available));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006870 return FAIL;
6871 }
6872
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006873 // No need to initialize twice.
6874 if (hKerneldll)
6875 return OK;
6876
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006877 hKerneldll = vimLoadLib("kernel32.dll");
6878 for (i = 0; conpty_entry[i].name != NULL
6879 && conpty_entry[i].ptr != NULL; ++i)
6880 {
6881 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll,
6882 conpty_entry[i].name)) == NULL)
6883 {
6884 if (verbose)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00006885 semsg(_(e_could_not_load_library_function_str), conpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01006886 hKerneldll = NULL;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006887 return FAIL;
6888 }
6889 }
6890
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006891 return OK;
6892}
6893
6894 static int
6895conpty_term_and_job_init(
6896 term_T *term,
6897 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02006898 char **argv UNUSED,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006899 jobopt_T *opt,
6900 jobopt_T *orig_opt)
6901{
6902 WCHAR *cmd_wchar = NULL;
6903 WCHAR *cmd_wchar_copy = NULL;
6904 WCHAR *cwd_wchar = NULL;
6905 WCHAR *env_wchar = NULL;
6906 channel_T *channel = NULL;
6907 job_T *job = NULL;
6908 HANDLE jo = NULL;
6909 garray_T ga_cmd, ga_env;
6910 char_u *cmd = NULL;
6911 HRESULT hr;
6912 COORD consize;
6913 SIZE_T breq;
6914 PROCESS_INFORMATION proc_info;
6915 HANDLE i_theirs = NULL;
6916 HANDLE o_theirs = NULL;
6917 HANDLE i_ours = NULL;
6918 HANDLE o_ours = NULL;
6919
Bram Moolenaar04935fb2022-01-08 16:19:22 +00006920 ga_init2(&ga_cmd, sizeof(char*), 20);
6921 ga_init2(&ga_env, sizeof(char*), 20);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006922
6923 if (argvar->v_type == VAR_STRING)
6924 {
6925 cmd = argvar->vval.v_string;
6926 }
6927 else if (argvar->v_type == VAR_LIST)
6928 {
6929 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
6930 goto failed;
6931 cmd = ga_cmd.ga_data;
6932 }
6933 if (cmd == NULL || *cmd == NUL)
6934 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00006935 emsg(_(e_invalid_argument));
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006936 goto failed;
6937 }
6938
6939 term->tl_arg0_cmd = vim_strsave(cmd);
6940
6941 cmd_wchar = enc_to_utf16(cmd, NULL);
6942
6943 if (cmd_wchar != NULL)
6944 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006945 // Request by CreateProcessW
6946 breq = wcslen(cmd_wchar) + 1 + 1; // Addition of NUL by API
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006947 cmd_wchar_copy = ALLOC_MULT(WCHAR, breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006948 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1);
6949 }
6950
6951 ga_clear(&ga_cmd);
6952 if (cmd_wchar == NULL)
6953 goto failed;
6954 if (opt->jo_cwd != NULL)
6955 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
6956
6957 win32_build_env(opt->jo_env, &ga_env, TRUE);
6958 env_wchar = ga_env.ga_data;
6959
6960 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0))
6961 goto failed;
6962 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0))
6963 goto failed;
6964
6965 consize.X = term->tl_cols;
6966 consize.Y = term->tl_rows;
6967 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0,
6968 &term->tl_conpty);
6969 if (FAILED(hr))
6970 goto failed;
6971
6972 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex);
6973
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01006974 // Set up pipe inheritance safely: Vista or later.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006975 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq);
Bram Moolenaarc799fe22019-05-28 23:08:19 +02006976 term->tl_siex.lpAttributeList = alloc(breq);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01006977 if (!term->tl_siex.lpAttributeList)
6978 goto failed;
6979 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1,
6980 0, &breq))
6981 goto failed;
6982 if (!pUpdateProcThreadAttribute(
6983 term->tl_siex.lpAttributeList, 0,
6984 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty,
6985 sizeof(HPCON), NULL, NULL))
6986 goto failed;
6987
6988 channel = add_channel();
6989 if (channel == NULL)
6990 goto failed;
6991
6992 job = job_alloc();
6993 if (job == NULL)
6994 goto failed;
6995 if (argvar->v_type == VAR_STRING)
6996 {
6997 int argc;
6998
6999 build_argv_from_string(cmd, &job->jv_argv, &argc);
7000 }
7001 else
7002 {
7003 int argc;
7004
7005 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
7006 }
7007
7008 if (opt->jo_set & JO_IN_BUF)
7009 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
7010
7011 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE,
7012 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT
Bram Moolenaar07b761a2020-04-26 16:06:01 +02007013 | CREATE_SUSPENDED | CREATE_DEFAULT_ERROR_MODE,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007014 env_wchar, cwd_wchar,
7015 &term->tl_siex.StartupInfo, &proc_info))
7016 goto failed;
7017
7018 CloseHandle(i_theirs);
7019 CloseHandle(o_theirs);
7020
7021 channel_set_pipes(channel,
7022 (sock_T)i_ours,
7023 (sock_T)o_ours,
7024 (sock_T)o_ours);
7025
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007026 // Write lines with CR instead of NL.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007027 channel->ch_write_text_mode = TRUE;
7028
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007029 // Use to explicitly delete anonymous pipe handle.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007030 channel->ch_anonymous_pipe = TRUE;
7031
7032 jo = CreateJobObject(NULL, NULL);
7033 if (jo == NULL)
7034 goto failed;
7035
7036 if (!AssignProcessToJobObject(jo, proc_info.hProcess))
7037 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007038 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007039 CloseHandle(jo);
7040 jo = NULL;
7041 }
7042
7043 ResumeThread(proc_info.hThread);
7044 CloseHandle(proc_info.hThread);
7045
7046 vim_free(cmd_wchar);
7047 vim_free(cmd_wchar_copy);
7048 vim_free(cwd_wchar);
7049 vim_free(env_wchar);
7050
7051 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7052 goto failed;
7053
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007054#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01007055 if (term_use_palette())
7056 {
7057 if (term->tl_palette != NULL)
7058 set_vterm_palette(term->tl_vterm, term->tl_palette);
7059 else
7060 init_vterm_ansi_colors(term->tl_vterm);
7061 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007062#endif
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007063
7064 channel_set_job(channel, job, opt);
7065 job_set_options(job, opt);
7066
7067 job->jv_channel = channel;
7068 job->jv_proc_info = proc_info;
7069 job->jv_job_object = jo;
7070 job->jv_status = JOB_STARTED;
Bram Moolenaar18442cb2019-02-13 21:22:12 +01007071 job->jv_tty_type = vim_strsave((char_u *)"conpty");
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007072 ++job->jv_refcount;
7073 term->tl_job = job;
7074
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007075 // Redirecting stdout and stderr doesn't work at the job level. Instead
7076 // open the file here and handle it in. opt->jo_io was changed in
7077 // setup_job_options(), use the original flags here.
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007078 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
7079 {
7080 char_u *fname = opt->jo_io_name[PART_OUT];
7081
7082 ch_log(channel, "Opening output file %s", fname);
7083 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
7084 if (term->tl_out_fd == NULL)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00007085 semsg(_(e_cant_open_file_str), fname);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007086 }
7087
7088 return OK;
7089
7090failed:
7091 ga_clear(&ga_cmd);
7092 ga_clear(&ga_env);
7093 vim_free(cmd_wchar);
7094 vim_free(cmd_wchar_copy);
7095 vim_free(cwd_wchar);
7096 if (channel != NULL)
7097 channel_clear(channel);
7098 if (job != NULL)
7099 {
7100 job->jv_channel = NULL;
7101 job_cleanup(job);
7102 }
7103 term->tl_job = NULL;
7104 if (jo != NULL)
7105 CloseHandle(jo);
7106
7107 if (term->tl_siex.lpAttributeList != NULL)
7108 {
7109 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
7110 vim_free(term->tl_siex.lpAttributeList);
7111 }
7112 term->tl_siex.lpAttributeList = NULL;
7113 if (o_theirs != NULL)
7114 CloseHandle(o_theirs);
7115 if (o_ours != NULL)
7116 CloseHandle(o_ours);
7117 if (i_ours != NULL)
7118 CloseHandle(i_ours);
7119 if (i_theirs != NULL)
7120 CloseHandle(i_theirs);
7121 if (term->tl_conpty != NULL)
7122 pClosePseudoConsole(term->tl_conpty);
7123 term->tl_conpty = NULL;
7124 return FAIL;
7125}
7126
7127 static void
7128conpty_term_report_winsize(term_T *term, int rows, int cols)
7129{
7130 COORD consize;
7131
7132 consize.X = cols;
7133 consize.Y = rows;
7134 pResizePseudoConsole(term->tl_conpty, consize);
7135}
7136
Bram Moolenaar840d16f2019-09-10 21:27:18 +02007137 static void
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007138term_free_conpty(term_T *term)
7139{
7140 if (term->tl_siex.lpAttributeList != NULL)
7141 {
7142 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList);
7143 vim_free(term->tl_siex.lpAttributeList);
7144 }
7145 term->tl_siex.lpAttributeList = NULL;
7146 if (term->tl_conpty != NULL)
7147 pClosePseudoConsole(term->tl_conpty);
7148 term->tl_conpty = NULL;
7149}
7150
7151 int
7152use_conpty(void)
7153{
7154 return has_conpty;
7155}
7156
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007157# ifndef PROTO
7158
7159#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
7160#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
Bram Moolenaard317b382018-02-08 22:33:31 +01007161#define WINPTY_MOUSE_MODE_FORCE 2
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007162
7163void* (*winpty_config_new)(UINT64, void*);
7164void* (*winpty_open)(void*, void*);
7165void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
7166BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
7167void (*winpty_config_set_mouse_mode)(void*, int);
7168void (*winpty_config_set_initial_size)(void*, int, int);
7169LPCWSTR (*winpty_conin_name)(void*);
7170LPCWSTR (*winpty_conout_name)(void*);
7171LPCWSTR (*winpty_conerr_name)(void*);
7172void (*winpty_free)(void*);
7173void (*winpty_config_free)(void*);
7174void (*winpty_spawn_config_free)(void*);
7175void (*winpty_error_free)(void*);
7176LPCWSTR (*winpty_error_msg)(void*);
7177BOOL (*winpty_set_size)(void*, int, int, void*);
7178HANDLE (*winpty_agent_process)(void*);
7179
7180#define WINPTY_DLL "winpty.dll"
7181
7182static HINSTANCE hWinPtyDLL = NULL;
7183# endif
7184
7185 static int
7186dyn_winpty_init(int verbose)
7187{
7188 int i;
7189 static struct
7190 {
7191 char *name;
7192 FARPROC *ptr;
7193 } winpty_entry[] =
7194 {
7195 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
7196 {"winpty_config_free", (FARPROC*)&winpty_config_free},
7197 {"winpty_config_new", (FARPROC*)&winpty_config_new},
7198 {"winpty_config_set_mouse_mode",
7199 (FARPROC*)&winpty_config_set_mouse_mode},
7200 {"winpty_config_set_initial_size",
7201 (FARPROC*)&winpty_config_set_initial_size},
7202 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
7203 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
7204 {"winpty_error_free", (FARPROC*)&winpty_error_free},
7205 {"winpty_free", (FARPROC*)&winpty_free},
7206 {"winpty_open", (FARPROC*)&winpty_open},
7207 {"winpty_spawn", (FARPROC*)&winpty_spawn},
7208 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
7209 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
7210 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
7211 {"winpty_set_size", (FARPROC*)&winpty_set_size},
7212 {"winpty_agent_process", (FARPROC*)&winpty_agent_process},
7213 {NULL, NULL}
7214 };
7215
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007216 // No need to initialize twice.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007217 if (hWinPtyDLL)
7218 return OK;
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007219 // Load winpty.dll, prefer using the 'winptydll' option, fall back to just
7220 // winpty.dll.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007221 if (*p_winptydll != NUL)
7222 hWinPtyDLL = vimLoadLib((char *)p_winptydll);
7223 if (!hWinPtyDLL)
7224 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
7225 if (!hWinPtyDLL)
7226 {
7227 if (verbose)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00007228 semsg(_(e_could_not_load_library_str_str),
Martin Tournoij1a3e5742021-07-24 13:57:29 +02007229 (*p_winptydll != NUL ? p_winptydll : (char_u *)WINPTY_DLL),
7230 GetWin32Error());
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007231 return FAIL;
7232 }
7233 for (i = 0; winpty_entry[i].name != NULL
7234 && winpty_entry[i].ptr != NULL; ++i)
7235 {
7236 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
7237 winpty_entry[i].name)) == NULL)
7238 {
7239 if (verbose)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00007240 semsg(_(e_could_not_load_library_function_str), winpty_entry[i].name);
Bram Moolenaar5acd9872019-02-16 13:35:13 +01007241 hWinPtyDLL = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007242 return FAIL;
7243 }
7244 }
7245
7246 return OK;
7247}
7248
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007249 static int
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007250winpty_term_and_job_init(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007251 term_T *term,
7252 typval_T *argvar,
Bram Moolenaarbd67aac2019-09-21 23:09:04 +02007253 char **argv UNUSED,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007254 jobopt_T *opt,
7255 jobopt_T *orig_opt)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007256{
7257 WCHAR *cmd_wchar = NULL;
7258 WCHAR *cwd_wchar = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007259 WCHAR *env_wchar = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007260 channel_T *channel = NULL;
7261 job_T *job = NULL;
7262 DWORD error;
7263 HANDLE jo = NULL;
7264 HANDLE child_process_handle;
7265 HANDLE child_thread_handle;
Bram Moolenaar4aad53c2018-01-26 21:11:03 +01007266 void *winpty_err = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007267 void *spawn_config = NULL;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007268 garray_T ga_cmd, ga_env;
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007269 char_u *cmd = NULL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007270
Bram Moolenaar04935fb2022-01-08 16:19:22 +00007271 ga_init2(&ga_cmd, sizeof(char*), 20);
7272 ga_init2(&ga_env, sizeof(char*), 20);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007273
7274 if (argvar->v_type == VAR_STRING)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007275 {
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007276 cmd = argvar->vval.v_string;
7277 }
7278 else if (argvar->v_type == VAR_LIST)
7279 {
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007280 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007281 goto failed;
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007282 cmd = ga_cmd.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007283 }
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007284 if (cmd == NULL || *cmd == NUL)
7285 {
Bram Moolenaar436b5ad2021-12-31 22:49:24 +00007286 emsg(_(e_invalid_argument));
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007287 goto failed;
7288 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007289
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007290 term->tl_arg0_cmd = vim_strsave(cmd);
7291
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007292 cmd_wchar = enc_to_utf16(cmd, NULL);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007293 ga_clear(&ga_cmd);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007294 if (cmd_wchar == NULL)
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007295 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007296 if (opt->jo_cwd != NULL)
7297 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL);
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01007298
Bram Moolenaar52dbb5e2017-11-21 18:11:27 +01007299 win32_build_env(opt->jo_env, &ga_env, TRUE);
7300 env_wchar = ga_env.ga_data;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007301
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007302 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
7303 if (term->tl_winpty_config == NULL)
7304 goto failed;
7305
7306 winpty_config_set_mouse_mode(term->tl_winpty_config,
7307 WINPTY_MOUSE_MODE_FORCE);
7308 winpty_config_set_initial_size(term->tl_winpty_config,
7309 term->tl_cols, term->tl_rows);
7310 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
7311 if (term->tl_winpty == NULL)
7312 goto failed;
7313
7314 spawn_config = winpty_spawn_config_new(
7315 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
7316 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
7317 NULL,
7318 cmd_wchar,
7319 cwd_wchar,
Bram Moolenaarba6febd2017-10-30 21:56:23 +01007320 env_wchar,
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007321 &winpty_err);
7322 if (spawn_config == NULL)
7323 goto failed;
7324
7325 channel = add_channel();
7326 if (channel == NULL)
7327 goto failed;
7328
7329 job = job_alloc();
7330 if (job == NULL)
7331 goto failed;
Bram Moolenaarebe74b72018-04-21 23:34:43 +02007332 if (argvar->v_type == VAR_STRING)
7333 {
7334 int argc;
7335
7336 build_argv_from_string(cmd, &job->jv_argv, &argc);
7337 }
7338 else
7339 {
7340 int argc;
7341
7342 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc);
7343 }
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007344
7345 if (opt->jo_set & JO_IN_BUF)
7346 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]);
7347
7348 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
7349 &child_thread_handle, &error, &winpty_err))
7350 goto failed;
7351
7352 channel_set_pipes(channel,
7353 (sock_T)CreateFileW(
7354 winpty_conin_name(term->tl_winpty),
7355 GENERIC_WRITE, 0, NULL,
7356 OPEN_EXISTING, 0, NULL),
7357 (sock_T)CreateFileW(
7358 winpty_conout_name(term->tl_winpty),
7359 GENERIC_READ, 0, NULL,
7360 OPEN_EXISTING, 0, NULL),
7361 (sock_T)CreateFileW(
7362 winpty_conerr_name(term->tl_winpty),
7363 GENERIC_READ, 0, NULL,
7364 OPEN_EXISTING, 0, NULL));
7365
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007366 // Write lines with CR instead of NL.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007367 channel->ch_write_text_mode = TRUE;
7368
7369 jo = CreateJobObject(NULL, NULL);
7370 if (jo == NULL)
7371 goto failed;
7372
7373 if (!AssignProcessToJobObject(jo, child_process_handle))
7374 {
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007375 // Failed, switch the way to terminate process with TerminateProcess.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007376 CloseHandle(jo);
7377 jo = NULL;
7378 }
7379
7380 winpty_spawn_config_free(spawn_config);
7381 vim_free(cmd_wchar);
7382 vim_free(cwd_wchar);
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007383 vim_free(env_wchar);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007384
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007385 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7386 goto failed;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007387
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007388#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01007389 if (term_use_palette())
7390 {
7391 if (term->tl_palette != NULL)
7392 set_vterm_palette(term->tl_vterm, term->tl_palette);
7393 else
7394 init_vterm_ansi_colors(term->tl_vterm);
7395 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007396#endif
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02007397
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007398 channel_set_job(channel, job, opt);
7399 job_set_options(job, opt);
7400
7401 job->jv_channel = channel;
7402 job->jv_proc_info.hProcess = child_process_handle;
7403 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
7404 job->jv_job_object = jo;
7405 job->jv_status = JOB_STARTED;
7406 job->jv_tty_in = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007407 (short_u *)winpty_conin_name(term->tl_winpty), NULL);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007408 job->jv_tty_out = utf16_to_enc(
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007409 (short_u *)winpty_conout_name(term->tl_winpty), NULL);
Bram Moolenaar18442cb2019-02-13 21:22:12 +01007410 job->jv_tty_type = vim_strsave((char_u *)"winpty");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007411 ++job->jv_refcount;
7412 term->tl_job = job;
7413
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007414 // Redirecting stdout and stderr doesn't work at the job level. Instead
7415 // open the file here and handle it in. opt->jo_io was changed in
7416 // setup_job_options(), use the original flags here.
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007417 if (orig_opt->jo_io[PART_OUT] == JIO_FILE)
7418 {
7419 char_u *fname = opt->jo_io_name[PART_OUT];
7420
7421 ch_log(channel, "Opening output file %s", fname);
7422 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN);
7423 if (term->tl_out_fd == NULL)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00007424 semsg(_(e_cant_open_file_str), fname);
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007425 }
7426
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007427 return OK;
7428
7429failed:
Bram Moolenaarede35bb2018-01-26 20:05:18 +01007430 ga_clear(&ga_cmd);
7431 ga_clear(&ga_env);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007432 vim_free(cmd_wchar);
7433 vim_free(cwd_wchar);
7434 if (spawn_config != NULL)
7435 winpty_spawn_config_free(spawn_config);
7436 if (channel != NULL)
7437 channel_clear(channel);
7438 if (job != NULL)
7439 {
7440 job->jv_channel = NULL;
7441 job_cleanup(job);
7442 }
7443 term->tl_job = NULL;
7444 if (jo != NULL)
7445 CloseHandle(jo);
7446 if (term->tl_winpty != NULL)
7447 winpty_free(term->tl_winpty);
7448 term->tl_winpty = NULL;
7449 if (term->tl_winpty_config != NULL)
7450 winpty_config_free(term->tl_winpty_config);
7451 term->tl_winpty_config = NULL;
7452 if (winpty_err != NULL)
7453 {
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007454 char *msg = (char *)utf16_to_enc(
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007455 (short_u *)winpty_error_msg(winpty_err), NULL);
7456
Bram Moolenaarf9e3e092019-01-13 23:38:42 +01007457 emsg(msg);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007458 winpty_error_free(winpty_err);
7459 }
7460 return FAIL;
7461}
7462
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007463/*
7464 * Create a new terminal of "rows" by "cols" cells.
7465 * Store a reference in "term".
7466 * Return OK or FAIL.
7467 */
7468 static int
7469term_and_job_init(
7470 term_T *term,
7471 typval_T *argvar,
Bram Moolenaar197c6b72019-11-03 23:37:12 +01007472 char **argv,
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007473 jobopt_T *opt,
7474 jobopt_T *orig_opt)
7475{
7476 int use_winpty = FALSE;
7477 int use_conpty = FALSE;
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007478 int tty_type = *p_twt;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007479
7480 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE;
7481 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE;
7482
7483 if (!has_winpty && !has_conpty)
7484 // If neither is available give the errors for winpty, since when
7485 // conpty is not available it can't be installed either.
7486 return dyn_winpty_init(TRUE);
7487
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007488 if (opt->jo_tty_type != NUL)
7489 tty_type = opt->jo_tty_type;
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007490
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007491 if (tty_type == NUL)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007492 {
Bram Moolenaard9ef1b82019-02-13 19:23:10 +01007493 if (has_conpty && (is_conpty_stable() || !has_winpty))
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007494 use_conpty = TRUE;
7495 else if (has_winpty)
7496 use_winpty = TRUE;
7497 // else: error
7498 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007499 else if (tty_type == 'w') // winpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007500 {
7501 if (has_winpty)
7502 use_winpty = TRUE;
7503 }
Bram Moolenaarc6ddce32019-02-08 12:47:03 +01007504 else if (tty_type == 'c') // conpty
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007505 {
7506 if (has_conpty)
7507 use_conpty = TRUE;
7508 else
7509 return dyn_conpty_init(TRUE);
7510 }
7511
7512 if (use_conpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007513 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007514
7515 if (use_winpty)
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007516 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt);
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007517
7518 // error
7519 return dyn_winpty_init(TRUE);
7520}
7521
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007522 static int
7523create_pty_only(term_T *term, jobopt_T *options)
7524{
7525 HANDLE hPipeIn = INVALID_HANDLE_VALUE;
7526 HANDLE hPipeOut = INVALID_HANDLE_VALUE;
7527 char in_name[80], out_name[80];
7528 channel_T *channel = NULL;
7529
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007530 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7531 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007532
7533 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d",
7534 GetCurrentProcessId(),
7535 curbuf->b_fnum);
7536 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND,
7537 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
7538 PIPE_UNLIMITED_INSTANCES,
7539 0, 0, NMPWAIT_NOWAIT, NULL);
7540 if (hPipeIn == INVALID_HANDLE_VALUE)
7541 goto failed;
7542
7543 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d",
7544 GetCurrentProcessId(),
7545 curbuf->b_fnum);
7546 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND,
7547 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
7548 PIPE_UNLIMITED_INSTANCES,
7549 0, 0, 0, NULL);
7550 if (hPipeOut == INVALID_HANDLE_VALUE)
7551 goto failed;
7552
7553 ConnectNamedPipe(hPipeIn, NULL);
7554 ConnectNamedPipe(hPipeOut, NULL);
7555
7556 term->tl_job = job_alloc();
7557 if (term->tl_job == NULL)
7558 goto failed;
7559 ++term->tl_job->jv_refcount;
7560
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007561 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007562 term->tl_job->jv_status = JOB_FINISHED;
7563
7564 channel = add_channel();
7565 if (channel == NULL)
7566 goto failed;
7567 term->tl_job->jv_channel = channel;
7568 channel->ch_keep_open = TRUE;
7569 channel->ch_named_pipe = TRUE;
7570
7571 channel_set_pipes(channel,
7572 (sock_T)hPipeIn,
7573 (sock_T)hPipeOut,
7574 (sock_T)hPipeOut);
7575 channel_set_job(channel, term->tl_job, options);
7576 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name);
7577 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name);
7578
7579 return OK;
7580
7581failed:
7582 if (hPipeIn != NULL)
7583 CloseHandle(hPipeIn);
7584 if (hPipeOut != NULL)
7585 CloseHandle(hPipeOut);
7586 return FAIL;
7587}
7588
7589/*
7590 * Free the terminal emulator part of "term".
7591 */
7592 static void
7593term_free_vterm(term_T *term)
7594{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007595 term_free_conpty(term);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007596 if (term->tl_winpty != NULL)
7597 winpty_free(term->tl_winpty);
7598 term->tl_winpty = NULL;
7599 if (term->tl_winpty_config != NULL)
7600 winpty_config_free(term->tl_winpty_config);
7601 term->tl_winpty_config = NULL;
7602 if (term->tl_vterm != NULL)
7603 vterm_free(term->tl_vterm);
7604 term->tl_vterm = NULL;
7605}
7606
7607/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02007608 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007609 */
7610 static void
7611term_report_winsize(term_T *term, int rows, int cols)
7612{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007613 if (term->tl_conpty)
7614 conpty_term_report_winsize(term, rows, cols);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007615 if (term->tl_winpty)
7616 winpty_set_size(term->tl_winpty, cols, rows, NULL);
7617}
7618
7619 int
7620terminal_enabled(void)
7621{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007622 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007623}
7624
7625# else
7626
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007627///////////////////////////////////////
7628// 3. Unix-like implementation.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007629
7630/*
7631 * Create a new terminal of "rows" by "cols" cells.
7632 * Start job for "cmd".
7633 * Store the pointers in "term".
Bram Moolenaar13568252018-03-16 20:46:58 +01007634 * When "argv" is not NULL then "argvar" is not used.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007635 * Return OK or FAIL.
7636 */
7637 static int
7638term_and_job_init(
7639 term_T *term,
7640 typval_T *argvar,
Bram Moolenaar13568252018-03-16 20:46:58 +01007641 char **argv,
Bram Moolenaarf25329c2018-05-06 21:49:32 +02007642 jobopt_T *opt,
7643 jobopt_T *orig_opt UNUSED)
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007644{
Bram Moolenaaraa5df7e2019-02-03 14:53:10 +01007645 term->tl_arg0_cmd = NULL;
7646
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007647 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7648 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007649
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007650#if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS)
LemonBoyb2b3acb2022-05-20 10:10:34 +01007651 if (term_use_palette())
7652 {
7653 if (term->tl_palette != NULL)
7654 set_vterm_palette(term->tl_vterm, term->tl_palette);
7655 else
7656 init_vterm_ansi_colors(term->tl_vterm);
7657 }
Bram Moolenaar30b9a412022-05-26 14:06:37 +01007658#endif
Bram Moolenaarf59c6e82018-04-10 15:59:11 +02007659
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007660 // This may change a string in "argvar".
Bram Moolenaar21109272020-01-30 16:27:20 +01007661 term->tl_job = job_start(argvar, argv, opt, &term->tl_job);
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007662 if (term->tl_job != NULL)
7663 ++term->tl_job->jv_refcount;
7664
7665 return term->tl_job != NULL
7666 && term->tl_job->jv_channel != NULL
7667 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
7668}
7669
7670 static int
7671create_pty_only(term_T *term, jobopt_T *opt)
7672{
Bram Moolenaarcd929f72018-12-24 21:38:45 +01007673 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL)
7674 return FAIL;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007675
7676 term->tl_job = job_alloc();
7677 if (term->tl_job == NULL)
7678 return FAIL;
7679 ++term->tl_job->jv_refcount;
7680
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007681 // behave like the job is already finished
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007682 term->tl_job->jv_status = JOB_FINISHED;
7683
7684 return mch_create_pty_channel(term->tl_job, opt);
7685}
7686
7687/*
7688 * Free the terminal emulator part of "term".
7689 */
7690 static void
7691term_free_vterm(term_T *term)
7692{
7693 if (term->tl_vterm != NULL)
7694 vterm_free(term->tl_vterm);
7695 term->tl_vterm = NULL;
7696}
7697
7698/*
Bram Moolenaara42d3632018-04-14 17:05:38 +02007699 * Report the size to the terminal.
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007700 */
7701 static void
7702term_report_winsize(term_T *term, int rows, int cols)
7703{
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007704 // Use an ioctl() to report the new window size to the job.
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00007705 if (term->tl_job == NULL || term->tl_job->jv_channel == NULL)
7706 return;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007707
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00007708 int fd = -1;
7709 int part;
7710
7711 for (part = PART_OUT; part < PART_COUNT; ++part)
7712 {
7713 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
7714 if (mch_isatty(fd))
7715 break;
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007716 }
Yegappan Lakshmanan032713f2023-01-25 21:05:38 +00007717 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
7718 mch_signal_job(term->tl_job, (char_u *)"winch");
Bram Moolenaar2e6ab182017-09-20 10:03:07 +02007719}
7720
7721# endif
7722
Bram Moolenaar0d6f5d92019-12-05 21:33:15 +01007723#endif // FEAT_TERMINAL