blob: 1f235872483de8bc2a2b22f2a140e9720378578b [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 Moolenaar12d853f2017-08-01 18:04:04 +020039 * - Use "." for current line instead of optional argument.
Bram Moolenaar392d1bf2017-07-31 21:18:58 +020040 * - make row and cols one-based instead of zero-based in term_ functions.
41 * - Add StatusLineTerm highlighting
Bram Moolenaar423802d2017-07-30 16:52:24 +020042 * - in bash mouse clicks are inserting characters.
43 * - mouse scroll: when over other window, scroll that window.
Bram Moolenaard85f2712017-07-28 21:51:57 +020044 * - For the scrollback buffer store lines in the buffer, only attributes in
45 * tl_scrollback.
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020046 * - When the job ends:
Bram Moolenaar21554412017-07-24 21:44:43 +020047 * - Need an option or argument to drop the window+buffer right away, to be
Bram Moolenaar423802d2017-07-30 16:52:24 +020048 * used for a shell or Vim. 'termfinish'; "close", "open" (open window when
49 * job finishes).
50 * - add option values to the command:
51 * :term <24x80> <close> vim notes.txt
Bram Moolenaar12d93ee2017-07-30 19:02:02 +020052 * - support different cursor shapes, colors and attributes
53 * - make term_getcursor() return type (none/block/bar/underline) and
54 * attributes (color, blink, etc.)
Bram Moolenaard85f2712017-07-28 21:51:57 +020055 * - To set BS correctly, check get_stty(); Pass the fd of the pty.
Bram Moolenaar423802d2017-07-30 16:52:24 +020056 * - do not store terminal window in viminfo. Or prefix term:// ?
Bram Moolenaar21554412017-07-24 21:44:43 +020057 * - add a character in :ls output
Bram Moolenaar43c007f2017-07-30 17:45:37 +020058 * - add 't' to mode()
Bram Moolenaar12d853f2017-08-01 18:04:04 +020059 * - When making a change after the job has ended, make the buffer a normal
60 * buffer; needs to be written.
Bram Moolenaar938783d2017-07-16 20:13:26 +020061 * - when closing window and job has not ended, make terminal hidden?
Bram Moolenaard85f2712017-07-28 21:51:57 +020062 * - when closing window and job has ended, make buffer hidden?
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +020063 * - don't allow exiting Vim when a terminal is still running a job
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020064 * - use win_del_lines() to make scroll-up efficient.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020065 * - add test for giving error for invalid 'termsize' value.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020066 * - support minimal size when 'termsize' is "rows*cols".
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020067 * - support minimal size when 'termsize' is empty?
Bram Moolenaar5a1feb82017-07-22 18:04:08 +020068 * - implement "term" for job_start(): more job options when starting a
69 * terminal.
Bram Moolenaar423802d2017-07-30 16:52:24 +020070 * - if the job in the terminal does not support the mouse, we can use the
71 * mouse in the Terminal window for copy/paste.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020072 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
73 * conversions.
Bram Moolenaarc9456ce2017-07-30 21:46:04 +020074 * - update ":help function-list" for terminal functions.
Bram Moolenaardbe948d2017-07-23 22:50:51 +020075 * - In the GUI use a terminal emulator for :!cmd.
Bram Moolenaar12d853f2017-08-01 18:04:04 +020076 * - Copy text in the vterm to the Vim buffer once in a while, so that
77 * completion works.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020078 */
79
80#include "vim.h"
81
Bram Moolenaarc6df10e2017-07-29 20:15:08 +020082#if defined(FEAT_TERMINAL) || defined(PROTO)
Bram Moolenaare4f25e42017-07-07 11:54:15 +020083
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020084#ifdef WIN3264
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020085# define MIN(x,y) (x < y ? x : y)
86# define MAX(x,y) (x > y ? x : y)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020087#endif
Bram Moolenaare4f25e42017-07-07 11:54:15 +020088
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020089#include "libvterm/include/vterm.h"
90
Bram Moolenaard85f2712017-07-28 21:51:57 +020091typedef struct sb_line_S {
92 int sb_cols; /* can differ per line */
93 VTermScreenCell *sb_cells; /* allocated */
94} sb_line_T;
95
Bram Moolenaare4f25e42017-07-07 11:54:15 +020096/* typedef term_T in structs.h */
97struct terminal_S {
98 term_T *tl_next;
99
Bram Moolenaar423802d2017-07-30 16:52:24 +0200100 VTerm *tl_vterm;
101 job_T *tl_job;
102 buf_T *tl_buffer;
103
104 int tl_terminal_mode;
105 int tl_channel_closed;
106
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200107#ifdef WIN3264
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200108 void *tl_winpty_config;
109 void *tl_winpty;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200110#endif
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200111
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200112 /* last known vterm size */
113 int tl_rows;
114 int tl_cols;
115 /* vterm size does not follow window size */
116 int tl_rows_fixed;
117 int tl_cols_fixed;
118
Bram Moolenaar21554412017-07-24 21:44:43 +0200119 char_u *tl_title; /* NULL or allocated */
120 char_u *tl_status_text; /* NULL or allocated */
121
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200122 /* Range of screen rows to update. Zero based. */
123 int tl_dirty_row_start; /* -1 if nothing dirty */
124 int tl_dirty_row_end; /* row below last one to update */
125
Bram Moolenaard85f2712017-07-28 21:51:57 +0200126 garray_T tl_scrollback;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200127 int tl_scrollback_scrolled;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200128
Bram Moolenaar22aad2f2017-07-30 18:19:46 +0200129 VTermPos tl_cursor_pos;
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200130 int tl_cursor_visible;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200131};
132
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200133/*
134 * List of all active terminals.
135 */
136static term_T *first_term = NULL;
137
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200138
139#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
140#define KEY_BUF_LEN 200
141
142/*
143 * Functions with separate implementation for MS-Windows and Unix-like systems.
144 */
145static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd);
Bram Moolenaar43da3e32017-07-23 17:27:54 +0200146static void term_report_winsize(term_T *term, int rows, int cols);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200147static void term_free_vterm(term_T *term);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200148
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200149/**************************************
150 * 1. Generic code for all systems.
151 */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200152
153/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200154 * Determine the terminal size from 'termsize' and the current window.
155 * Assumes term->tl_rows and term->tl_cols are zero.
156 */
157 static void
158set_term_and_win_size(term_T *term)
159{
160 if (*curwin->w_p_tms != NUL)
161 {
162 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
163
164 term->tl_rows = atoi((char *)curwin->w_p_tms);
165 term->tl_cols = atoi((char *)p);
166 }
167 if (term->tl_rows == 0)
168 term->tl_rows = curwin->w_height;
169 else
170 {
171 win_setheight_win(term->tl_rows, curwin);
172 term->tl_rows_fixed = TRUE;
173 }
174 if (term->tl_cols == 0)
175 term->tl_cols = curwin->w_width;
176 else
177 {
178 win_setwidth_win(term->tl_cols, curwin);
179 term->tl_cols_fixed = TRUE;
180 }
181}
182
183/*
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200184 * ":terminal": open a terminal window and execute a job in it.
185 */
186 void
187ex_terminal(exarg_T *eap)
188{
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200189 exarg_T split_ea;
190 win_T *old_curwin = curwin;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200191 term_T *term;
Bram Moolenaare173fd02017-07-22 19:03:32 +0200192 char_u *cmd = eap->arg;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200193
194 if (check_restricted() || check_secure())
195 return;
196
197 term = (term_T *)alloc_clear(sizeof(term_T));
198 if (term == NULL)
199 return;
200 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200201 term->tl_cursor_visible = TRUE;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200202 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200203
204 /* Open a new window or tab. */
205 vim_memset(&split_ea, 0, sizeof(split_ea));
206 split_ea.cmdidx = CMD_new;
207 split_ea.cmd = (char_u *)"new";
208 split_ea.arg = (char_u *)"";
209 ex_splitview(&split_ea);
210 if (curwin == old_curwin)
211 {
212 /* split failed */
213 vim_free(term);
214 return;
215 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200216 term->tl_buffer = curbuf;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200217 curbuf->b_term = term;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200218
219 /* Link the new terminal in the list of active terminals. */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200220 term->tl_next = first_term;
221 first_term = term;
222
Bram Moolenaar293424c2017-07-26 23:11:01 +0200223 if (cmd == NULL || *cmd == NUL)
224 cmd = p_sh;
225
Bram Moolenaar1f2903c2017-07-23 19:51:01 +0200226 if (buflist_findname(cmd) == NULL)
227 curbuf->b_ffname = vim_strsave(cmd);
228 else
229 {
230 int i;
231 size_t len = STRLEN(cmd) + 10;
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200232 char_u *p = alloc((int)len);
Bram Moolenaar1f2903c2017-07-23 19:51:01 +0200233
234 for (i = 1; p != NULL; ++i)
235 {
236 vim_snprintf((char *)p, len, "%s (%d)", cmd, i);
237 if (buflist_findname(p) == NULL)
238 {
239 curbuf->b_ffname = p;
240 break;
241 }
242 }
243 }
244 curbuf->b_fname = curbuf->b_ffname;
245
246 /* Mark the buffer as changed, so that it's not easy to abandon the job. */
247 curbuf->b_changed = TRUE;
248 curbuf->b_p_ma = FALSE;
249 set_string_option_direct((char_u *)"buftype", -1,
250 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200251
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200252 set_term_and_win_size(term);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200253
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200254 /* System dependent: setup the vterm and start the job in it. */
Bram Moolenaare173fd02017-07-22 19:03:32 +0200255 if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd) == OK)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200256 {
257 /* store the size we ended up with */
258 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200259 }
260 else
261 {
Bram Moolenaard85f2712017-07-28 21:51:57 +0200262 free_terminal(curbuf);
Bram Moolenaar61a66052017-07-22 18:39:00 +0200263
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200264 /* Wiping out the buffer will also close the window and call
265 * free_terminal(). */
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200266 do_buffer(DOBUF_WIPE, DOBUF_CURRENT, FORWARD, 0, TRUE);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200267 }
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200268
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200269 /* TODO: Setup pty, see mch_call_shell(). */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200270}
271
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200272/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +0200273 * Free the scrollback buffer for "term".
274 */
275 static void
276free_scrollback(term_T *term)
277{
278 int i;
279
280 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
281 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
282 ga_clear(&term->tl_scrollback);
283}
284
285/*
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200286 * Free a terminal and everything it refers to.
287 * Kills the job if there is one.
288 * Called when wiping out a buffer.
289 */
290 void
Bram Moolenaard85f2712017-07-28 21:51:57 +0200291free_terminal(buf_T *buf)
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200292{
Bram Moolenaard85f2712017-07-28 21:51:57 +0200293 term_T *term = buf->b_term;
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200294 term_T *tp;
295
296 if (term == NULL)
297 return;
298 if (first_term == term)
299 first_term = term->tl_next;
300 else
301 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
302 if (tp->tl_next == term)
303 {
304 tp->tl_next = term->tl_next;
305 break;
306 }
307
308 if (term->tl_job != NULL)
309 {
Bram Moolenaar61a66052017-07-22 18:39:00 +0200310 if (term->tl_job->jv_status != JOB_ENDED
311 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200312 job_stop(term->tl_job, NULL, "kill");
313 job_unref(term->tl_job);
314 }
315
Bram Moolenaar63ecdda2017-07-28 22:29:35 +0200316 free_scrollback(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200317
318 term_free_vterm(term);
Bram Moolenaar21554412017-07-24 21:44:43 +0200319 vim_free(term->tl_title);
320 vim_free(term->tl_status_text);
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200321 vim_free(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200322 buf->b_term = NULL;
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200323}
324
325/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200326 * Write job output "msg[len]" to the vterm.
327 */
328 static void
329term_write_job_output(term_T *term, char_u *msg, size_t len)
330{
331 VTerm *vterm = term->tl_vterm;
332 char_u *p;
333 size_t done;
334 size_t len_now;
335
336 for (done = 0; done < len; done += len_now)
337 {
338 for (p = msg + done; p < msg + len; )
339 {
340 if (*p == NL)
341 break;
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200342 p += utf_ptr2len_len(p, (int)(len - (p - msg)));
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200343 }
344 len_now = p - msg - done;
345 vterm_input_write(vterm, (char *)msg + done, len_now);
346 if (p < msg + len && *p == NL)
347 {
348 /* Convert NL to CR-NL, that appears to work best. */
349 vterm_input_write(vterm, "\r\n", 2);
350 ++len_now;
351 }
352 }
353
354 /* this invokes the damage callbacks */
355 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
356}
357
Bram Moolenaar1c844932017-07-24 23:36:41 +0200358 static void
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200359update_cursor(term_T *term, int redraw)
Bram Moolenaar1c844932017-07-24 23:36:41 +0200360{
Bram Moolenaar392d1bf2017-07-31 21:18:58 +0200361 if (term->tl_terminal_mode)
362 return;
Bram Moolenaar1c844932017-07-24 23:36:41 +0200363 setcursor();
Bram Moolenaar4cc93dc2017-07-26 21:49:37 +0200364 if (redraw && term->tl_buffer == curbuf)
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200365 {
Bram Moolenaar4cc93dc2017-07-26 21:49:37 +0200366 if (term->tl_cursor_visible)
367 cursor_on();
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200368 out_flush();
Bram Moolenaar1c844932017-07-24 23:36:41 +0200369#ifdef FEAT_GUI
Bram Moolenaar12d93ee2017-07-30 19:02:02 +0200370 if (gui.in_use)
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200371 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar1c844932017-07-24 23:36:41 +0200372#endif
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200373 }
Bram Moolenaar1c844932017-07-24 23:36:41 +0200374}
375
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200376/*
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200377 * Invoked when "msg" output from a job was received. Write it to the terminal
378 * of "buffer".
379 */
380 void
381write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
382{
383 size_t len = STRLEN(msg);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200384 term_T *term = buffer->b_term;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200385
Bram Moolenaard85f2712017-07-28 21:51:57 +0200386 if (term->tl_vterm == NULL)
387 {
388 ch_logn(channel, "NOT writing %d bytes to terminal", (int)len);
389 return;
390 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200391 ch_logn(channel, "writing %d bytes to terminal", (int)len);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200392 term_write_job_output(term, msg, len);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200393
Bram Moolenaar392d1bf2017-07-31 21:18:58 +0200394 if (!term->tl_terminal_mode)
395 {
396 /* TODO: only update once in a while. */
397 update_screen(0);
398 update_cursor(term, TRUE);
399 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200400}
401
402/*
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200403 * Send a mouse position and click to the vterm
404 */
405 static int
406term_send_mouse(VTerm *vterm, int button, int pressed)
407{
408 VTermModifier mod = VTERM_MOD_NONE;
409
410 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
411 mouse_col - W_WINCOL(curwin), mod);
412 vterm_mouse_button(vterm, button, pressed, mod);
413 return TRUE;
414}
415
416/*
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200417 * Convert typed key "c" into bytes to send to the job.
418 * Return the number of bytes in "buf".
419 */
420 static int
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200421term_convert_key(term_T *term, int c, char *buf)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200422{
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200423 VTerm *vterm = term->tl_vterm;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200424 VTermKey key = VTERM_KEY_NONE;
425 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200426 int mouse = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200427
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200428 switch (c)
429 {
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200430 case CAR: key = VTERM_KEY_ENTER; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200431 case ESC: key = VTERM_KEY_ESCAPE; break;
Bram Moolenaare906ae82017-07-21 21:10:01 +0200432 /* VTERM_KEY_BACKSPACE becomes 0x7f DEL */
433 case K_BS: c = BS; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200434 case K_DEL: key = VTERM_KEY_DEL; break;
435 case K_DOWN: key = VTERM_KEY_DOWN; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200436 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
437 key = VTERM_KEY_DOWN; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200438 case K_END: key = VTERM_KEY_END; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200439 case K_S_END: mod = VTERM_MOD_SHIFT;
440 key = VTERM_KEY_END; break;
441 case K_C_END: mod = VTERM_MOD_CTRL;
442 key = VTERM_KEY_END; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200443 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
444 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
445 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
446 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
447 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
448 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
449 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
450 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
451 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
452 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
453 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
454 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
455 case K_HOME: key = VTERM_KEY_HOME; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200456 case K_S_HOME: mod = VTERM_MOD_SHIFT;
457 key = VTERM_KEY_HOME; break;
458 case K_C_HOME: mod = VTERM_MOD_CTRL;
459 key = VTERM_KEY_HOME; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200460 case K_INS: key = VTERM_KEY_INS; break;
461 case K_K0: key = VTERM_KEY_KP_0; break;
462 case K_K1: key = VTERM_KEY_KP_1; break;
463 case K_K2: key = VTERM_KEY_KP_2; break;
464 case K_K3: key = VTERM_KEY_KP_3; break;
465 case K_K4: key = VTERM_KEY_KP_4; break;
466 case K_K5: key = VTERM_KEY_KP_5; break;
467 case K_K6: key = VTERM_KEY_KP_6; break;
468 case K_K7: key = VTERM_KEY_KP_7; break;
469 case K_K8: key = VTERM_KEY_KP_8; break;
470 case K_K9: key = VTERM_KEY_KP_9; break;
471 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
472 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
473 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
474 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
475 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
476 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
477 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
478 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
479 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
480 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
481 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
482 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
483 case K_LEFT: key = VTERM_KEY_LEFT; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200484 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
485 key = VTERM_KEY_LEFT; break;
486 case K_C_LEFT: mod = VTERM_MOD_CTRL;
487 key = VTERM_KEY_LEFT; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200488 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
489 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
490 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200491 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
492 key = VTERM_KEY_RIGHT; break;
493 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
494 key = VTERM_KEY_RIGHT; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200495 case K_UP: key = VTERM_KEY_UP; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200496 case K_S_UP: mod = VTERM_MOD_SHIFT;
497 key = VTERM_KEY_UP; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200498 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaare825d8b2017-07-19 23:20:19 +0200499
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200500 case K_MOUSEUP: mouse = term_send_mouse(vterm, 5, 1); break;
501 case K_MOUSEDOWN: mouse = term_send_mouse(vterm, 4, 1); break;
502 case K_MOUSELEFT: /* TODO */ return 0;
503 case K_MOUSERIGHT: /* TODO */ return 0;
Bram Moolenaare825d8b2017-07-19 23:20:19 +0200504
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200505 case K_LEFTMOUSE:
506 case K_LEFTMOUSE_NM: mouse = term_send_mouse(vterm, 1, 1); break;
507 case K_LEFTDRAG: mouse = term_send_mouse(vterm, 1, 1); break;
508 case K_LEFTRELEASE:
509 case K_LEFTRELEASE_NM: mouse = term_send_mouse(vterm, 1, 0); break;
510 case K_MIDDLEMOUSE: mouse = term_send_mouse(vterm, 2, 1); break;
511 case K_MIDDLEDRAG: mouse = term_send_mouse(vterm, 2, 1); break;
512 case K_MIDDLERELEASE: mouse = term_send_mouse(vterm, 2, 0); break;
513 case K_RIGHTMOUSE: mouse = term_send_mouse(vterm, 3, 1); break;
514 case K_RIGHTDRAG: mouse = term_send_mouse(vterm, 3, 1); break;
515 case K_RIGHTRELEASE: mouse = term_send_mouse(vterm, 3, 0); break;
516 case K_X1MOUSE: /* TODO */ return 0;
517 case K_X1DRAG: /* TODO */ return 0;
518 case K_X1RELEASE: /* TODO */ return 0;
519 case K_X2MOUSE: /* TODO */ return 0;
520 case K_X2DRAG: /* TODO */ return 0;
521 case K_X2RELEASE: /* TODO */ return 0;
Bram Moolenaare825d8b2017-07-19 23:20:19 +0200522
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200523 case K_IGNORE: return 0;
524 case K_NOP: return 0;
525 case K_UNDO: return 0;
526 case K_HELP: return 0;
527 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
528 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
529 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
530 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
531 case K_SELECT: return 0;
532#ifdef FEAT_GUI
533 case K_VER_SCROLLBAR: return 0;
534 case K_HOR_SCROLLBAR: return 0;
535#endif
536#ifdef FEAT_GUI_TABLINE
537 case K_TABLINE: return 0;
538 case K_TABMENU: return 0;
539#endif
540#ifdef FEAT_NETBEANS_INTG
541 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
542#endif
543#ifdef FEAT_DND
544 case K_DROP: return 0;
545#endif
546#ifdef FEAT_AUTOCMD
547 case K_CURSORHOLD: return 0;
548#endif
549 case K_PS: vterm_keyboard_start_paste(vterm); return 0;
550 case K_PE: vterm_keyboard_end_paste(vterm); return 0;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200551 }
552
553 /*
554 * Convert special keys to vterm keys:
555 * - Write keys to vterm: vterm_keyboard_key()
556 * - Write output to channel.
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200557 * TODO: use mod_mask
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200558 */
559 if (key != VTERM_KEY_NONE)
560 /* Special key, let vterm convert it. */
561 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200562 else if (!mouse)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200563 /* Normal character, let vterm convert it. */
564 vterm_keyboard_unichar(vterm, c, mod);
565
566 /* Read back the converted escape sequence. */
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200567 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200568}
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200569
Bram Moolenaar938783d2017-07-16 20:13:26 +0200570/*
Bram Moolenaarb000e322017-07-30 19:38:21 +0200571 * Return TRUE if the job for "term" is still running.
Bram Moolenaard85f2712017-07-28 21:51:57 +0200572 */
573 static int
574term_job_running(term_T *term)
575{
Bram Moolenaar1e8340b2017-07-29 15:53:39 +0200576 /* Also consider the job finished when the channel is closed, to avoid a
577 * race condition when updating the title. */
578 return term->tl_job != NULL
579 && term->tl_job->jv_status == JOB_STARTED
580 && channel_is_open(term->tl_job->jv_channel);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200581}
582
583/*
Bram Moolenaar423802d2017-07-30 16:52:24 +0200584 * Add the last line of the scrollback buffer to the buffer in the window.
585 */
586 static void
587add_scrollback_line_to_buffer(term_T *term)
588{
589 linenr_T lnum = term->tl_scrollback.ga_len - 1;
590 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
591 garray_T ga;
592 int c;
593 int col;
594 int i;
595
596 ga_init2(&ga, 1, 100);
597 for (col = 0; col < line->sb_cols; col += line->sb_cells[col].width)
598 {
599 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
600 goto failed;
601 for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i)
602 ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
603 (char_u *)ga.ga_data + ga.ga_len);
604 }
605 if (ga_grow(&ga, 1) == FAIL)
606 goto failed;
607 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
608 ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE);
609
610 if (lnum == 0)
611 {
612 /* Delete the empty line that was in the empty buffer. */
613 curbuf = term->tl_buffer;
614 ml_delete(2, FALSE);
615 curbuf = curwin->w_buffer;
616 }
617
618failed:
619 ga_clear(&ga);
620}
621
622/*
623 * Add the current lines of the terminal to scrollback and to the buffer.
624 * Called after the job has ended and when switching to Terminal mode.
625 */
626 static void
627move_terminal_to_buffer(term_T *term)
628{
629 win_T *wp;
630 int len;
631 int lines_skipped = 0;
632 VTermPos pos;
633 VTermScreenCell cell;
634 VTermScreenCell *p;
635 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
636
637 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
638 {
639 len = 0;
640 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
641 if (vterm_screen_get_cell(screen, pos, &cell) != 0
642 && cell.chars[0] != NUL)
643 len = pos.col + 1;
644
645 if (len == 0)
646 ++lines_skipped;
647 else
648 {
649 while (lines_skipped > 0)
650 {
651 /* Line was skipped, add an empty line. */
652 --lines_skipped;
653 if (ga_grow(&term->tl_scrollback, 1) == OK)
654 {
655 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
656 + term->tl_scrollback.ga_len;
657
658 line->sb_cols = 0;
659 line->sb_cells = NULL;
660 ++term->tl_scrollback.ga_len;
661
662 add_scrollback_line_to_buffer(term);
663 }
664 }
665
666 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
667 if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK)
668 {
669 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
670 + term->tl_scrollback.ga_len;
671
672 for (pos.col = 0; pos.col < len; ++pos.col)
673 {
674 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
675 vim_memset(p + pos.col, 0, sizeof(cell));
676 else
677 p[pos.col] = cell;
678 }
679 line->sb_cols = len;
680 line->sb_cells = p;
681 ++term->tl_scrollback.ga_len;
682
683 add_scrollback_line_to_buffer(term);
684 }
685 else
686 vim_free(p);
687 }
688 }
689
690 FOR_ALL_WINDOWS(wp)
691 {
692 if (wp->w_buffer == term->tl_buffer)
693 {
694 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
695 wp->w_cursor.col = 0;
696 wp->w_valid = 0;
697 redraw_win_later(wp, NOT_VALID);
698 }
699 }
700}
701
702 static void
703set_terminal_mode(term_T *term, int on)
704{
705 term->tl_terminal_mode = on;
706 vim_free(term->tl_status_text);
707 term->tl_status_text = NULL;
708 if (term->tl_buffer == curbuf)
709 maketitle();
710}
711
712/*
713 * Called after the job if finished and Terminal mode is not active:
714 * Move the vterm contents into the scrollback buffer and free the vterm.
715 */
716 static void
717cleanup_vterm(term_T *term)
718{
719 move_terminal_to_buffer(term);
720 term_free_vterm(term);
721 set_terminal_mode(term, FALSE);
722}
723
724/*
725 * Switch from sending keys to the job to Terminal-Normal mode.
726 * Suspends updating the terminal window.
727 */
728 static void
729term_enter_terminal_mode()
730{
731 term_T *term = curbuf->b_term;
732
733 /* Append the current terminal contents to the buffer. */
734 move_terminal_to_buffer(term);
735
736 set_terminal_mode(term, TRUE);
737}
738
739/*
740 * Returns TRUE if the current window contains a terminal and we are in
741 * Terminal-Normal mode.
742 */
743 int
744term_in_terminal_mode()
745{
746 term_T *term = curbuf->b_term;
747
748 return term != NULL && term->tl_terminal_mode;
749}
750
751/*
752 * Switch from Terminal-Normal mode to sending keys to the job.
753 * Restores updating the terminal window.
754 */
755 void
756term_leave_terminal_mode()
757{
758 term_T *term = curbuf->b_term;
759 sb_line_T *line;
760 garray_T *gap;
761
762 /* Remove the terminal contents from the scrollback and the buffer. */
763 gap = &term->tl_scrollback;
764 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled)
765 {
766 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
767 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
768 vim_free(line->sb_cells);
769 --gap->ga_len;
770 if (gap->ga_len == 0)
771 break;
772 }
773 check_cursor();
774
775 set_terminal_mode(term, FALSE);
776
777 if (term->tl_channel_closed)
778 cleanup_vterm(term);
779 redraw_buf_and_status_later(curbuf, NOT_VALID);
780}
781
782/*
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200783 * Get a key from the user without mapping.
784 * TODO: use terminal mode mappings.
785 */
786 static int
787term_vgetc()
788{
789 int c;
790
791 ++no_mapping;
792 ++allow_keys;
793 got_int = FALSE;
794 c = vgetc();
Bram Moolenaar43c007f2017-07-30 17:45:37 +0200795 got_int = FALSE;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200796 --no_mapping;
797 --allow_keys;
798 return c;
799}
800
801/*
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200802 * Send keys to terminal.
803 */
804 static int
805send_keys_to_term(term_T *term, int c, int typed)
806{
807 char msg[KEY_BUF_LEN];
808 size_t len;
809 static int mouse_was_outside = FALSE;
810 int dragging_outside = FALSE;
811
812 /* Catch keys that need to be handled as in Normal mode. */
813 switch (c)
814 {
815 case NUL:
816 case K_ZERO:
817 if (typed)
818 stuffcharReadbuff(c);
819 return FAIL;
820
821 case K_IGNORE:
822 return FAIL;
823
824 case K_LEFTDRAG:
825 case K_MIDDLEDRAG:
826 case K_RIGHTDRAG:
827 case K_X1DRAG:
828 case K_X2DRAG:
829 dragging_outside = mouse_was_outside;
830 /* FALLTHROUGH */
831 case K_LEFTMOUSE:
832 case K_LEFTMOUSE_NM:
833 case K_LEFTRELEASE:
834 case K_LEFTRELEASE_NM:
835 case K_MIDDLEMOUSE:
836 case K_MIDDLERELEASE:
837 case K_RIGHTMOUSE:
838 case K_RIGHTRELEASE:
839 case K_X1MOUSE:
840 case K_X1RELEASE:
841 case K_X2MOUSE:
842 case K_X2RELEASE:
843 if (mouse_row < W_WINROW(curwin)
844 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
845 || mouse_col < W_WINCOL(curwin)
846 || mouse_col >= W_ENDCOL(curwin)
847 || dragging_outside)
848 {
849 /* click outside the current window */
850 if (typed)
851 {
852 stuffcharReadbuff(c);
853 mouse_was_outside = TRUE;
854 }
855 return FAIL;
856 }
857 }
858 if (typed)
859 mouse_was_outside = FALSE;
860
861 /* Convert the typed key to a sequence of bytes for the job. */
862 len = term_convert_key(term, c, msg);
863 if (len > 0)
864 /* TODO: if FAIL is returned, stop? */
865 channel_send(term->tl_job->jv_channel, PART_IN,
866 (char_u *)msg, (int)len, NULL);
867
868 return OK;
869}
870
Bram Moolenaar0e23e9c2017-07-30 18:47:19 +0200871 static void
872position_cursor(win_T *wp, VTermPos *pos)
873{
874 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
875 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
876 wp->w_valid |= (VALID_WCOL|VALID_WROW);
877}
878
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200879/*
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200880 * Handle CTRL-W "": send register contents to the job.
881 */
882 static void
883term_paste_register(int prev_c UNUSED)
884{
885 int c;
886 list_T *l;
887 listitem_T *item;
888 long reglen = 0;
889 int type;
890
891#ifdef FEAT_CMDL_INFO
892 if (add_to_showcmd(prev_c))
893 if (add_to_showcmd('"'))
894 out_flush();
895#endif
896 c = term_vgetc();
897#ifdef FEAT_CMDL_INFO
898 clear_showcmd();
899#endif
900
901 /* CTRL-W "= prompt for expression to evaluate. */
902 if (c == '=' && get_expr_register() != '=')
903 return;
904
905 l = (list_T *)get_reg_contents(c, GREG_LIST);
906 if (l != NULL)
907 {
908 type = get_reg_type(c, &reglen);
909 for (item = l->lv_first; item != NULL; item = item->li_next)
910 {
911 char_u *s = get_tv_string(&item->li_tv);
912
913 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
914 s, STRLEN(s), NULL);
915 if (item->li_next != NULL || type == MLINE)
916 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
917 (char_u *)"\r", 1, NULL);
918 }
919 list_free(l);
920 }
921}
922
923/*
Bram Moolenaar423802d2017-07-30 16:52:24 +0200924 * Returns TRUE if the current window contains a terminal and we are sending
925 * keys to the job.
926 */
927 int
928term_use_loop()
929{
930 term_T *term = curbuf->b_term;
931
932 return term != NULL
933 && !term->tl_terminal_mode
934 && term->tl_vterm != NULL
935 && term_job_running(term);
936}
937
938/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200939 * Wait for input and send it to the job.
940 * Return when the start of a CTRL-W command is typed or anything else that
941 * should be handled as a Normal mode command.
Bram Moolenaard85f2712017-07-28 21:51:57 +0200942 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
943 * the terminal was closed.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200944 */
Bram Moolenaard85f2712017-07-28 21:51:57 +0200945 int
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200946terminal_loop(void)
947{
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200948 int c;
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200949 int termkey = 0;
950
951 if (*curwin->w_p_tk != NUL)
952 termkey = string_to_key(curwin->w_p_tk, TRUE);
Bram Moolenaar0e23e9c2017-07-30 18:47:19 +0200953 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200954
955 for (;;)
956 {
957 /* TODO: skip screen update when handling a sequence of keys. */
Bram Moolenaar43c007f2017-07-30 17:45:37 +0200958 /* Repeat redrawing in case a message is received while redrawing. */
959 while (curwin->w_redr_type != 0)
960 update_screen(0);
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200961 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaar423802d2017-07-30 16:52:24 +0200962
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200963 c = term_vgetc();
Bram Moolenaard85f2712017-07-28 21:51:57 +0200964 if (curbuf->b_term->tl_vterm == NULL
965 || !term_job_running(curbuf->b_term))
966 /* job finished while waiting for a character */
967 break;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200968
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200969 if (c == (termkey == 0 ? Ctrl_W : termkey))
970 {
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200971 int prev_c = c;
972
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200973#ifdef FEAT_CMDL_INFO
974 if (add_to_showcmd(c))
975 out_flush();
976#endif
977 c = term_vgetc();
978#ifdef FEAT_CMDL_INFO
979 clear_showcmd();
980#endif
Bram Moolenaard85f2712017-07-28 21:51:57 +0200981 if (curbuf->b_term->tl_vterm == NULL
982 || !term_job_running(curbuf->b_term))
983 /* job finished while waiting for a character */
984 break;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200985
986 if (termkey == 0 && c == '.')
Bram Moolenaar423802d2017-07-30 16:52:24 +0200987 {
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200988 /* "CTRL-W .": send CTRL-W to the job */
989 c = Ctrl_W;
Bram Moolenaar423802d2017-07-30 16:52:24 +0200990 }
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200991 else if (c == 'N')
Bram Moolenaar423802d2017-07-30 16:52:24 +0200992 {
993 term_enter_terminal_mode();
994 return FAIL;
995 }
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200996 else if (c == '"')
997 {
998 term_paste_register(prev_c);
999 continue;
1000 }
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +02001001 else if (termkey == 0 || c != termkey)
1002 {
1003 stuffcharReadbuff(Ctrl_W);
1004 stuffcharReadbuff(c);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001005 return OK;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +02001006 }
Bram Moolenaardbe948d2017-07-23 22:50:51 +02001007 }
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001008 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
1009 return OK;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001010 }
Bram Moolenaard85f2712017-07-28 21:51:57 +02001011 return FAIL;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001012}
1013
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001014/*
1015 * Called when a job has finished.
Bram Moolenaar423802d2017-07-30 16:52:24 +02001016 * This updates the title and status, but does not close the vter, because
1017 * there might still be pending output in the channel.
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001018 */
1019 void
1020term_job_ended(job_T *job)
1021{
1022 term_T *term;
1023 int did_one = FALSE;
1024
1025 for (term = first_term; term != NULL; term = term->tl_next)
1026 if (term->tl_job == job)
1027 {
1028 vim_free(term->tl_title);
1029 term->tl_title = NULL;
1030 vim_free(term->tl_status_text);
1031 term->tl_status_text = NULL;
1032 redraw_buf_and_status_later(term->tl_buffer, VALID);
1033 did_one = TRUE;
1034 }
1035 if (did_one)
1036 redraw_statuslines();
1037 if (curbuf->b_term != NULL)
1038 {
1039 if (curbuf->b_term->tl_job == job)
1040 maketitle();
1041 update_cursor(curbuf->b_term, TRUE);
1042 }
1043}
1044
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001045 static void
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001046may_toggle_cursor(term_T *term)
1047{
1048 if (curbuf == term->tl_buffer)
1049 {
1050 if (term->tl_cursor_visible)
1051 cursor_on();
1052 else
1053 cursor_off();
1054 }
1055}
1056
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001057 static int
1058handle_damage(VTermRect rect, void *user)
1059{
1060 term_T *term = (term_T *)user;
1061
1062 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
1063 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
1064 redraw_buf_later(term->tl_buffer, NOT_VALID);
1065 return 1;
1066}
1067
1068 static int
1069handle_moverect(VTermRect dest UNUSED, VTermRect src UNUSED, void *user)
1070{
1071 term_T *term = (term_T *)user;
1072
1073 /* TODO */
1074 redraw_buf_later(term->tl_buffer, NOT_VALID);
1075 return 1;
1076}
1077
1078 static int
1079handle_movecursor(
1080 VTermPos pos,
1081 VTermPos oldpos UNUSED,
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001082 int visible,
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001083 void *user)
1084{
1085 term_T *term = (term_T *)user;
1086 win_T *wp;
Bram Moolenaar22aad2f2017-07-30 18:19:46 +02001087
1088 term->tl_cursor_pos = pos;
1089 term->tl_cursor_visible = visible;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001090
1091 FOR_ALL_WINDOWS(wp)
1092 {
1093 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001094 position_cursor(wp, &pos);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001095 }
Bram Moolenaar392d1bf2017-07-31 21:18:58 +02001096 if (term->tl_buffer == curbuf && !term->tl_terminal_mode)
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001097 {
1098 may_toggle_cursor(term);
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001099 update_cursor(term, term->tl_cursor_visible);
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001100 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001101
1102 return 1;
1103}
1104
Bram Moolenaar21554412017-07-24 21:44:43 +02001105 static int
1106handle_settermprop(
1107 VTermProp prop,
1108 VTermValue *value,
1109 void *user)
1110{
1111 term_T *term = (term_T *)user;
1112
1113 switch (prop)
1114 {
1115 case VTERM_PROP_TITLE:
1116 vim_free(term->tl_title);
1117 term->tl_title = vim_strsave((char_u *)value->string);
1118 vim_free(term->tl_status_text);
1119 term->tl_status_text = NULL;
1120 if (term == curbuf->b_term)
1121 maketitle();
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001122 break;
1123
1124 case VTERM_PROP_CURSORVISIBLE:
1125 term->tl_cursor_visible = value->boolean;
1126 may_toggle_cursor(term);
1127 out_flush();
1128 break;
1129
Bram Moolenaar21554412017-07-24 21:44:43 +02001130 default:
1131 break;
1132 }
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001133 /* Always return 1, otherwise vterm doesn't store the value internally. */
1134 return 1;
Bram Moolenaar21554412017-07-24 21:44:43 +02001135}
1136
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001137/*
1138 * The job running in the terminal resized the terminal.
1139 */
1140 static int
1141handle_resize(int rows, int cols, void *user)
1142{
1143 term_T *term = (term_T *)user;
1144 win_T *wp;
1145
1146 term->tl_rows = rows;
1147 term->tl_cols = cols;
1148 FOR_ALL_WINDOWS(wp)
1149 {
1150 if (wp->w_buffer == term->tl_buffer)
1151 {
1152 win_setheight_win(rows, wp);
1153 win_setwidth_win(cols, wp);
1154 }
1155 }
1156
1157 redraw_buf_later(term->tl_buffer, NOT_VALID);
1158 return 1;
1159}
1160
Bram Moolenaard85f2712017-07-28 21:51:57 +02001161/*
1162 * Handle a line that is pushed off the top of the screen.
1163 */
1164 static int
1165handle_pushline(int cols, const VTermScreenCell *cells, void *user)
1166{
1167 term_T *term = (term_T *)user;
1168
1169 /* TODO: Limit the number of lines that are stored. */
1170 /* TODO: put the text in the buffer. */
1171 if (ga_grow(&term->tl_scrollback, 1) == OK)
1172 {
Bram Moolenaar696d00f2017-07-29 14:52:43 +02001173 VTermScreenCell *p = NULL;
1174 int len = 0;
1175 int i;
1176 sb_line_T *line;
Bram Moolenaard85f2712017-07-28 21:51:57 +02001177
1178 /* do not store empty cells at the end */
1179 for (i = 0; i < cols; ++i)
1180 if (cells[i].chars[0] != 0)
1181 len = i + 1;
1182
Bram Moolenaar696d00f2017-07-29 14:52:43 +02001183 if (len > 0)
1184 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001185 if (p != NULL)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001186 mch_memmove(p, cells, sizeof(VTermScreenCell) * len);
Bram Moolenaar696d00f2017-07-29 14:52:43 +02001187
1188 line = (sb_line_T *)term->tl_scrollback.ga_data
1189 + term->tl_scrollback.ga_len;
1190 line->sb_cols = len;
1191 line->sb_cells = p;
1192 ++term->tl_scrollback.ga_len;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001193 ++term->tl_scrollback_scrolled;
Bram Moolenaar423802d2017-07-30 16:52:24 +02001194
1195 add_scrollback_line_to_buffer(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001196 }
1197 return 0; /* ignored */
1198}
1199
Bram Moolenaar21554412017-07-24 21:44:43 +02001200static VTermScreenCallbacks screen_callbacks = {
1201 handle_damage, /* damage */
1202 handle_moverect, /* moverect */
1203 handle_movecursor, /* movecursor */
1204 handle_settermprop, /* settermprop */
1205 NULL, /* bell */
1206 handle_resize, /* resize */
Bram Moolenaard85f2712017-07-28 21:51:57 +02001207 handle_pushline, /* sb_pushline */
Bram Moolenaar21554412017-07-24 21:44:43 +02001208 NULL /* sb_popline */
1209};
1210
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001211/*
Bram Moolenaard85f2712017-07-28 21:51:57 +02001212 * Called when a channel has been closed.
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001213 * If this was a channel for a terminal window then finish it up.
Bram Moolenaard85f2712017-07-28 21:51:57 +02001214 */
1215 void
1216term_channel_closed(channel_T *ch)
1217{
1218 term_T *term;
1219 int did_one = FALSE;
1220
1221 for (term = first_term; term != NULL; term = term->tl_next)
1222 if (term->tl_job == ch->ch_job)
1223 {
Bram Moolenaar423802d2017-07-30 16:52:24 +02001224 term->tl_channel_closed = TRUE;
1225
Bram Moolenaard85f2712017-07-28 21:51:57 +02001226 vim_free(term->tl_title);
1227 term->tl_title = NULL;
1228 vim_free(term->tl_status_text);
1229 term->tl_status_text = NULL;
1230
Bram Moolenaar423802d2017-07-30 16:52:24 +02001231 /* Unless in Terminal-Normal mode: clear the vterm. */
1232 if (!term->tl_terminal_mode)
1233 cleanup_vterm(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001234
1235 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
1236 did_one = TRUE;
1237 }
1238 if (did_one)
1239 {
1240 redraw_statuslines();
1241
1242 /* Need to break out of vgetc(). */
1243 ins_char_typebuf(K_IGNORE);
1244
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001245 term = curbuf->b_term;
1246 if (term != NULL)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001247 {
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001248 if (term->tl_job == ch->ch_job)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001249 maketitle();
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001250 update_cursor(term, term->tl_cursor_visible);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001251 }
1252 }
1253}
1254
1255/*
Bram Moolenaareeac6772017-07-23 15:48:37 +02001256 * Reverse engineer the RGB value into a cterm color index.
1257 * First color is 1. Return 0 if no match found.
1258 */
1259 static int
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001260color2index(VTermColor *color, int fg, int *boldp)
Bram Moolenaareeac6772017-07-23 15:48:37 +02001261{
1262 int red = color->red;
1263 int blue = color->blue;
1264 int green = color->green;
1265
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001266 /* The argument for lookup_color() is for the color_names[] table. */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001267 if (red == 0)
1268 {
1269 if (green == 0)
1270 {
1271 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001272 return lookup_color(0, fg, boldp) + 1; /* black */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001273 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001274 return lookup_color(1, fg, boldp) + 1; /* dark blue */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001275 }
1276 else if (green == 224)
1277 {
1278 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001279 return lookup_color(2, fg, boldp) + 1; /* dark green */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001280 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001281 return lookup_color(3, fg, boldp) + 1; /* dark cyan */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001282 }
1283 }
1284 else if (red == 224)
1285 {
1286 if (green == 0)
1287 {
1288 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001289 return lookup_color(4, fg, boldp) + 1; /* dark red */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001290 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001291 return lookup_color(5, fg, boldp) + 1; /* dark magenta */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001292 }
1293 else if (green == 224)
1294 {
1295 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001296 return lookup_color(6, fg, boldp) + 1; /* dark yellow / brown */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001297 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001298 return lookup_color(8, fg, boldp) + 1; /* white / light grey */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001299 }
1300 }
1301 else if (red == 128)
1302 {
1303 if (green == 128 && blue == 128)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001304 return lookup_color(12, fg, boldp) + 1; /* high intensity black / dark grey */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001305 }
1306 else if (red == 255)
1307 {
1308 if (green == 64)
1309 {
1310 if (blue == 64)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001311 return lookup_color(20, fg, boldp) + 1; /* light red */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001312 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001313 return lookup_color(22, fg, boldp) + 1; /* light magenta */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001314 }
1315 else if (green == 255)
1316 {
1317 if (blue == 64)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001318 return lookup_color(24, fg, boldp) + 1; /* yellow */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001319 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001320 return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001321 }
1322 }
1323 else if (red == 64)
1324 {
1325 if (green == 64)
1326 {
1327 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001328 return lookup_color(14, fg, boldp) + 1; /* light blue */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001329 }
1330 else if (green == 255)
1331 {
1332 if (blue == 64)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001333 return lookup_color(16, fg, boldp) + 1; /* light green */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001334 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001335 return lookup_color(18, fg, boldp) + 1; /* light cyan */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001336 }
1337 }
1338 if (t_colors >= 256)
1339 {
1340 if (red == blue && red == green)
1341 {
1342 /* 24-color greyscale */
1343 static int cutoff[23] = {
1344 0x05, 0x10, 0x1B, 0x26, 0x31, 0x3C, 0x47, 0x52,
1345 0x5D, 0x68, 0x73, 0x7F, 0x8A, 0x95, 0xA0, 0xAB,
1346 0xB6, 0xC1, 0xCC, 0xD7, 0xE2, 0xED, 0xF9};
1347 int i;
1348
1349 for (i = 0; i < 23; ++i)
1350 if (red < cutoff[i])
1351 return i + 233;
1352 return 256;
1353 }
1354
1355 /* 216-color cube */
1356 return 17 + ((red + 25) / 0x33) * 36
1357 + ((green + 25) / 0x33) * 6
1358 + (blue + 25) / 0x33;
1359 }
1360 return 0;
1361}
1362
1363/*
1364 * Convert the attributes of a vterm cell into an attribute index.
1365 */
1366 static int
1367cell2attr(VTermScreenCell *cell)
1368{
1369 int attr = 0;
1370
1371 if (cell->attrs.bold)
1372 attr |= HL_BOLD;
1373 if (cell->attrs.underline)
1374 attr |= HL_UNDERLINE;
1375 if (cell->attrs.italic)
1376 attr |= HL_ITALIC;
1377 if (cell->attrs.strike)
1378 attr |= HL_STANDOUT;
1379 if (cell->attrs.reverse)
1380 attr |= HL_INVERSE;
Bram Moolenaareeac6772017-07-23 15:48:37 +02001381
1382#ifdef FEAT_GUI
1383 if (gui.in_use)
1384 {
Bram Moolenaar26af85d2017-07-23 16:45:10 +02001385 guicolor_T fg, bg;
1386
1387 fg = gui_mch_get_rgb_color(cell->fg.red, cell->fg.green, cell->fg.blue);
1388 bg = gui_mch_get_rgb_color(cell->bg.red, cell->bg.green, cell->bg.blue);
1389 return get_gui_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001390 }
1391 else
1392#endif
1393#ifdef FEAT_TERMGUICOLORS
1394 if (p_tgc)
1395 {
Bram Moolenaar065f41c2017-07-23 18:07:56 +02001396 guicolor_T fg, bg;
1397
1398 fg = gui_get_rgb_color_cmn(cell->fg.red, cell->fg.green, cell->fg.blue);
1399 bg = gui_get_rgb_color_cmn(cell->bg.red, cell->bg.green, cell->bg.blue);
1400
1401 return get_tgc_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001402 }
Bram Moolenaar065f41c2017-07-23 18:07:56 +02001403 else
Bram Moolenaareeac6772017-07-23 15:48:37 +02001404#endif
1405 {
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001406 int bold = MAYBE;
1407 int fg = color2index(&cell->fg, TRUE, &bold);
1408 int bg = color2index(&cell->bg, FALSE, &bold);
1409
1410 /* with 8 colors set the bold attribute to get a bright foreground */
1411 if (bold == TRUE)
1412 attr |= HL_BOLD;
1413 return get_cterm_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001414 }
1415 return 0;
1416}
1417
1418/*
Bram Moolenaard85f2712017-07-28 21:51:57 +02001419 * Called to update the window that contains a terminal.
1420 * Returns FAIL when there is no terminal running in this window.
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001421 */
Bram Moolenaard85f2712017-07-28 21:51:57 +02001422 int
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001423term_update_window(win_T *wp)
Bram Moolenaar938783d2017-07-16 20:13:26 +02001424{
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001425 term_T *term = wp->w_buffer->b_term;
Bram Moolenaard85f2712017-07-28 21:51:57 +02001426 VTerm *vterm;
1427 VTermScreen *screen;
1428 VTermState *state;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001429 VTermPos pos;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001430
Bram Moolenaar423802d2017-07-30 16:52:24 +02001431 if (term == NULL || term->tl_vterm == NULL || term->tl_terminal_mode)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001432 return FAIL;
Bram Moolenaar423802d2017-07-30 16:52:24 +02001433
Bram Moolenaard85f2712017-07-28 21:51:57 +02001434 vterm = term->tl_vterm;
1435 screen = vterm_obtain_screen(vterm);
1436 state = vterm_obtain_state(vterm);
1437
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001438 /*
1439 * If the window was resized a redraw will be triggered and we get here.
1440 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
1441 */
1442 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
1443 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001444 {
Bram Moolenaar96ad8c92017-07-28 14:17:34 +02001445 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
1446 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
1447 win_T *twp;
1448
1449 FOR_ALL_WINDOWS(twp)
1450 {
1451 /* When more than one window shows the same terminal, use the
1452 * smallest size. */
1453 if (twp->w_buffer == term->tl_buffer)
1454 {
1455 if (!term->tl_rows_fixed && rows > twp->w_height)
1456 rows = twp->w_height;
1457 if (!term->tl_cols_fixed && cols > twp->w_width)
1458 cols = twp->w_width;
1459 }
1460 }
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001461
1462 vterm_set_size(vterm, rows, cols);
1463 ch_logn(term->tl_job->jv_channel, "Resizing terminal to %d lines",
1464 rows);
Bram Moolenaar43da3e32017-07-23 17:27:54 +02001465 term_report_winsize(term, rows, cols);
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001466 }
Bram Moolenaar58556cd2017-07-20 23:04:46 +02001467
1468 /* The cursor may have been moved when resizing. */
1469 vterm_state_get_cursorpos(state, &pos);
1470 position_cursor(wp, &pos);
1471
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001472 /* TODO: Only redraw what changed. */
1473 for (pos.row = 0; pos.row < wp->w_height; ++pos.row)
Bram Moolenaar938783d2017-07-16 20:13:26 +02001474 {
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001475 int off = screen_get_current_line_off();
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001476 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar938783d2017-07-16 20:13:26 +02001477
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001478 if (pos.row < term->tl_rows)
1479 {
1480 for (pos.col = 0; pos.col < max_col; )
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001481 {
1482 VTermScreenCell cell;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001483 int c;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001484
Bram Moolenaareeac6772017-07-23 15:48:37 +02001485 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1486 vim_memset(&cell, 0, sizeof(cell));
1487
1488 /* TODO: composing chars */
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001489 c = cell.chars[0];
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001490 if (c == NUL)
1491 {
1492 ScreenLines[off] = ' ';
Bram Moolenaar8a773062017-07-24 22:29:21 +02001493#if defined(FEAT_MBYTE)
1494 if (enc_utf8)
1495 ScreenLinesUC[off] = NUL;
1496#endif
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001497 }
1498 else
1499 {
1500#if defined(FEAT_MBYTE)
1501 if (enc_utf8 && c >= 0x80)
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001502 {
1503 ScreenLines[off] = ' ';
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001504 ScreenLinesUC[off] = c;
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001505 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001506 else
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001507 {
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001508 ScreenLines[off] = c;
Bram Moolenaar8a773062017-07-24 22:29:21 +02001509 if (enc_utf8)
1510 ScreenLinesUC[off] = NUL;
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001511 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001512#else
1513 ScreenLines[off] = c;
1514#endif
1515 }
Bram Moolenaareeac6772017-07-23 15:48:37 +02001516 ScreenAttrs[off] = cell2attr(&cell);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001517
1518 ++pos.col;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001519 ++off;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001520 if (cell.width == 2)
1521 {
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001522 ScreenLines[off] = NUL;
Bram Moolenaar8a773062017-07-24 22:29:21 +02001523#if defined(FEAT_MBYTE)
1524 if (enc_utf8)
1525 ScreenLinesUC[off] = NUL;
1526#endif
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001527 ++pos.col;
1528 ++off;
1529 }
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001530 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001531 }
Bram Moolenaare825d8b2017-07-19 23:20:19 +02001532 else
1533 pos.col = 0;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001534
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001535 screen_line(wp->w_winrow + pos.row, wp->w_wincol,
1536 pos.col, wp->w_width, FALSE);
Bram Moolenaar938783d2017-07-16 20:13:26 +02001537 }
Bram Moolenaard85f2712017-07-28 21:51:57 +02001538
1539 return OK;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001540}
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001541
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001542/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001543 * Return TRUE if "wp" is a terminal window where the job has finished.
1544 */
1545 int
1546term_is_finished(buf_T *buf)
1547{
1548 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
1549}
1550
1551/*
Bram Moolenaar423802d2017-07-30 16:52:24 +02001552 * Return TRUE if "wp" is a terminal window where the job has finished or we
1553 * are in Terminal-Normal mode.
1554 */
1555 int
1556term_show_buffer(buf_T *buf)
1557{
1558 term_T *term = buf->b_term;
1559
1560 return term != NULL && (term->tl_vterm == NULL || term->tl_terminal_mode);
1561}
1562
1563/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001564 * The current buffer is going to be changed. If there is terminal
1565 * highlighting remove it now.
1566 */
1567 void
1568term_change_in_curbuf(void)
1569{
1570 term_T *term = curbuf->b_term;
1571
1572 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
1573 {
1574 free_scrollback(term);
1575 redraw_buf_later(term->tl_buffer, NOT_VALID);
1576 }
1577}
1578
1579/*
1580 * Get the screen attribute for a position in the buffer.
1581 */
1582 int
1583term_get_attr(buf_T *buf, linenr_T lnum, int col)
1584{
1585 term_T *term = buf->b_term;
1586 sb_line_T *line;
1587
Bram Moolenaar70229f92017-07-29 16:01:53 +02001588 if (lnum > term->tl_scrollback.ga_len)
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001589 return 0;
1590 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
1591 if (col >= line->sb_cols)
1592 return 0;
1593 return cell2attr(line->sb_cells + col);
1594}
1595
1596/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001597 * Set job options common for Unix and MS-Windows.
1598 */
1599 static void
1600setup_job_options(jobopt_T *opt, int rows, int cols)
1601{
1602 clear_job_options(opt);
1603 opt->jo_mode = MODE_RAW;
1604 opt->jo_out_mode = MODE_RAW;
1605 opt->jo_err_mode = MODE_RAW;
1606 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001607
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001608 opt->jo_io[PART_OUT] = JIO_BUFFER;
1609 opt->jo_io[PART_ERR] = JIO_BUFFER;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001610 opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
1611
1612 opt->jo_modifiable[PART_OUT] = 0;
1613 opt->jo_modifiable[PART_ERR] = 0;
1614 opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
1615
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001616 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
1617 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
Bram Moolenaar5a1feb82017-07-22 18:04:08 +02001618 opt->jo_pty = TRUE;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001619 opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
1620
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001621 opt->jo_term_rows = rows;
1622 opt->jo_term_cols = cols;
1623}
1624
1625/*
1626 * Create a new vterm and initialize it.
1627 */
1628 static void
1629create_vterm(term_T *term, int rows, int cols)
1630{
1631 VTerm *vterm;
1632 VTermScreen *screen;
1633
1634 vterm = vterm_new(rows, cols);
1635 term->tl_vterm = vterm;
1636 screen = vterm_obtain_screen(vterm);
1637 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
1638 /* TODO: depends on 'encoding'. */
1639 vterm_set_utf8(vterm, 1);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001640
1641 /* Vterm uses a default black background. Set it to white when
1642 * 'background' is "light". */
1643 if (*p_bg == 'l')
1644 {
1645 VTermColor fg, bg;
1646
1647 fg.red = fg.green = fg.blue = 0;
1648 bg.red = bg.green = bg.blue = 255;
1649 vterm_state_set_default_colors(vterm_obtain_state(vterm), &fg, &bg);
1650 }
1651
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001652 /* Required to initialize most things. */
1653 vterm_screen_reset(screen, 1 /* hard */);
1654}
1655
Bram Moolenaar21554412017-07-24 21:44:43 +02001656/*
1657 * Return the text to show for the buffer name and status.
1658 */
1659 char_u *
1660term_get_status_text(term_T *term)
1661{
1662 if (term->tl_status_text == NULL)
1663 {
1664 char_u *txt;
1665 size_t len;
1666
Bram Moolenaar423802d2017-07-30 16:52:24 +02001667 if (term->tl_terminal_mode)
1668 {
1669 if (term_job_running(term))
1670 txt = (char_u *)_("Terminal");
1671 else
1672 txt = (char_u *)_("Terminal-finished");
1673 }
1674 else if (term->tl_title != NULL)
Bram Moolenaar21554412017-07-24 21:44:43 +02001675 txt = term->tl_title;
1676 else if (term_job_running(term))
1677 txt = (char_u *)_("running");
1678 else
1679 txt = (char_u *)_("finished");
1680 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
Bram Moolenaara1b5b092017-07-26 21:29:34 +02001681 term->tl_status_text = alloc((int)len);
Bram Moolenaar21554412017-07-24 21:44:43 +02001682 if (term->tl_status_text != NULL)
1683 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
1684 term->tl_buffer->b_fname, txt);
1685 }
1686 return term->tl_status_text;
1687}
1688
Bram Moolenaarf86eea92017-07-28 13:51:30 +02001689/*
1690 * Mark references in jobs of terminals.
1691 */
1692 int
1693set_ref_in_term(int copyID)
1694{
1695 int abort = FALSE;
1696 term_T *term;
1697 typval_T tv;
1698
1699 for (term = first_term; term != NULL; term = term->tl_next)
1700 if (term->tl_job != NULL)
1701 {
1702 tv.v_type = VAR_JOB;
1703 tv.vval.v_job = term->tl_job;
1704 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
1705 }
1706 return abort;
1707}
1708
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001709/*
Bram Moolenaar97870002017-07-30 18:28:38 +02001710 * Get the buffer from the first argument in "argvars".
1711 * Returns NULL when the buffer is not for a terminal window.
1712 */
1713 static buf_T *
1714term_get_buf(typval_T *argvars)
1715{
1716 buf_T *buf;
1717
1718 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
1719 ++emsg_off;
1720 buf = get_buf_tv(&argvars[0], FALSE);
1721 --emsg_off;
1722 if (buf == NULL || buf->b_term == NULL)
1723 return NULL;
1724 return buf;
1725}
1726
1727/*
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001728 * "term_getattr(attr, name)" function
1729 */
1730 void
1731f_term_getattr(typval_T *argvars, typval_T *rettv)
1732{
1733 int attr;
1734 size_t i;
1735 char_u *name;
1736
1737 static struct {
1738 char *name;
1739 int attr;
1740 } attrs[] = {
1741 {"bold", HL_BOLD},
1742 {"italic", HL_ITALIC},
1743 {"underline", HL_UNDERLINE},
1744 {"strike", HL_STANDOUT},
1745 {"reverse", HL_INVERSE},
1746 };
1747
1748 attr = get_tv_number(&argvars[0]);
1749 name = get_tv_string_chk(&argvars[1]);
1750 if (name == NULL)
1751 return;
1752
1753 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
1754 if (STRCMP(name, attrs[i].name) == 0)
1755 {
1756 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
1757 break;
1758 }
1759}
1760
1761/*
Bram Moolenaar97870002017-07-30 18:28:38 +02001762 * "term_getcursor(buf)" function
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001763 */
Bram Moolenaar97870002017-07-30 18:28:38 +02001764 void
1765f_term_getcursor(typval_T *argvars, typval_T *rettv)
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001766{
Bram Moolenaar97870002017-07-30 18:28:38 +02001767 buf_T *buf = term_get_buf(argvars);
1768 list_T *l;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001769
Bram Moolenaar97870002017-07-30 18:28:38 +02001770 if (rettv_list_alloc(rettv) == FAIL)
1771 return;
1772 if (buf == NULL)
1773 return;
1774
1775 l = rettv->vval.v_list;
1776 list_append_number(l, buf->b_term->tl_cursor_pos.row);
1777 list_append_number(l, buf->b_term->tl_cursor_pos.col);
1778 list_append_number(l, buf->b_term->tl_cursor_visible);
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001779}
1780
1781/*
1782 * "term_getjob(buf)" function
1783 */
1784 void
1785f_term_getjob(typval_T *argvars, typval_T *rettv)
1786{
1787 buf_T *buf = term_get_buf(argvars);
1788
1789 rettv->v_type = VAR_JOB;
1790 rettv->vval.v_job = NULL;
1791 if (buf == NULL)
1792 return;
1793
1794 rettv->vval.v_job = buf->b_term->tl_job;
1795 if (rettv->vval.v_job != NULL)
1796 ++rettv->vval.v_job->jv_refcount;
1797}
1798
1799/*
1800 * "term_getline(buf, row)" function
1801 */
1802 void
1803f_term_getline(typval_T *argvars, typval_T *rettv)
1804{
1805 buf_T *buf = term_get_buf(argvars);
1806 term_T *term;
1807 int row;
1808
1809 rettv->v_type = VAR_STRING;
1810 if (buf == NULL)
1811 return;
1812 term = buf->b_term;
Bram Moolenaar22aad2f2017-07-30 18:19:46 +02001813 if (argvars[1].v_type == VAR_UNKNOWN)
1814 row = term->tl_cursor_pos.row;
1815 else
1816 row = (int)get_tv_number(&argvars[1]);
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001817
1818 if (term->tl_vterm == NULL)
1819 {
1820 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
1821
1822 /* vterm is finished, get the text from the buffer */
1823 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
1824 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
1825 }
1826 else
1827 {
1828 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
1829 VTermRect rect;
1830 int len;
1831 char_u *p;
1832
1833 len = term->tl_cols * MB_MAXBYTES + 1;
1834 p = alloc(len);
1835 if (p == NULL)
1836 return;
1837 rettv->vval.v_string = p;
1838
1839 rect.start_col = 0;
1840 rect.end_col = term->tl_cols;
1841 rect.start_row = row;
1842 rect.end_row = row + 1;
1843 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
1844 }
1845}
1846
1847/*
1848 * "term_getsize(buf)" function
1849 */
1850 void
1851f_term_getsize(typval_T *argvars, typval_T *rettv)
1852{
1853 buf_T *buf = term_get_buf(argvars);
1854 list_T *l;
1855
1856 if (rettv_list_alloc(rettv) == FAIL)
1857 return;
1858 if (buf == NULL)
1859 return;
1860
1861 l = rettv->vval.v_list;
1862 list_append_number(l, buf->b_term->tl_rows);
1863 list_append_number(l, buf->b_term->tl_cols);
1864}
1865
1866/*
Bram Moolenaarb000e322017-07-30 19:38:21 +02001867 * "term_getstatus(buf)" function
1868 */
1869 void
1870f_term_getstatus(typval_T *argvars, typval_T *rettv)
1871{
1872 buf_T *buf = term_get_buf(argvars);
1873 term_T *term;
1874 char_u val[100];
1875
1876 rettv->v_type = VAR_STRING;
1877 if (buf == NULL)
1878 return;
1879 term = buf->b_term;
1880
1881 if (term_job_running(term))
1882 STRCPY(val, "running");
1883 else
1884 STRCPY(val, "finished");
1885 if (term->tl_terminal_mode)
1886 STRCAT(val, ",terminal");
1887 rettv->vval.v_string = vim_strsave(val);
1888}
1889
1890/*
1891 * "term_gettitle(buf)" function
1892 */
1893 void
1894f_term_gettitle(typval_T *argvars, typval_T *rettv)
1895{
1896 buf_T *buf = term_get_buf(argvars);
1897
1898 rettv->v_type = VAR_STRING;
1899 if (buf == NULL)
1900 return;
1901
1902 if (buf->b_term->tl_title != NULL)
1903 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
1904}
1905
1906/*
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001907 * "term_list()" function
1908 */
1909 void
1910f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
1911{
1912 term_T *tp;
1913 list_T *l;
1914
1915 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
1916 return;
1917
1918 l = rettv->vval.v_list;
1919 for (tp = first_term; tp != NULL; tp = tp->tl_next)
1920 if (tp != NULL && tp->tl_buffer != NULL)
1921 if (list_append_number(l,
1922 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
1923 return;
1924}
1925
1926/*
1927 * "term_scrape(buf, row)" function
1928 */
1929 void
1930f_term_scrape(typval_T *argvars, typval_T *rettv)
1931{
1932 buf_T *buf = term_get_buf(argvars);
1933 VTermScreen *screen = NULL;
1934 VTermPos pos;
1935 list_T *l;
1936 term_T *term;
1937
1938 if (rettv_list_alloc(rettv) == FAIL)
1939 return;
1940 if (buf == NULL)
1941 return;
1942 term = buf->b_term;
1943 if (term->tl_vterm != NULL)
1944 screen = vterm_obtain_screen(term->tl_vterm);
1945
1946 l = rettv->vval.v_list;
Bram Moolenaar22aad2f2017-07-30 18:19:46 +02001947 if (argvars[1].v_type == VAR_UNKNOWN)
1948 pos.row = term->tl_cursor_pos.row;
1949 else
1950 pos.row = (int)get_tv_number(&argvars[1]);
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001951 for (pos.col = 0; pos.col < term->tl_cols; )
1952 {
1953 dict_T *dcell;
1954 VTermScreenCell cell;
1955 char_u rgb[8];
1956 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
1957 int off = 0;
1958 int i;
1959
1960 if (screen == NULL)
1961 {
1962 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
1963 sb_line_T *line;
1964
1965 /* vterm has finished, get the cell from scrollback */
1966 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
1967 break;
1968 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
1969 if (pos.col >= line->sb_cols)
1970 break;
1971 cell = line->sb_cells[pos.col];
1972 }
1973 else if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1974 break;
1975 dcell = dict_alloc();
1976 list_append_dict(l, dcell);
1977
1978 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
1979 {
1980 if (cell.chars[i] == 0)
1981 break;
1982 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
1983 }
1984 mbs[off] = NUL;
1985 dict_add_nr_str(dcell, "chars", 0, mbs);
1986
1987 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
1988 cell.fg.red, cell.fg.green, cell.fg.blue);
1989 dict_add_nr_str(dcell, "fg", 0, rgb);
1990 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
1991 cell.bg.red, cell.bg.green, cell.bg.blue);
1992 dict_add_nr_str(dcell, "bg", 0, rgb);
1993
1994 dict_add_nr_str(dcell, "attr", cell2attr(&cell), NULL);
1995 dict_add_nr_str(dcell, "width", cell.width, NULL);
1996
1997 ++pos.col;
1998 if (cell.width == 2)
1999 ++pos.col;
2000 }
2001}
2002
2003/*
2004 * "term_sendkeys(buf, keys)" function
2005 */
2006 void
2007f_term_sendkeys(typval_T *argvars, typval_T *rettv)
2008{
2009 buf_T *buf = term_get_buf(argvars);
2010 char_u *msg;
2011 term_T *term;
2012
2013 rettv->v_type = VAR_UNKNOWN;
2014 if (buf == NULL)
2015 return;
2016
2017 msg = get_tv_string_chk(&argvars[1]);
2018 if (msg == NULL)
2019 return;
2020 term = buf->b_term;
2021 if (term->tl_vterm == NULL)
2022 return;
2023
2024 while (*msg != NUL)
2025 {
2026 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
2027 msg += MB_PTR2LEN(msg);
2028 }
2029
Bram Moolenaar392d1bf2017-07-31 21:18:58 +02002030 if (!term->tl_terminal_mode)
2031 {
2032 /* TODO: only update once in a while. */
2033 update_screen(0);
2034 if (buf == curbuf)
2035 update_cursor(term, TRUE);
2036 }
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02002037}
2038
2039/*
2040 * "term_start(command, options)" function
2041 */
2042 void
2043f_term_start(typval_T *argvars, typval_T *rettv)
2044{
2045 char_u *cmd = get_tv_string_chk(&argvars[0]);
2046 exarg_T ea;
2047
2048 if (cmd == NULL)
2049 return;
2050 ea.arg = cmd;
2051 ex_terminal(&ea);
2052
2053 if (curbuf->b_term != NULL)
2054 rettv->vval.v_number = curbuf->b_fnum;
2055}
2056
2057/*
2058 * "term_wait" function
2059 */
2060 void
2061f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
2062{
2063 buf_T *buf = term_get_buf(argvars);
2064
2065 if (buf == NULL)
2066 return;
2067
2068 /* Get the job status, this will detect a job that finished. */
2069 if (buf->b_term->tl_job != NULL)
2070 (void)job_status(buf->b_term->tl_job);
2071
2072 /* Check for any pending channel I/O. */
2073 vpeekc_any();
2074 ui_delay(10L, FALSE);
2075
2076 /* Flushing messages on channels is hopefully sufficient.
2077 * TODO: is there a better way? */
2078 parse_queued_messages();
2079}
2080
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002081# ifdef WIN3264
2082
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02002083/**************************************
2084 * 2. MS-Windows implementation.
2085 */
2086
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002087#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
2088#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
2089
Bram Moolenaar8a773062017-07-24 22:29:21 +02002090void* (*winpty_config_new)(UINT64, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002091void* (*winpty_open)(void*, void*);
Bram Moolenaar8a773062017-07-24 22:29:21 +02002092void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002093BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
2094void (*winpty_config_set_initial_size)(void*, int, int);
2095LPCWSTR (*winpty_conin_name)(void*);
2096LPCWSTR (*winpty_conout_name)(void*);
2097LPCWSTR (*winpty_conerr_name)(void*);
2098void (*winpty_free)(void*);
2099void (*winpty_config_free)(void*);
2100void (*winpty_spawn_config_free)(void*);
2101void (*winpty_error_free)(void*);
2102LPCWSTR (*winpty_error_msg)(void*);
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002103BOOL (*winpty_set_size)(void*, int, int, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002104
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002105#define WINPTY_DLL "winpty.dll"
2106
2107static HINSTANCE hWinPtyDLL = NULL;
2108
2109 int
2110dyn_winpty_init(void)
2111{
2112 int i;
2113 static struct
2114 {
2115 char *name;
2116 FARPROC *ptr;
2117 } winpty_entry[] =
2118 {
2119 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
2120 {"winpty_config_free", (FARPROC*)&winpty_config_free},
2121 {"winpty_config_new", (FARPROC*)&winpty_config_new},
2122 {"winpty_config_set_initial_size", (FARPROC*)&winpty_config_set_initial_size},
2123 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
2124 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
2125 {"winpty_error_free", (FARPROC*)&winpty_error_free},
2126 {"winpty_free", (FARPROC*)&winpty_free},
2127 {"winpty_open", (FARPROC*)&winpty_open},
2128 {"winpty_spawn", (FARPROC*)&winpty_spawn},
2129 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
2130 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
2131 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002132 {"winpty_set_size", (FARPROC*)&winpty_set_size},
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002133 {NULL, NULL}
2134 };
2135
2136 /* No need to initialize twice. */
2137 if (hWinPtyDLL)
2138 return 1;
2139 /* Load winpty.dll */
2140 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
2141 if (!hWinPtyDLL)
2142 {
2143 EMSG2(_(e_loadlib), WINPTY_DLL);
2144 return 0;
2145 }
2146 for (i = 0; winpty_entry[i].name != NULL
2147 && winpty_entry[i].ptr != NULL; ++i)
2148 {
2149 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
2150 winpty_entry[i].name)) == NULL)
2151 {
2152 EMSG2(_(e_loadfunc), winpty_entry[i].name);
2153 return 0;
2154 }
2155 }
2156
2157 return 1;
2158}
2159
2160/*
2161 * Create a new terminal of "rows" by "cols" cells.
2162 * Store a reference in "term".
2163 * Return OK or FAIL.
2164 */
2165 static int
2166term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
2167{
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002168 WCHAR *p;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002169 channel_T *channel = NULL;
2170 job_T *job = NULL;
2171 jobopt_T opt;
2172 DWORD error;
2173 HANDLE jo = NULL, child_process_handle, child_thread_handle;
2174 void *winpty_err;
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002175 void *spawn_config = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002176
2177 if (!dyn_winpty_init())
2178 return FAIL;
2179
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002180 p = enc_to_utf16(cmd, NULL);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002181 if (p == NULL)
2182 return FAIL;
2183
2184 job = job_alloc();
2185 if (job == NULL)
2186 goto failed;
2187
2188 channel = add_channel();
2189 if (channel == NULL)
2190 goto failed;
2191
2192 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
2193 if (term->tl_winpty_config == NULL)
2194 goto failed;
2195
2196 winpty_config_set_initial_size(term->tl_winpty_config, cols, rows);
2197 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
2198 if (term->tl_winpty == NULL)
2199 goto failed;
2200
2201 spawn_config = winpty_spawn_config_new(
2202 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
2203 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
2204 NULL,
2205 p,
2206 NULL,
2207 NULL,
2208 &winpty_err);
2209 if (spawn_config == NULL)
2210 goto failed;
2211
2212 channel = add_channel();
2213 if (channel == NULL)
2214 goto failed;
2215
2216 job = job_alloc();
2217 if (job == NULL)
2218 goto failed;
2219
2220 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
2221 &child_thread_handle, &error, &winpty_err))
2222 goto failed;
2223
2224 channel_set_pipes(channel,
2225 (sock_T) CreateFileW(
2226 winpty_conin_name(term->tl_winpty),
2227 GENERIC_WRITE, 0, NULL,
2228 OPEN_EXISTING, 0, NULL),
2229 (sock_T) CreateFileW(
2230 winpty_conout_name(term->tl_winpty),
2231 GENERIC_READ, 0, NULL,
2232 OPEN_EXISTING, 0, NULL),
2233 (sock_T) CreateFileW(
2234 winpty_conerr_name(term->tl_winpty),
2235 GENERIC_READ, 0, NULL,
2236 OPEN_EXISTING, 0, NULL));
2237
2238 jo = CreateJobObject(NULL, NULL);
2239 if (jo == NULL)
2240 goto failed;
2241
2242 if (!AssignProcessToJobObject(jo, child_process_handle))
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002243 {
2244 /* Failed, switch the way to terminate process with TerminateProcess. */
2245 CloseHandle(jo);
2246 jo = NULL;
2247 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002248
2249 winpty_spawn_config_free(spawn_config);
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002250 vim_free(p);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002251
2252 create_vterm(term, rows, cols);
2253
2254 setup_job_options(&opt, rows, cols);
2255 channel_set_job(channel, job, &opt);
2256
2257 job->jv_channel = channel;
2258 job->jv_proc_info.hProcess = child_process_handle;
2259 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
2260 job->jv_job_object = jo;
2261 job->jv_status = JOB_STARTED;
Bram Moolenaar0e83f022017-07-27 22:07:35 +02002262 ++job->jv_refcount;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002263 term->tl_job = job;
2264
2265 return OK;
2266
2267failed:
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002268 if (spawn_config != NULL)
2269 winpty_spawn_config_free(spawn_config);
2270 vim_free(p);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002271 if (channel != NULL)
2272 channel_clear(channel);
2273 if (job != NULL)
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002274 {
2275 job->jv_channel = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002276 job_cleanup(job);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002277 }
2278 term->tl_job = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002279 if (jo != NULL)
2280 CloseHandle(jo);
2281 if (term->tl_winpty != NULL)
2282 winpty_free(term->tl_winpty);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002283 term->tl_winpty = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002284 if (term->tl_winpty_config != NULL)
2285 winpty_config_free(term->tl_winpty_config);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002286 term->tl_winpty_config = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002287 if (winpty_err != NULL)
2288 {
2289 char_u *msg = utf16_to_enc(
2290 (short_u *)winpty_error_msg(winpty_err), NULL);
2291
2292 EMSG(msg);
2293 winpty_error_free(winpty_err);
2294 }
2295 return FAIL;
2296}
2297
2298/*
2299 * Free the terminal emulator part of "term".
2300 */
2301 static void
Bram Moolenaard85f2712017-07-28 21:51:57 +02002302term_free_vterm(term_T *term)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002303{
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002304 if (term->tl_winpty != NULL)
2305 winpty_free(term->tl_winpty);
Bram Moolenaard85f2712017-07-28 21:51:57 +02002306 term->tl_winpty = NULL;
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002307 if (term->tl_winpty_config != NULL)
2308 winpty_config_free(term->tl_winpty_config);
Bram Moolenaard85f2712017-07-28 21:51:57 +02002309 term->tl_winpty_config = NULL;
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002310 if (term->tl_vterm != NULL)
2311 vterm_free(term->tl_vterm);
Bram Moolenaardcbfa332017-07-28 23:16:13 +02002312 term->tl_vterm = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002313}
2314
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002315/*
2316 * Request size to terminal.
2317 */
2318 static void
2319term_report_winsize(term_T *term, int rows, int cols)
2320{
2321 winpty_set_size(term->tl_winpty, cols, rows, NULL);
2322}
2323
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002324# else
2325
2326/**************************************
2327 * 3. Unix-like implementation.
2328 */
2329
2330/*
2331 * Create a new terminal of "rows" by "cols" cells.
2332 * Start job for "cmd".
2333 * Store the pointers in "term".
2334 * Return OK or FAIL.
2335 */
2336 static int
2337term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
2338{
2339 typval_T argvars[2];
2340 jobopt_T opt;
2341
2342 create_vterm(term, rows, cols);
2343
2344 argvars[0].v_type = VAR_STRING;
2345 argvars[0].vval.v_string = cmd;
2346 setup_job_options(&opt, rows, cols);
2347 term->tl_job = job_start(argvars, &opt);
Bram Moolenaar0e83f022017-07-27 22:07:35 +02002348 if (term->tl_job != NULL)
2349 ++term->tl_job->jv_refcount;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002350
Bram Moolenaar61a66052017-07-22 18:39:00 +02002351 return term->tl_job != NULL
2352 && term->tl_job->jv_channel != NULL
2353 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002354}
2355
2356/*
2357 * Free the terminal emulator part of "term".
2358 */
2359 static void
Bram Moolenaard85f2712017-07-28 21:51:57 +02002360term_free_vterm(term_T *term)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002361{
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002362 if (term->tl_vterm != NULL)
2363 vterm_free(term->tl_vterm);
Bram Moolenaard85f2712017-07-28 21:51:57 +02002364 term->tl_vterm = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002365}
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002366
2367/*
2368 * Request size to terminal.
2369 */
2370 static void
2371term_report_winsize(term_T *term, int rows, int cols)
2372{
2373 /* Use an ioctl() to report the new window size to the job. */
2374 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
2375 {
2376 int fd = -1;
2377 int part;
2378
2379 for (part = PART_OUT; part < PART_COUNT; ++part)
2380 {
2381 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
2382 if (isatty(fd))
2383 break;
2384 }
2385 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
2386 mch_stop_job(term->tl_job, (char_u *)"winch");
2387 }
2388}
2389
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002390# endif
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02002391
Bram Moolenaare4f25e42017-07-07 11:54:15 +02002392#endif /* FEAT_TERMINAL */