blob: fee87d5892efbba3970a84c229d58cae2fd80bb2 [file] [log] [blame]
Bram Moolenaare4f25e42017-07-07 11:54:15 +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 *
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020013 * There are three parts:
14 * 1. Generic code for all systems.
Bram Moolenaarb13501f2017-07-22 22:32:56 +020015 * Uses libvterm for the terminal emulator.
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020016 * 2. The MS-Windows implementation.
Bram Moolenaarb13501f2017-07-22 22:32:56 +020017 * Uses winpty.
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020018 * 3. The Unix-like implementation.
Bram Moolenaarb13501f2017-07-22 22:32:56 +020019 * Uses pseudo-tty's (pty's).
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020020 *
21 * For each terminal one VTerm is constructed. This uses libvterm. A copy of
Bram Moolenaar63ecdda2017-07-28 22:29:35 +020022 * this library is in the libvterm directory.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020023 *
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020024 * When a terminal window is opened, a job is started that will be connected to
25 * the terminal emulator.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020026 *
27 * If the terminal window has keyboard focus, typed keys are converted to the
Bram Moolenaar8a773062017-07-24 22:29:21 +020028 * terminal encoding and writing to the job over a channel.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020029 *
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020030 * 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.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020034 *
Bram Moolenaar63ecdda2017-07-28 22:29:35 +020035 * 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 *
Bram Moolenaare4f25e42017-07-07 11:54:15 +020038 * TODO:
Bram Moolenaar63ecdda2017-07-28 22:29:35 +020039 * - Patch for functions: Yasuhiro Matsumoto, #1871
Bram Moolenaard85f2712017-07-28 21:51:57 +020040 * - For the scrollback buffer store lines in the buffer, only attributes in
41 * tl_scrollback.
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020042 * - When the job ends:
Bram Moolenaar21554412017-07-24 21:44:43 +020043 * - Need an option or argument to drop the window+buffer right away, to be
44 * used for a shell or Vim.
Bram Moolenaard85f2712017-07-28 21:51:57 +020045 * - To set BS correctly, check get_stty(); Pass the fd of the pty.
Bram Moolenaard85f2712017-07-28 21:51:57 +020046 * - do not store terminal buffer in viminfo. Or prefix term:// ?
Bram Moolenaar21554412017-07-24 21:44:43 +020047 * - add a character in :ls output
Bram Moolenaar938783d2017-07-16 20:13:26 +020048 * - when closing window and job has not ended, make terminal hidden?
Bram Moolenaard85f2712017-07-28 21:51:57 +020049 * - when closing window and job has ended, make buffer hidden?
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +020050 * - don't allow exiting Vim when a terminal is still running a job
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020051 * - use win_del_lines() to make scroll-up efficient.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020052 * - add test for giving error for invalid 'termsize' value.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020053 * - support minimal size when 'termsize' is "rows*cols".
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020054 * - support minimal size when 'termsize' is empty?
Bram Moolenaar5a1feb82017-07-22 18:04:08 +020055 * - implement "term" for job_start(): more job options when starting a
56 * terminal.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020057 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
58 * conversions.
Bram Moolenaardbe948d2017-07-23 22:50:51 +020059 * - In the GUI use a terminal emulator for :!cmd.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020060 */
61
62#include "vim.h"
63
Bram Moolenaarc6df10e2017-07-29 20:15:08 +020064#if defined(FEAT_TERMINAL) || defined(PROTO)
Bram Moolenaare4f25e42017-07-07 11:54:15 +020065
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020066#ifdef WIN3264
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020067# define MIN(x,y) (x < y ? x : y)
68# define MAX(x,y) (x > y ? x : y)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020069#endif
Bram Moolenaare4f25e42017-07-07 11:54:15 +020070
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020071#include "libvterm/include/vterm.h"
72
Bram Moolenaard85f2712017-07-28 21:51:57 +020073typedef struct sb_line_S {
74 int sb_cols; /* can differ per line */
75 VTermScreenCell *sb_cells; /* allocated */
76} sb_line_T;
77
Bram Moolenaare4f25e42017-07-07 11:54:15 +020078/* typedef term_T in structs.h */
79struct terminal_S {
80 term_T *tl_next;
81
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020082#ifdef WIN3264
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020083 void *tl_winpty_config;
84 void *tl_winpty;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020085#endif
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020086 VTerm *tl_vterm;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020087 job_T *tl_job;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +020088 buf_T *tl_buffer;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020089
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020090 /* last known vterm size */
91 int tl_rows;
92 int tl_cols;
93 /* vterm size does not follow window size */
94 int tl_rows_fixed;
95 int tl_cols_fixed;
96
Bram Moolenaar21554412017-07-24 21:44:43 +020097 char_u *tl_title; /* NULL or allocated */
98 char_u *tl_status_text; /* NULL or allocated */
99
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200100 /* Range of screen rows to update. Zero based. */
101 int tl_dirty_row_start; /* -1 if nothing dirty */
102 int tl_dirty_row_end; /* row below last one to update */
103
Bram Moolenaard85f2712017-07-28 21:51:57 +0200104 garray_T tl_scrollback;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200105 int tl_scrollback_scrolled;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200106
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200107 pos_T tl_cursor;
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200108 int tl_cursor_visible;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200109};
110
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200111/*
112 * List of all active terminals.
113 */
114static term_T *first_term = NULL;
115
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200116
117#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
118#define KEY_BUF_LEN 200
119
120/*
121 * Functions with separate implementation for MS-Windows and Unix-like systems.
122 */
123static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd);
Bram Moolenaar43da3e32017-07-23 17:27:54 +0200124static void term_report_winsize(term_T *term, int rows, int cols);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200125static void term_free_vterm(term_T *term);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200126
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200127/**************************************
128 * 1. Generic code for all systems.
129 */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200130
131/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200132 * Determine the terminal size from 'termsize' and the current window.
133 * Assumes term->tl_rows and term->tl_cols are zero.
134 */
135 static void
136set_term_and_win_size(term_T *term)
137{
138 if (*curwin->w_p_tms != NUL)
139 {
140 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
141
142 term->tl_rows = atoi((char *)curwin->w_p_tms);
143 term->tl_cols = atoi((char *)p);
144 }
145 if (term->tl_rows == 0)
146 term->tl_rows = curwin->w_height;
147 else
148 {
149 win_setheight_win(term->tl_rows, curwin);
150 term->tl_rows_fixed = TRUE;
151 }
152 if (term->tl_cols == 0)
153 term->tl_cols = curwin->w_width;
154 else
155 {
156 win_setwidth_win(term->tl_cols, curwin);
157 term->tl_cols_fixed = TRUE;
158 }
159}
160
161/*
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200162 * ":terminal": open a terminal window and execute a job in it.
163 */
164 void
165ex_terminal(exarg_T *eap)
166{
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200167 exarg_T split_ea;
168 win_T *old_curwin = curwin;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200169 term_T *term;
Bram Moolenaare173fd02017-07-22 19:03:32 +0200170 char_u *cmd = eap->arg;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200171
172 if (check_restricted() || check_secure())
173 return;
174
175 term = (term_T *)alloc_clear(sizeof(term_T));
176 if (term == NULL)
177 return;
178 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200179 term->tl_cursor_visible = TRUE;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200180 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200181
182 /* Open a new window or tab. */
183 vim_memset(&split_ea, 0, sizeof(split_ea));
184 split_ea.cmdidx = CMD_new;
185 split_ea.cmd = (char_u *)"new";
186 split_ea.arg = (char_u *)"";
187 ex_splitview(&split_ea);
188 if (curwin == old_curwin)
189 {
190 /* split failed */
191 vim_free(term);
192 return;
193 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200194 term->tl_buffer = curbuf;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200195 curbuf->b_term = term;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200196
197 /* Link the new terminal in the list of active terminals. */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200198 term->tl_next = first_term;
199 first_term = term;
200
Bram Moolenaar293424c2017-07-26 23:11:01 +0200201 if (cmd == NULL || *cmd == NUL)
202 cmd = p_sh;
203
Bram Moolenaar1f2903c2017-07-23 19:51:01 +0200204 if (buflist_findname(cmd) == NULL)
205 curbuf->b_ffname = vim_strsave(cmd);
206 else
207 {
208 int i;
209 size_t len = STRLEN(cmd) + 10;
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200210 char_u *p = alloc((int)len);
Bram Moolenaar1f2903c2017-07-23 19:51:01 +0200211
212 for (i = 1; p != NULL; ++i)
213 {
214 vim_snprintf((char *)p, len, "%s (%d)", cmd, i);
215 if (buflist_findname(p) == NULL)
216 {
217 curbuf->b_ffname = p;
218 break;
219 }
220 }
221 }
222 curbuf->b_fname = curbuf->b_ffname;
223
224 /* Mark the buffer as changed, so that it's not easy to abandon the job. */
225 curbuf->b_changed = TRUE;
226 curbuf->b_p_ma = FALSE;
227 set_string_option_direct((char_u *)"buftype", -1,
228 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200229
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200230 set_term_and_win_size(term);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200231
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200232 /* System dependent: setup the vterm and start the job in it. */
Bram Moolenaare173fd02017-07-22 19:03:32 +0200233 if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd) == OK)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200234 {
235 /* store the size we ended up with */
236 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200237 }
238 else
239 {
Bram Moolenaard85f2712017-07-28 21:51:57 +0200240 free_terminal(curbuf);
Bram Moolenaar61a66052017-07-22 18:39:00 +0200241
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200242 /* Wiping out the buffer will also close the window and call
243 * free_terminal(). */
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200244 do_buffer(DOBUF_WIPE, DOBUF_CURRENT, FORWARD, 0, TRUE);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200245 }
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200246
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200247 /* TODO: Setup pty, see mch_call_shell(). */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200248}
249
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200250/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +0200251 * Free the scrollback buffer for "term".
252 */
253 static void
254free_scrollback(term_T *term)
255{
256 int i;
257
258 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
259 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
260 ga_clear(&term->tl_scrollback);
261}
262
263/*
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200264 * Free a terminal and everything it refers to.
265 * Kills the job if there is one.
266 * Called when wiping out a buffer.
267 */
268 void
Bram Moolenaard85f2712017-07-28 21:51:57 +0200269free_terminal(buf_T *buf)
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200270{
Bram Moolenaard85f2712017-07-28 21:51:57 +0200271 term_T *term = buf->b_term;
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200272 term_T *tp;
273
274 if (term == NULL)
275 return;
276 if (first_term == term)
277 first_term = term->tl_next;
278 else
279 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
280 if (tp->tl_next == term)
281 {
282 tp->tl_next = term->tl_next;
283 break;
284 }
285
286 if (term->tl_job != NULL)
287 {
Bram Moolenaar61a66052017-07-22 18:39:00 +0200288 if (term->tl_job->jv_status != JOB_ENDED
289 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200290 job_stop(term->tl_job, NULL, "kill");
291 job_unref(term->tl_job);
292 }
293
Bram Moolenaar63ecdda2017-07-28 22:29:35 +0200294 free_scrollback(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200295
296 term_free_vterm(term);
Bram Moolenaar21554412017-07-24 21:44:43 +0200297 vim_free(term->tl_title);
298 vim_free(term->tl_status_text);
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200299 vim_free(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200300 buf->b_term = NULL;
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200301}
302
303/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200304 * Write job output "msg[len]" to the vterm.
305 */
306 static void
307term_write_job_output(term_T *term, char_u *msg, size_t len)
308{
309 VTerm *vterm = term->tl_vterm;
310 char_u *p;
311 size_t done;
312 size_t len_now;
313
314 for (done = 0; done < len; done += len_now)
315 {
316 for (p = msg + done; p < msg + len; )
317 {
318 if (*p == NL)
319 break;
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200320 p += utf_ptr2len_len(p, (int)(len - (p - msg)));
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200321 }
322 len_now = p - msg - done;
323 vterm_input_write(vterm, (char *)msg + done, len_now);
324 if (p < msg + len && *p == NL)
325 {
326 /* Convert NL to CR-NL, that appears to work best. */
327 vterm_input_write(vterm, "\r\n", 2);
328 ++len_now;
329 }
330 }
331
332 /* this invokes the damage callbacks */
333 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
334}
335
Bram Moolenaar1c844932017-07-24 23:36:41 +0200336 static void
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200337update_cursor(term_T *term, int redraw)
Bram Moolenaar1c844932017-07-24 23:36:41 +0200338{
Bram Moolenaar1c844932017-07-24 23:36:41 +0200339 setcursor();
Bram Moolenaar4cc93dc2017-07-26 21:49:37 +0200340 if (redraw && term->tl_buffer == curbuf)
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200341 {
Bram Moolenaar4cc93dc2017-07-26 21:49:37 +0200342 if (term->tl_cursor_visible)
343 cursor_on();
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200344 out_flush();
Bram Moolenaar1c844932017-07-24 23:36:41 +0200345#ifdef FEAT_GUI
Bram Moolenaar4cc93dc2017-07-26 21:49:37 +0200346 if (gui.in_use && term->tl_cursor_visible)
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200347 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar1c844932017-07-24 23:36:41 +0200348#endif
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200349 }
Bram Moolenaar1c844932017-07-24 23:36:41 +0200350}
351
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200352/*
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200353 * Invoked when "msg" output from a job was received. Write it to the terminal
354 * of "buffer".
355 */
356 void
357write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
358{
359 size_t len = STRLEN(msg);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200360 term_T *term = buffer->b_term;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200361
Bram Moolenaard85f2712017-07-28 21:51:57 +0200362 if (term->tl_vterm == NULL)
363 {
364 ch_logn(channel, "NOT writing %d bytes to terminal", (int)len);
365 return;
366 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200367 ch_logn(channel, "writing %d bytes to terminal", (int)len);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200368 term_write_job_output(term, msg, len);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200369
370 /* TODO: only update once in a while. */
371 update_screen(0);
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200372 update_cursor(term, TRUE);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200373}
374
375/*
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200376 * Convert typed key "c" into bytes to send to the job.
377 * Return the number of bytes in "buf".
378 */
379 static int
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200380term_convert_key(term_T *term, int c, char *buf)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200381{
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200382 VTerm *vterm = term->tl_vterm;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200383 VTermKey key = VTERM_KEY_NONE;
384 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200385
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200386 switch (c)
387 {
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200388 case CAR: key = VTERM_KEY_ENTER; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200389 case ESC: key = VTERM_KEY_ESCAPE; break;
Bram Moolenaare906ae82017-07-21 21:10:01 +0200390 /* VTERM_KEY_BACKSPACE becomes 0x7f DEL */
391 case K_BS: c = BS; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200392 case K_DEL: key = VTERM_KEY_DEL; break;
393 case K_DOWN: key = VTERM_KEY_DOWN; break;
394 case K_END: key = VTERM_KEY_END; break;
395 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
396 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
397 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
398 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
399 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
400 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
401 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
402 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
403 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
404 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
405 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
406 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
407 case K_HOME: key = VTERM_KEY_HOME; break;
408 case K_INS: key = VTERM_KEY_INS; break;
409 case K_K0: key = VTERM_KEY_KP_0; break;
410 case K_K1: key = VTERM_KEY_KP_1; break;
411 case K_K2: key = VTERM_KEY_KP_2; break;
412 case K_K3: key = VTERM_KEY_KP_3; break;
413 case K_K4: key = VTERM_KEY_KP_4; break;
414 case K_K5: key = VTERM_KEY_KP_5; break;
415 case K_K6: key = VTERM_KEY_KP_6; break;
416 case K_K7: key = VTERM_KEY_KP_7; break;
417 case K_K8: key = VTERM_KEY_KP_8; break;
418 case K_K9: key = VTERM_KEY_KP_9; break;
419 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
420 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
421 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
422 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
423 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
424 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
425 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
426 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
427 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
428 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
429 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
430 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
431 case K_LEFT: key = VTERM_KEY_LEFT; break;
432 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
433 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
434 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
435 case K_UP: key = VTERM_KEY_UP; break;
436 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaare825d8b2017-07-19 23:20:19 +0200437
438 case K_MOUSEUP: /* TODO */ break;
439 case K_MOUSEDOWN: /* TODO */ break;
440 case K_MOUSELEFT: /* TODO */ break;
441 case K_MOUSERIGHT: /* TODO */ break;
442
443 case K_LEFTMOUSE: /* TODO */ break;
444 case K_LEFTMOUSE_NM: /* TODO */ break;
445 case K_LEFTDRAG: /* TODO */ break;
446 case K_LEFTRELEASE: /* TODO */ break;
447 case K_LEFTRELEASE_NM: /* TODO */ break;
448 case K_MIDDLEMOUSE: /* TODO */ break;
449 case K_MIDDLEDRAG: /* TODO */ break;
450 case K_MIDDLERELEASE: /* TODO */ break;
451 case K_RIGHTMOUSE: /* TODO */ break;
452 case K_RIGHTDRAG: /* TODO */ break;
453 case K_RIGHTRELEASE: /* TODO */ break;
454 case K_X1MOUSE: /* TODO */ break;
455 case K_X1DRAG: /* TODO */ break;
456 case K_X1RELEASE: /* TODO */ break;
457 case K_X2MOUSE: /* TODO */ break;
458 case K_X2DRAG: /* TODO */ break;
459 case K_X2RELEASE: /* TODO */ break;
460
461 /* TODO: handle all special keys and modifiers that terminal_loop()
462 * does not handle. */
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200463 }
464
465 /*
466 * Convert special keys to vterm keys:
467 * - Write keys to vterm: vterm_keyboard_key()
468 * - Write output to channel.
469 */
470 if (key != VTERM_KEY_NONE)
471 /* Special key, let vterm convert it. */
472 vterm_keyboard_key(vterm, key, mod);
473 else
474 /* Normal character, let vterm convert it. */
475 vterm_keyboard_unichar(vterm, c, mod);
476
477 /* Read back the converted escape sequence. */
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200478 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200479}
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200480
Bram Moolenaar938783d2017-07-16 20:13:26 +0200481/*
Bram Moolenaard85f2712017-07-28 21:51:57 +0200482 * Return TRUE if the job for "buf" is still running.
483 */
484 static int
485term_job_running(term_T *term)
486{
Bram Moolenaar1e8340b2017-07-29 15:53:39 +0200487 /* Also consider the job finished when the channel is closed, to avoid a
488 * race condition when updating the title. */
489 return term->tl_job != NULL
490 && term->tl_job->jv_status == JOB_STARTED
491 && channel_is_open(term->tl_job->jv_channel);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200492}
493
494/*
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200495 * Get a key from the user without mapping.
496 * TODO: use terminal mode mappings.
497 */
498 static int
499term_vgetc()
500{
501 int c;
502
503 ++no_mapping;
504 ++allow_keys;
505 got_int = FALSE;
506 c = vgetc();
507 --no_mapping;
508 --allow_keys;
509 return c;
510}
511
512/*
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200513 * Send keys to terminal.
514 */
515 static int
516send_keys_to_term(term_T *term, int c, int typed)
517{
518 char msg[KEY_BUF_LEN];
519 size_t len;
520 static int mouse_was_outside = FALSE;
521 int dragging_outside = FALSE;
522
523 /* Catch keys that need to be handled as in Normal mode. */
524 switch (c)
525 {
526 case NUL:
527 case K_ZERO:
528 if (typed)
529 stuffcharReadbuff(c);
530 return FAIL;
531
532 case K_IGNORE:
533 return FAIL;
534
535 case K_LEFTDRAG:
536 case K_MIDDLEDRAG:
537 case K_RIGHTDRAG:
538 case K_X1DRAG:
539 case K_X2DRAG:
540 dragging_outside = mouse_was_outside;
541 /* FALLTHROUGH */
542 case K_LEFTMOUSE:
543 case K_LEFTMOUSE_NM:
544 case K_LEFTRELEASE:
545 case K_LEFTRELEASE_NM:
546 case K_MIDDLEMOUSE:
547 case K_MIDDLERELEASE:
548 case K_RIGHTMOUSE:
549 case K_RIGHTRELEASE:
550 case K_X1MOUSE:
551 case K_X1RELEASE:
552 case K_X2MOUSE:
553 case K_X2RELEASE:
554 if (mouse_row < W_WINROW(curwin)
555 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
556 || mouse_col < W_WINCOL(curwin)
557 || mouse_col >= W_ENDCOL(curwin)
558 || dragging_outside)
559 {
560 /* click outside the current window */
561 if (typed)
562 {
563 stuffcharReadbuff(c);
564 mouse_was_outside = TRUE;
565 }
566 return FAIL;
567 }
568 }
569 if (typed)
570 mouse_was_outside = FALSE;
571
572 /* Convert the typed key to a sequence of bytes for the job. */
573 len = term_convert_key(term, c, msg);
574 if (len > 0)
575 /* TODO: if FAIL is returned, stop? */
576 channel_send(term->tl_job->jv_channel, PART_IN,
577 (char_u *)msg, (int)len, NULL);
578
579 return OK;
580}
581
582/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200583 * Wait for input and send it to the job.
584 * Return when the start of a CTRL-W command is typed or anything else that
585 * should be handled as a Normal mode command.
Bram Moolenaard85f2712017-07-28 21:51:57 +0200586 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
587 * the terminal was closed.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200588 */
Bram Moolenaard85f2712017-07-28 21:51:57 +0200589 int
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200590terminal_loop(void)
591{
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200592 int c;
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200593 int termkey = 0;
594
Bram Moolenaard85f2712017-07-28 21:51:57 +0200595 if (curbuf->b_term->tl_vterm == NULL || !term_job_running(curbuf->b_term))
596 /* job finished */
597 return OK;
598
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200599 if (*curwin->w_p_tk != NUL)
600 termkey = string_to_key(curwin->w_p_tk, TRUE);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200601
602 for (;;)
603 {
604 /* TODO: skip screen update when handling a sequence of keys. */
605 update_screen(0);
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200606 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200607 c = term_vgetc();
Bram Moolenaard85f2712017-07-28 21:51:57 +0200608 if (curbuf->b_term->tl_vterm == NULL
609 || !term_job_running(curbuf->b_term))
610 /* job finished while waiting for a character */
611 break;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200612
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200613 if (c == (termkey == 0 ? Ctrl_W : termkey))
614 {
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200615#ifdef FEAT_CMDL_INFO
616 if (add_to_showcmd(c))
617 out_flush();
618#endif
619 c = term_vgetc();
620#ifdef FEAT_CMDL_INFO
621 clear_showcmd();
622#endif
Bram Moolenaard85f2712017-07-28 21:51:57 +0200623 if (curbuf->b_term->tl_vterm == NULL
624 || !term_job_running(curbuf->b_term))
625 /* job finished while waiting for a character */
626 break;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200627
628 if (termkey == 0 && c == '.')
629 /* "CTRL-W .": send CTRL-W to the job */
630 c = Ctrl_W;
631 else if (termkey == 0 || c != termkey)
632 {
633 stuffcharReadbuff(Ctrl_W);
634 stuffcharReadbuff(c);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200635 return OK;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200636 }
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200637 }
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200638 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
639 return OK;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200640 }
Bram Moolenaard85f2712017-07-28 21:51:57 +0200641 return FAIL;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +0200642}
643
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200644/*
645 * Called when a job has finished.
646 */
647 void
648term_job_ended(job_T *job)
649{
650 term_T *term;
651 int did_one = FALSE;
652
653 for (term = first_term; term != NULL; term = term->tl_next)
654 if (term->tl_job == job)
655 {
656 vim_free(term->tl_title);
657 term->tl_title = NULL;
658 vim_free(term->tl_status_text);
659 term->tl_status_text = NULL;
660 redraw_buf_and_status_later(term->tl_buffer, VALID);
661 did_one = TRUE;
662 }
663 if (did_one)
664 redraw_statuslines();
665 if (curbuf->b_term != NULL)
666 {
667 if (curbuf->b_term->tl_job == job)
668 maketitle();
669 update_cursor(curbuf->b_term, TRUE);
670 }
671}
672
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200673 static void
674position_cursor(win_T *wp, VTermPos *pos)
675{
676 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
677 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
678}
679
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200680 static void
681may_toggle_cursor(term_T *term)
682{
683 if (curbuf == term->tl_buffer)
684 {
685 if (term->tl_cursor_visible)
686 cursor_on();
687 else
688 cursor_off();
689 }
690}
691
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200692 static int
693handle_damage(VTermRect rect, void *user)
694{
695 term_T *term = (term_T *)user;
696
697 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
698 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
699 redraw_buf_later(term->tl_buffer, NOT_VALID);
700 return 1;
701}
702
703 static int
704handle_moverect(VTermRect dest UNUSED, VTermRect src UNUSED, void *user)
705{
706 term_T *term = (term_T *)user;
707
708 /* TODO */
709 redraw_buf_later(term->tl_buffer, NOT_VALID);
710 return 1;
711}
712
713 static int
714handle_movecursor(
715 VTermPos pos,
716 VTermPos oldpos UNUSED,
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200717 int visible,
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200718 void *user)
719{
720 term_T *term = (term_T *)user;
721 win_T *wp;
722 int is_current = FALSE;
723
724 FOR_ALL_WINDOWS(wp)
725 {
726 if (wp->w_buffer == term->tl_buffer)
727 {
728 position_cursor(wp, &pos);
729 if (wp == curwin)
730 is_current = TRUE;
731 }
732 }
733
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200734 term->tl_cursor_visible = visible;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200735 if (is_current)
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200736 {
737 may_toggle_cursor(term);
738 update_cursor(term, TRUE);
739 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200740
741 return 1;
742}
743
Bram Moolenaar21554412017-07-24 21:44:43 +0200744 static int
745handle_settermprop(
746 VTermProp prop,
747 VTermValue *value,
748 void *user)
749{
750 term_T *term = (term_T *)user;
751
752 switch (prop)
753 {
754 case VTERM_PROP_TITLE:
755 vim_free(term->tl_title);
756 term->tl_title = vim_strsave((char_u *)value->string);
757 vim_free(term->tl_status_text);
758 term->tl_status_text = NULL;
759 if (term == curbuf->b_term)
760 maketitle();
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200761 break;
762
763 case VTERM_PROP_CURSORVISIBLE:
764 term->tl_cursor_visible = value->boolean;
765 may_toggle_cursor(term);
766 out_flush();
767 break;
768
Bram Moolenaar21554412017-07-24 21:44:43 +0200769 default:
770 break;
771 }
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200772 /* Always return 1, otherwise vterm doesn't store the value internally. */
773 return 1;
Bram Moolenaar21554412017-07-24 21:44:43 +0200774}
775
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200776/*
777 * The job running in the terminal resized the terminal.
778 */
779 static int
780handle_resize(int rows, int cols, void *user)
781{
782 term_T *term = (term_T *)user;
783 win_T *wp;
784
785 term->tl_rows = rows;
786 term->tl_cols = cols;
787 FOR_ALL_WINDOWS(wp)
788 {
789 if (wp->w_buffer == term->tl_buffer)
790 {
791 win_setheight_win(rows, wp);
792 win_setwidth_win(cols, wp);
793 }
794 }
795
796 redraw_buf_later(term->tl_buffer, NOT_VALID);
797 return 1;
798}
799
Bram Moolenaard85f2712017-07-28 21:51:57 +0200800/*
801 * Handle a line that is pushed off the top of the screen.
802 */
803 static int
804handle_pushline(int cols, const VTermScreenCell *cells, void *user)
805{
806 term_T *term = (term_T *)user;
807
808 /* TODO: Limit the number of lines that are stored. */
809 /* TODO: put the text in the buffer. */
810 if (ga_grow(&term->tl_scrollback, 1) == OK)
811 {
Bram Moolenaar696d00f2017-07-29 14:52:43 +0200812 VTermScreenCell *p = NULL;
813 int len = 0;
814 int i;
815 sb_line_T *line;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200816
817 /* do not store empty cells at the end */
818 for (i = 0; i < cols; ++i)
819 if (cells[i].chars[0] != 0)
820 len = i + 1;
821
Bram Moolenaar696d00f2017-07-29 14:52:43 +0200822 if (len > 0)
823 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200824 if (p != NULL)
Bram Moolenaard85f2712017-07-28 21:51:57 +0200825 mch_memmove(p, cells, sizeof(VTermScreenCell) * len);
Bram Moolenaar696d00f2017-07-29 14:52:43 +0200826
827 line = (sb_line_T *)term->tl_scrollback.ga_data
828 + term->tl_scrollback.ga_len;
829 line->sb_cols = len;
830 line->sb_cells = p;
831 ++term->tl_scrollback.ga_len;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200832 ++term->tl_scrollback_scrolled;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200833 }
834 return 0; /* ignored */
835}
836
837/*
838 * Fill the buffer with the scrollback lines and current lines of the terminal.
839 * Called after the job has ended.
840 */
841 static void
842move_scrollback_to_buffer(term_T *term)
843{
844 linenr_T lnum;
845 garray_T ga;
846 int c;
847 int col;
848 int i;
849 win_T *wp;
850 int len;
851 int lines_skipped = 0;
852 VTermPos pos;
853 VTermScreenCell cell;
854 VTermScreenCell *p;
855 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
856
857 /* Append the the visible lines to the scrollback. */
858 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
859 {
860 len = 0;
861 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
862 if (vterm_screen_get_cell(screen, pos, &cell) != 0
863 && cell.chars[0] != NUL)
864 len = pos.col + 1;
865
Bram Moolenaar696d00f2017-07-29 14:52:43 +0200866 if (len == 0)
867 ++lines_skipped;
868 else
Bram Moolenaard85f2712017-07-28 21:51:57 +0200869 {
870 while (lines_skipped > 0)
871 {
872 /* Line was skipped, add an empty line. */
873 --lines_skipped;
874 if (ga_grow(&term->tl_scrollback, 1) == OK)
875 {
876 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
877 + term->tl_scrollback.ga_len;
878
879 line->sb_cols = 0;
880 line->sb_cells = NULL;
881 ++term->tl_scrollback.ga_len;
882 }
883 }
884
885 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
886 if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK)
887 {
888 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
889 + term->tl_scrollback.ga_len;
890
891 for (pos.col = 0; pos.col < len; ++pos.col)
892 {
893 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
894 vim_memset(p + pos.col, 0, sizeof(cell));
895 else
896 p[pos.col] = cell;
897 }
898 line->sb_cols = len;
899 line->sb_cells = p;
900 ++term->tl_scrollback.ga_len;
901 }
902 else
903 vim_free(p);
904 }
905 }
906
907 /* Add the text to the buffer. */
908 ga_init2(&ga, 1, 100);
909 for (lnum = 0; lnum < term->tl_scrollback.ga_len; ++lnum)
910 {
911 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
912
913 ga.ga_len = 0;
914 for (col = 0; col < line->sb_cols; ++col)
Bram Moolenaar696d00f2017-07-29 14:52:43 +0200915 {
916 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
917 goto failed;
918 for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i)
Bram Moolenaard85f2712017-07-28 21:51:57 +0200919 ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
920 (char_u *)ga.ga_data + ga.ga_len);
Bram Moolenaar696d00f2017-07-29 14:52:43 +0200921 }
922 if (ga_grow(&ga, 1) == FAIL)
923 goto failed;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200924 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
925 ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE);
926 }
927
928 /* Delete the empty line that was in the empty buffer. */
929 curbuf = term->tl_buffer;
930 ml_delete(lnum + 1, FALSE);
931 curbuf = curwin->w_buffer;
932
933failed:
934 ga_clear(&ga);
935
936 FOR_ALL_WINDOWS(wp)
937 {
938 if (wp->w_buffer == term->tl_buffer)
939 {
940 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
941 wp->w_cursor.col = 0;
942 wp->w_valid = 0;
943 }
944 }
945}
946
Bram Moolenaar21554412017-07-24 21:44:43 +0200947static VTermScreenCallbacks screen_callbacks = {
948 handle_damage, /* damage */
949 handle_moverect, /* moverect */
950 handle_movecursor, /* movecursor */
951 handle_settermprop, /* settermprop */
952 NULL, /* bell */
953 handle_resize, /* resize */
Bram Moolenaard85f2712017-07-28 21:51:57 +0200954 handle_pushline, /* sb_pushline */
Bram Moolenaar21554412017-07-24 21:44:43 +0200955 NULL /* sb_popline */
956};
957
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200958/*
Bram Moolenaard85f2712017-07-28 21:51:57 +0200959 * Called when a channel has been closed.
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200960 * If this was a channel for a terminal window then finish it up.
Bram Moolenaard85f2712017-07-28 21:51:57 +0200961 */
962 void
963term_channel_closed(channel_T *ch)
964{
965 term_T *term;
966 int did_one = FALSE;
967
968 for (term = first_term; term != NULL; term = term->tl_next)
969 if (term->tl_job == ch->ch_job)
970 {
971 vim_free(term->tl_title);
972 term->tl_title = NULL;
973 vim_free(term->tl_status_text);
974 term->tl_status_text = NULL;
975
976 /* move the lines into the buffer and free the vterm */
977 move_scrollback_to_buffer(term);
978 term_free_vterm(term);
979
980 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
981 did_one = TRUE;
982 }
983 if (did_one)
984 {
985 redraw_statuslines();
986
987 /* Need to break out of vgetc(). */
988 ins_char_typebuf(K_IGNORE);
989
990 if (curbuf->b_term != NULL)
991 {
992 if (curbuf->b_term->tl_job == ch->ch_job)
993 maketitle();
994 update_cursor(curbuf->b_term, TRUE);
995 }
996 }
997}
998
999/*
Bram Moolenaareeac6772017-07-23 15:48:37 +02001000 * Reverse engineer the RGB value into a cterm color index.
1001 * First color is 1. Return 0 if no match found.
1002 */
1003 static int
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001004color2index(VTermColor *color, int foreground)
Bram Moolenaareeac6772017-07-23 15:48:37 +02001005{
1006 int red = color->red;
1007 int blue = color->blue;
1008 int green = color->green;
1009
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001010 /* The argument for lookup_color() is for the color_names[] table. */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001011 if (red == 0)
1012 {
1013 if (green == 0)
1014 {
1015 if (blue == 0)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001016 return lookup_color(0, foreground) + 1; /* black */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001017 if (blue == 224)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001018 return lookup_color(1, foreground) + 1; /* dark blue */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001019 }
1020 else if (green == 224)
1021 {
1022 if (blue == 0)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001023 return lookup_color(2, foreground) + 1; /* dark green */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001024 if (blue == 224)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001025 return lookup_color(3, foreground) + 1; /* dark cyan */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001026 }
1027 }
1028 else if (red == 224)
1029 {
1030 if (green == 0)
1031 {
1032 if (blue == 0)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001033 return lookup_color(4, foreground) + 1; /* dark red */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001034 if (blue == 224)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001035 return lookup_color(5, foreground) + 1; /* dark magenta */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001036 }
1037 else if (green == 224)
1038 {
1039 if (blue == 0)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001040 return lookup_color(6, foreground) + 1; /* dark yellow / brown */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001041 if (blue == 224)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001042 return lookup_color(8, foreground) + 1; /* white / light grey */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001043 }
1044 }
1045 else if (red == 128)
1046 {
1047 if (green == 128 && blue == 128)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001048 return lookup_color(12, foreground) + 1; /* high intensity black / dark grey */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001049 }
1050 else if (red == 255)
1051 {
1052 if (green == 64)
1053 {
1054 if (blue == 64)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001055 return lookup_color(20, foreground) + 1; /* light red */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001056 if (blue == 255)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001057 return lookup_color(22, foreground) + 1; /* light magenta */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001058 }
1059 else if (green == 255)
1060 {
1061 if (blue == 64)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001062 return lookup_color(24, foreground) + 1; /* yellow */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001063 if (blue == 255)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001064 return lookup_color(26, foreground) + 1; /* white */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001065 }
1066 }
1067 else if (red == 64)
1068 {
1069 if (green == 64)
1070 {
1071 if (blue == 255)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001072 return lookup_color(14, foreground) + 1; /* light blue */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001073 }
1074 else if (green == 255)
1075 {
1076 if (blue == 64)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001077 return lookup_color(16, foreground) + 1; /* light green */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001078 if (blue == 255)
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001079 return lookup_color(18, foreground) + 1; /* light cyan */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001080 }
1081 }
1082 if (t_colors >= 256)
1083 {
1084 if (red == blue && red == green)
1085 {
1086 /* 24-color greyscale */
1087 static int cutoff[23] = {
1088 0x05, 0x10, 0x1B, 0x26, 0x31, 0x3C, 0x47, 0x52,
1089 0x5D, 0x68, 0x73, 0x7F, 0x8A, 0x95, 0xA0, 0xAB,
1090 0xB6, 0xC1, 0xCC, 0xD7, 0xE2, 0xED, 0xF9};
1091 int i;
1092
1093 for (i = 0; i < 23; ++i)
1094 if (red < cutoff[i])
1095 return i + 233;
1096 return 256;
1097 }
1098
1099 /* 216-color cube */
1100 return 17 + ((red + 25) / 0x33) * 36
1101 + ((green + 25) / 0x33) * 6
1102 + (blue + 25) / 0x33;
1103 }
1104 return 0;
1105}
1106
1107/*
1108 * Convert the attributes of a vterm cell into an attribute index.
1109 */
1110 static int
1111cell2attr(VTermScreenCell *cell)
1112{
1113 int attr = 0;
1114
1115 if (cell->attrs.bold)
1116 attr |= HL_BOLD;
1117 if (cell->attrs.underline)
1118 attr |= HL_UNDERLINE;
1119 if (cell->attrs.italic)
1120 attr |= HL_ITALIC;
1121 if (cell->attrs.strike)
1122 attr |= HL_STANDOUT;
1123 if (cell->attrs.reverse)
1124 attr |= HL_INVERSE;
Bram Moolenaareeac6772017-07-23 15:48:37 +02001125
1126#ifdef FEAT_GUI
1127 if (gui.in_use)
1128 {
Bram Moolenaar26af85d2017-07-23 16:45:10 +02001129 guicolor_T fg, bg;
1130
1131 fg = gui_mch_get_rgb_color(cell->fg.red, cell->fg.green, cell->fg.blue);
1132 bg = gui_mch_get_rgb_color(cell->bg.red, cell->bg.green, cell->bg.blue);
1133 return get_gui_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001134 }
1135 else
1136#endif
1137#ifdef FEAT_TERMGUICOLORS
1138 if (p_tgc)
1139 {
Bram Moolenaar065f41c2017-07-23 18:07:56 +02001140 guicolor_T fg, bg;
1141
1142 fg = gui_get_rgb_color_cmn(cell->fg.red, cell->fg.green, cell->fg.blue);
1143 bg = gui_get_rgb_color_cmn(cell->bg.red, cell->bg.green, cell->bg.blue);
1144
1145 return get_tgc_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001146 }
Bram Moolenaar065f41c2017-07-23 18:07:56 +02001147 else
Bram Moolenaareeac6772017-07-23 15:48:37 +02001148#endif
1149 {
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001150 return get_cterm_attr_idx(attr, color2index(&cell->fg, TRUE),
1151 color2index(&cell->bg, FALSE));
Bram Moolenaareeac6772017-07-23 15:48:37 +02001152 }
1153 return 0;
1154}
1155
1156/*
Bram Moolenaard85f2712017-07-28 21:51:57 +02001157 * Called to update the window that contains a terminal.
1158 * Returns FAIL when there is no terminal running in this window.
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001159 */
Bram Moolenaard85f2712017-07-28 21:51:57 +02001160 int
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001161term_update_window(win_T *wp)
Bram Moolenaar938783d2017-07-16 20:13:26 +02001162{
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001163 term_T *term = wp->w_buffer->b_term;
Bram Moolenaard85f2712017-07-28 21:51:57 +02001164 VTerm *vterm;
1165 VTermScreen *screen;
1166 VTermState *state;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001167 VTermPos pos;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001168
Bram Moolenaard85f2712017-07-28 21:51:57 +02001169 if (term == NULL || term->tl_vterm == NULL)
1170 return FAIL;
1171 vterm = term->tl_vterm;
1172 screen = vterm_obtain_screen(vterm);
1173 state = vterm_obtain_state(vterm);
1174
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001175 /*
1176 * If the window was resized a redraw will be triggered and we get here.
1177 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
1178 */
1179 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
1180 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001181 {
Bram Moolenaar96ad8c92017-07-28 14:17:34 +02001182 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
1183 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
1184 win_T *twp;
1185
1186 FOR_ALL_WINDOWS(twp)
1187 {
1188 /* When more than one window shows the same terminal, use the
1189 * smallest size. */
1190 if (twp->w_buffer == term->tl_buffer)
1191 {
1192 if (!term->tl_rows_fixed && rows > twp->w_height)
1193 rows = twp->w_height;
1194 if (!term->tl_cols_fixed && cols > twp->w_width)
1195 cols = twp->w_width;
1196 }
1197 }
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001198
1199 vterm_set_size(vterm, rows, cols);
1200 ch_logn(term->tl_job->jv_channel, "Resizing terminal to %d lines",
1201 rows);
Bram Moolenaar43da3e32017-07-23 17:27:54 +02001202 term_report_winsize(term, rows, cols);
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001203 }
Bram Moolenaar58556cd2017-07-20 23:04:46 +02001204
1205 /* The cursor may have been moved when resizing. */
1206 vterm_state_get_cursorpos(state, &pos);
1207 position_cursor(wp, &pos);
1208
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001209 /* TODO: Only redraw what changed. */
1210 for (pos.row = 0; pos.row < wp->w_height; ++pos.row)
Bram Moolenaar938783d2017-07-16 20:13:26 +02001211 {
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001212 int off = screen_get_current_line_off();
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001213 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar938783d2017-07-16 20:13:26 +02001214
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001215 if (pos.row < term->tl_rows)
1216 {
1217 for (pos.col = 0; pos.col < max_col; )
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001218 {
1219 VTermScreenCell cell;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001220 int c;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001221
Bram Moolenaareeac6772017-07-23 15:48:37 +02001222 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1223 vim_memset(&cell, 0, sizeof(cell));
1224
1225 /* TODO: composing chars */
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001226 c = cell.chars[0];
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001227 if (c == NUL)
1228 {
1229 ScreenLines[off] = ' ';
Bram Moolenaar8a773062017-07-24 22:29:21 +02001230#if defined(FEAT_MBYTE)
1231 if (enc_utf8)
1232 ScreenLinesUC[off] = NUL;
1233#endif
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001234 }
1235 else
1236 {
1237#if defined(FEAT_MBYTE)
1238 if (enc_utf8 && c >= 0x80)
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001239 {
1240 ScreenLines[off] = ' ';
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001241 ScreenLinesUC[off] = c;
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001242 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001243 else
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001244 {
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001245 ScreenLines[off] = c;
Bram Moolenaar8a773062017-07-24 22:29:21 +02001246 if (enc_utf8)
1247 ScreenLinesUC[off] = NUL;
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001248 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001249#else
1250 ScreenLines[off] = c;
1251#endif
1252 }
Bram Moolenaareeac6772017-07-23 15:48:37 +02001253 ScreenAttrs[off] = cell2attr(&cell);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001254
1255 ++pos.col;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001256 ++off;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001257 if (cell.width == 2)
1258 {
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001259 ScreenLines[off] = NUL;
Bram Moolenaar8a773062017-07-24 22:29:21 +02001260#if defined(FEAT_MBYTE)
1261 if (enc_utf8)
1262 ScreenLinesUC[off] = NUL;
1263#endif
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001264 ++pos.col;
1265 ++off;
1266 }
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001267 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001268 }
Bram Moolenaare825d8b2017-07-19 23:20:19 +02001269 else
1270 pos.col = 0;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001271
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001272 screen_line(wp->w_winrow + pos.row, wp->w_wincol,
1273 pos.col, wp->w_width, FALSE);
Bram Moolenaar938783d2017-07-16 20:13:26 +02001274 }
Bram Moolenaard85f2712017-07-28 21:51:57 +02001275
1276 return OK;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001277}
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001278
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001279/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001280 * Return TRUE if "wp" is a terminal window where the job has finished.
1281 */
1282 int
1283term_is_finished(buf_T *buf)
1284{
1285 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
1286}
1287
1288/*
1289 * The current buffer is going to be changed. If there is terminal
1290 * highlighting remove it now.
1291 */
1292 void
1293term_change_in_curbuf(void)
1294{
1295 term_T *term = curbuf->b_term;
1296
1297 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
1298 {
1299 free_scrollback(term);
1300 redraw_buf_later(term->tl_buffer, NOT_VALID);
1301 }
1302}
1303
1304/*
1305 * Get the screen attribute for a position in the buffer.
1306 */
1307 int
1308term_get_attr(buf_T *buf, linenr_T lnum, int col)
1309{
1310 term_T *term = buf->b_term;
1311 sb_line_T *line;
1312
Bram Moolenaar70229f92017-07-29 16:01:53 +02001313 if (lnum > term->tl_scrollback.ga_len)
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001314 return 0;
1315 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
1316 if (col >= line->sb_cols)
1317 return 0;
1318 return cell2attr(line->sb_cells + col);
1319}
1320
1321/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001322 * Set job options common for Unix and MS-Windows.
1323 */
1324 static void
1325setup_job_options(jobopt_T *opt, int rows, int cols)
1326{
1327 clear_job_options(opt);
1328 opt->jo_mode = MODE_RAW;
1329 opt->jo_out_mode = MODE_RAW;
1330 opt->jo_err_mode = MODE_RAW;
1331 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001332
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001333 opt->jo_io[PART_OUT] = JIO_BUFFER;
1334 opt->jo_io[PART_ERR] = JIO_BUFFER;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001335 opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
1336
1337 opt->jo_modifiable[PART_OUT] = 0;
1338 opt->jo_modifiable[PART_ERR] = 0;
1339 opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
1340
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001341 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
1342 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
Bram Moolenaar5a1feb82017-07-22 18:04:08 +02001343 opt->jo_pty = TRUE;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001344 opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
1345
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001346 opt->jo_term_rows = rows;
1347 opt->jo_term_cols = cols;
1348}
1349
1350/*
1351 * Create a new vterm and initialize it.
1352 */
1353 static void
1354create_vterm(term_T *term, int rows, int cols)
1355{
1356 VTerm *vterm;
1357 VTermScreen *screen;
1358
1359 vterm = vterm_new(rows, cols);
1360 term->tl_vterm = vterm;
1361 screen = vterm_obtain_screen(vterm);
1362 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
1363 /* TODO: depends on 'encoding'. */
1364 vterm_set_utf8(vterm, 1);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001365
1366 /* Vterm uses a default black background. Set it to white when
1367 * 'background' is "light". */
1368 if (*p_bg == 'l')
1369 {
1370 VTermColor fg, bg;
1371
1372 fg.red = fg.green = fg.blue = 0;
1373 bg.red = bg.green = bg.blue = 255;
1374 vterm_state_set_default_colors(vterm_obtain_state(vterm), &fg, &bg);
1375 }
1376
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001377 /* Required to initialize most things. */
1378 vterm_screen_reset(screen, 1 /* hard */);
1379}
1380
Bram Moolenaar21554412017-07-24 21:44:43 +02001381/*
1382 * Return the text to show for the buffer name and status.
1383 */
1384 char_u *
1385term_get_status_text(term_T *term)
1386{
1387 if (term->tl_status_text == NULL)
1388 {
1389 char_u *txt;
1390 size_t len;
1391
1392 if (term->tl_title != NULL)
1393 txt = term->tl_title;
1394 else if (term_job_running(term))
1395 txt = (char_u *)_("running");
1396 else
1397 txt = (char_u *)_("finished");
1398 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
Bram Moolenaara1b5b092017-07-26 21:29:34 +02001399 term->tl_status_text = alloc((int)len);
Bram Moolenaar21554412017-07-24 21:44:43 +02001400 if (term->tl_status_text != NULL)
1401 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
1402 term->tl_buffer->b_fname, txt);
1403 }
1404 return term->tl_status_text;
1405}
1406
Bram Moolenaarf86eea92017-07-28 13:51:30 +02001407/*
1408 * Mark references in jobs of terminals.
1409 */
1410 int
1411set_ref_in_term(int copyID)
1412{
1413 int abort = FALSE;
1414 term_T *term;
1415 typval_T tv;
1416
1417 for (term = first_term; term != NULL; term = term->tl_next)
1418 if (term->tl_job != NULL)
1419 {
1420 tv.v_type = VAR_JOB;
1421 tv.vval.v_job = term->tl_job;
1422 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
1423 }
1424 return abort;
1425}
1426
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001427/*
1428 * "term_getattr(attr, name)" function
1429 */
1430 void
1431f_term_getattr(typval_T *argvars, typval_T *rettv)
1432{
1433 int attr;
1434 size_t i;
1435 char_u *name;
1436
1437 static struct {
1438 char *name;
1439 int attr;
1440 } attrs[] = {
1441 {"bold", HL_BOLD},
1442 {"italic", HL_ITALIC},
1443 {"underline", HL_UNDERLINE},
1444 {"strike", HL_STANDOUT},
1445 {"reverse", HL_INVERSE},
1446 };
1447
1448 attr = get_tv_number(&argvars[0]);
1449 name = get_tv_string_chk(&argvars[1]);
1450 if (name == NULL)
1451 return;
1452
1453 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
1454 if (STRCMP(name, attrs[i].name) == 0)
1455 {
1456 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
1457 break;
1458 }
1459}
1460
1461/*
1462 * Get the buffer from the first argument in "argvars".
1463 * Returns NULL when the buffer is not for a terminal window.
1464 */
1465 static buf_T *
1466term_get_buf(typval_T *argvars)
1467{
1468 buf_T *buf;
1469
1470 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
1471 ++emsg_off;
1472 buf = get_buf_tv(&argvars[0], FALSE);
1473 --emsg_off;
1474 if (buf->b_term == NULL)
1475 return NULL;
1476 return buf;
1477}
1478
1479/*
1480 * "term_getjob(buf)" function
1481 */
1482 void
1483f_term_getjob(typval_T *argvars, typval_T *rettv)
1484{
1485 buf_T *buf = term_get_buf(argvars);
1486
1487 rettv->v_type = VAR_JOB;
1488 rettv->vval.v_job = NULL;
1489 if (buf == NULL)
1490 return;
1491
1492 rettv->vval.v_job = buf->b_term->tl_job;
1493 if (rettv->vval.v_job != NULL)
1494 ++rettv->vval.v_job->jv_refcount;
1495}
1496
1497/*
1498 * "term_getline(buf, row)" function
1499 */
1500 void
1501f_term_getline(typval_T *argvars, typval_T *rettv)
1502{
1503 buf_T *buf = term_get_buf(argvars);
1504 term_T *term;
1505 int row;
1506
1507 rettv->v_type = VAR_STRING;
1508 if (buf == NULL)
1509 return;
1510 term = buf->b_term;
1511 row = (int)get_tv_number(&argvars[1]);
1512
1513 if (term->tl_vterm == NULL)
1514 {
1515 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
1516
1517 /* vterm is finished, get the text from the buffer */
1518 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
1519 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
1520 }
1521 else
1522 {
1523 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
1524 VTermRect rect;
1525 int len;
1526 char_u *p;
1527
1528 len = term->tl_cols * MB_MAXBYTES + 1;
1529 p = alloc(len);
1530 if (p == NULL)
1531 return;
1532 rettv->vval.v_string = p;
1533
1534 rect.start_col = 0;
1535 rect.end_col = term->tl_cols;
1536 rect.start_row = row;
1537 rect.end_row = row + 1;
1538 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
1539 }
1540}
1541
1542/*
1543 * "term_getsize(buf)" function
1544 */
1545 void
1546f_term_getsize(typval_T *argvars, typval_T *rettv)
1547{
1548 buf_T *buf = term_get_buf(argvars);
1549 list_T *l;
1550
1551 if (rettv_list_alloc(rettv) == FAIL)
1552 return;
1553 if (buf == NULL)
1554 return;
1555
1556 l = rettv->vval.v_list;
1557 list_append_number(l, buf->b_term->tl_rows);
1558 list_append_number(l, buf->b_term->tl_cols);
1559}
1560
1561/*
1562 * "term_list()" function
1563 */
1564 void
1565f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
1566{
1567 term_T *tp;
1568 list_T *l;
1569
1570 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
1571 return;
1572
1573 l = rettv->vval.v_list;
1574 for (tp = first_term; tp != NULL; tp = tp->tl_next)
1575 if (tp != NULL && tp->tl_buffer != NULL)
1576 if (list_append_number(l,
1577 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
1578 return;
1579}
1580
1581/*
1582 * "term_scrape(buf, row)" function
1583 */
1584 void
1585f_term_scrape(typval_T *argvars, typval_T *rettv)
1586{
1587 buf_T *buf = term_get_buf(argvars);
1588 VTermScreen *screen = NULL;
1589 VTermPos pos;
1590 list_T *l;
1591 term_T *term;
1592
1593 if (rettv_list_alloc(rettv) == FAIL)
1594 return;
1595 if (buf == NULL)
1596 return;
1597 term = buf->b_term;
1598 if (term->tl_vterm != NULL)
1599 screen = vterm_obtain_screen(term->tl_vterm);
1600
1601 l = rettv->vval.v_list;
1602 pos.row = (int)get_tv_number(&argvars[1]);
1603 for (pos.col = 0; pos.col < term->tl_cols; )
1604 {
1605 dict_T *dcell;
1606 VTermScreenCell cell;
1607 char_u rgb[8];
1608 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
1609 int off = 0;
1610 int i;
1611
1612 if (screen == NULL)
1613 {
1614 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
1615 sb_line_T *line;
1616
1617 /* vterm has finished, get the cell from scrollback */
1618 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
1619 break;
1620 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
1621 if (pos.col >= line->sb_cols)
1622 break;
1623 cell = line->sb_cells[pos.col];
1624 }
1625 else if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1626 break;
1627 dcell = dict_alloc();
1628 list_append_dict(l, dcell);
1629
1630 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
1631 {
1632 if (cell.chars[i] == 0)
1633 break;
1634 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
1635 }
1636 mbs[off] = NUL;
1637 dict_add_nr_str(dcell, "chars", 0, mbs);
1638
1639 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
1640 cell.fg.red, cell.fg.green, cell.fg.blue);
1641 dict_add_nr_str(dcell, "fg", 0, rgb);
1642 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
1643 cell.bg.red, cell.bg.green, cell.bg.blue);
1644 dict_add_nr_str(dcell, "bg", 0, rgb);
1645
1646 dict_add_nr_str(dcell, "attr", cell2attr(&cell), NULL);
1647 dict_add_nr_str(dcell, "width", cell.width, NULL);
1648
1649 ++pos.col;
1650 if (cell.width == 2)
1651 ++pos.col;
1652 }
1653}
1654
1655/*
1656 * "term_sendkeys(buf, keys)" function
1657 */
1658 void
1659f_term_sendkeys(typval_T *argvars, typval_T *rettv)
1660{
1661 buf_T *buf = term_get_buf(argvars);
1662 char_u *msg;
1663 term_T *term;
1664
1665 rettv->v_type = VAR_UNKNOWN;
1666 if (buf == NULL)
1667 return;
1668
1669 msg = get_tv_string_chk(&argvars[1]);
1670 if (msg == NULL)
1671 return;
1672 term = buf->b_term;
1673 if (term->tl_vterm == NULL)
1674 return;
1675
1676 while (*msg != NUL)
1677 {
1678 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
1679 msg += MB_PTR2LEN(msg);
1680 }
1681
1682 /* TODO: only update once in a while. */
1683 update_screen(0);
1684 if (buf == curbuf)
1685 update_cursor(term, TRUE);
1686}
1687
1688/*
1689 * "term_start(command, options)" function
1690 */
1691 void
1692f_term_start(typval_T *argvars, typval_T *rettv)
1693{
1694 char_u *cmd = get_tv_string_chk(&argvars[0]);
1695 exarg_T ea;
1696
1697 if (cmd == NULL)
1698 return;
1699 ea.arg = cmd;
1700 ex_terminal(&ea);
1701
1702 if (curbuf->b_term != NULL)
1703 rettv->vval.v_number = curbuf->b_fnum;
1704}
1705
1706/*
1707 * "term_wait" function
1708 */
1709 void
1710f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
1711{
1712 buf_T *buf = term_get_buf(argvars);
1713
1714 if (buf == NULL)
1715 return;
1716
1717 /* Get the job status, this will detect a job that finished. */
1718 if (buf->b_term->tl_job != NULL)
1719 (void)job_status(buf->b_term->tl_job);
1720
1721 /* Check for any pending channel I/O. */
1722 vpeekc_any();
1723 ui_delay(10L, FALSE);
1724
1725 /* Flushing messages on channels is hopefully sufficient.
1726 * TODO: is there a better way? */
1727 parse_queued_messages();
1728}
1729
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001730# ifdef WIN3264
1731
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001732/**************************************
1733 * 2. MS-Windows implementation.
1734 */
1735
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001736#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
1737#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
1738
Bram Moolenaar8a773062017-07-24 22:29:21 +02001739void* (*winpty_config_new)(UINT64, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001740void* (*winpty_open)(void*, void*);
Bram Moolenaar8a773062017-07-24 22:29:21 +02001741void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001742BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
1743void (*winpty_config_set_initial_size)(void*, int, int);
1744LPCWSTR (*winpty_conin_name)(void*);
1745LPCWSTR (*winpty_conout_name)(void*);
1746LPCWSTR (*winpty_conerr_name)(void*);
1747void (*winpty_free)(void*);
1748void (*winpty_config_free)(void*);
1749void (*winpty_spawn_config_free)(void*);
1750void (*winpty_error_free)(void*);
1751LPCWSTR (*winpty_error_msg)(void*);
Bram Moolenaar43da3e32017-07-23 17:27:54 +02001752BOOL (*winpty_set_size)(void*, int, int, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001753
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001754#define WINPTY_DLL "winpty.dll"
1755
1756static HINSTANCE hWinPtyDLL = NULL;
1757
1758 int
1759dyn_winpty_init(void)
1760{
1761 int i;
1762 static struct
1763 {
1764 char *name;
1765 FARPROC *ptr;
1766 } winpty_entry[] =
1767 {
1768 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
1769 {"winpty_config_free", (FARPROC*)&winpty_config_free},
1770 {"winpty_config_new", (FARPROC*)&winpty_config_new},
1771 {"winpty_config_set_initial_size", (FARPROC*)&winpty_config_set_initial_size},
1772 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
1773 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
1774 {"winpty_error_free", (FARPROC*)&winpty_error_free},
1775 {"winpty_free", (FARPROC*)&winpty_free},
1776 {"winpty_open", (FARPROC*)&winpty_open},
1777 {"winpty_spawn", (FARPROC*)&winpty_spawn},
1778 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
1779 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
1780 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
Bram Moolenaar43da3e32017-07-23 17:27:54 +02001781 {"winpty_set_size", (FARPROC*)&winpty_set_size},
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001782 {NULL, NULL}
1783 };
1784
1785 /* No need to initialize twice. */
1786 if (hWinPtyDLL)
1787 return 1;
1788 /* Load winpty.dll */
1789 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
1790 if (!hWinPtyDLL)
1791 {
1792 EMSG2(_(e_loadlib), WINPTY_DLL);
1793 return 0;
1794 }
1795 for (i = 0; winpty_entry[i].name != NULL
1796 && winpty_entry[i].ptr != NULL; ++i)
1797 {
1798 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
1799 winpty_entry[i].name)) == NULL)
1800 {
1801 EMSG2(_(e_loadfunc), winpty_entry[i].name);
1802 return 0;
1803 }
1804 }
1805
1806 return 1;
1807}
1808
1809/*
1810 * Create a new terminal of "rows" by "cols" cells.
1811 * Store a reference in "term".
1812 * Return OK or FAIL.
1813 */
1814 static int
1815term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
1816{
Bram Moolenaarab6eec32017-07-27 21:46:43 +02001817 WCHAR *p;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001818 channel_T *channel = NULL;
1819 job_T *job = NULL;
1820 jobopt_T opt;
1821 DWORD error;
1822 HANDLE jo = NULL, child_process_handle, child_thread_handle;
1823 void *winpty_err;
Bram Moolenaarab6eec32017-07-27 21:46:43 +02001824 void *spawn_config = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001825
1826 if (!dyn_winpty_init())
1827 return FAIL;
1828
Bram Moolenaarab6eec32017-07-27 21:46:43 +02001829 p = enc_to_utf16(cmd, NULL);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001830 if (p == NULL)
1831 return FAIL;
1832
1833 job = job_alloc();
1834 if (job == NULL)
1835 goto failed;
1836
1837 channel = add_channel();
1838 if (channel == NULL)
1839 goto failed;
1840
1841 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
1842 if (term->tl_winpty_config == NULL)
1843 goto failed;
1844
1845 winpty_config_set_initial_size(term->tl_winpty_config, cols, rows);
1846 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
1847 if (term->tl_winpty == NULL)
1848 goto failed;
1849
1850 spawn_config = winpty_spawn_config_new(
1851 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
1852 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
1853 NULL,
1854 p,
1855 NULL,
1856 NULL,
1857 &winpty_err);
1858 if (spawn_config == NULL)
1859 goto failed;
1860
1861 channel = add_channel();
1862 if (channel == NULL)
1863 goto failed;
1864
1865 job = job_alloc();
1866 if (job == NULL)
1867 goto failed;
1868
1869 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
1870 &child_thread_handle, &error, &winpty_err))
1871 goto failed;
1872
1873 channel_set_pipes(channel,
1874 (sock_T) CreateFileW(
1875 winpty_conin_name(term->tl_winpty),
1876 GENERIC_WRITE, 0, NULL,
1877 OPEN_EXISTING, 0, NULL),
1878 (sock_T) CreateFileW(
1879 winpty_conout_name(term->tl_winpty),
1880 GENERIC_READ, 0, NULL,
1881 OPEN_EXISTING, 0, NULL),
1882 (sock_T) CreateFileW(
1883 winpty_conerr_name(term->tl_winpty),
1884 GENERIC_READ, 0, NULL,
1885 OPEN_EXISTING, 0, NULL));
1886
1887 jo = CreateJobObject(NULL, NULL);
1888 if (jo == NULL)
1889 goto failed;
1890
1891 if (!AssignProcessToJobObject(jo, child_process_handle))
Bram Moolenaarab6eec32017-07-27 21:46:43 +02001892 {
1893 /* Failed, switch the way to terminate process with TerminateProcess. */
1894 CloseHandle(jo);
1895 jo = NULL;
1896 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001897
1898 winpty_spawn_config_free(spawn_config);
Bram Moolenaarab6eec32017-07-27 21:46:43 +02001899 vim_free(p);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001900
1901 create_vterm(term, rows, cols);
1902
1903 setup_job_options(&opt, rows, cols);
1904 channel_set_job(channel, job, &opt);
1905
1906 job->jv_channel = channel;
1907 job->jv_proc_info.hProcess = child_process_handle;
1908 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
1909 job->jv_job_object = jo;
1910 job->jv_status = JOB_STARTED;
Bram Moolenaar0e83f022017-07-27 22:07:35 +02001911 ++job->jv_refcount;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001912 term->tl_job = job;
1913
1914 return OK;
1915
1916failed:
Bram Moolenaarab6eec32017-07-27 21:46:43 +02001917 if (spawn_config != NULL)
1918 winpty_spawn_config_free(spawn_config);
1919 vim_free(p);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001920 if (channel != NULL)
1921 channel_clear(channel);
1922 if (job != NULL)
Bram Moolenaarcdeae992017-07-23 17:22:35 +02001923 {
1924 job->jv_channel = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001925 job_cleanup(job);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02001926 }
1927 term->tl_job = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001928 if (jo != NULL)
1929 CloseHandle(jo);
1930 if (term->tl_winpty != NULL)
1931 winpty_free(term->tl_winpty);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02001932 term->tl_winpty = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001933 if (term->tl_winpty_config != NULL)
1934 winpty_config_free(term->tl_winpty_config);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02001935 term->tl_winpty_config = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001936 if (winpty_err != NULL)
1937 {
1938 char_u *msg = utf16_to_enc(
1939 (short_u *)winpty_error_msg(winpty_err), NULL);
1940
1941 EMSG(msg);
1942 winpty_error_free(winpty_err);
1943 }
1944 return FAIL;
1945}
1946
1947/*
1948 * Free the terminal emulator part of "term".
1949 */
1950 static void
Bram Moolenaard85f2712017-07-28 21:51:57 +02001951term_free_vterm(term_T *term)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001952{
Bram Moolenaarcdeae992017-07-23 17:22:35 +02001953 if (term->tl_winpty != NULL)
1954 winpty_free(term->tl_winpty);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001955 term->tl_winpty = NULL;
Bram Moolenaarcdeae992017-07-23 17:22:35 +02001956 if (term->tl_winpty_config != NULL)
1957 winpty_config_free(term->tl_winpty_config);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001958 term->tl_winpty_config = NULL;
Bram Moolenaarcdeae992017-07-23 17:22:35 +02001959 if (term->tl_vterm != NULL)
1960 vterm_free(term->tl_vterm);
Bram Moolenaardcbfa332017-07-28 23:16:13 +02001961 term->tl_vterm = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001962}
1963
Bram Moolenaar43da3e32017-07-23 17:27:54 +02001964/*
1965 * Request size to terminal.
1966 */
1967 static void
1968term_report_winsize(term_T *term, int rows, int cols)
1969{
1970 winpty_set_size(term->tl_winpty, cols, rows, NULL);
1971}
1972
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001973# else
1974
1975/**************************************
1976 * 3. Unix-like implementation.
1977 */
1978
1979/*
1980 * Create a new terminal of "rows" by "cols" cells.
1981 * Start job for "cmd".
1982 * Store the pointers in "term".
1983 * Return OK or FAIL.
1984 */
1985 static int
1986term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
1987{
1988 typval_T argvars[2];
1989 jobopt_T opt;
1990
1991 create_vterm(term, rows, cols);
1992
1993 argvars[0].v_type = VAR_STRING;
1994 argvars[0].vval.v_string = cmd;
1995 setup_job_options(&opt, rows, cols);
1996 term->tl_job = job_start(argvars, &opt);
Bram Moolenaar0e83f022017-07-27 22:07:35 +02001997 if (term->tl_job != NULL)
1998 ++term->tl_job->jv_refcount;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001999
Bram Moolenaar61a66052017-07-22 18:39:00 +02002000 return term->tl_job != NULL
2001 && term->tl_job->jv_channel != NULL
2002 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002003}
2004
2005/*
2006 * Free the terminal emulator part of "term".
2007 */
2008 static void
Bram Moolenaard85f2712017-07-28 21:51:57 +02002009term_free_vterm(term_T *term)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002010{
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002011 if (term->tl_vterm != NULL)
2012 vterm_free(term->tl_vterm);
Bram Moolenaard85f2712017-07-28 21:51:57 +02002013 term->tl_vterm = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002014}
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002015
2016/*
2017 * Request size to terminal.
2018 */
2019 static void
2020term_report_winsize(term_T *term, int rows, int cols)
2021{
2022 /* Use an ioctl() to report the new window size to the job. */
2023 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
2024 {
2025 int fd = -1;
2026 int part;
2027
2028 for (part = PART_OUT; part < PART_COUNT; ++part)
2029 {
2030 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
2031 if (isatty(fd))
2032 break;
2033 }
2034 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
2035 mch_stop_job(term->tl_job, (char_u *)"winch");
2036 }
2037}
2038
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002039# endif
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02002040
Bram Moolenaare4f25e42017-07-07 11:54:15 +02002041#endif /* FEAT_TERMINAL */