blob: 9dc2d32028b418a2e6e410a398a75cc2bd91303a [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 Moolenaar392d1bf2017-07-31 21:18:58 +020039 * - Add StatusLineTerm highlighting
Bram Moolenaar423802d2017-07-30 16:52:24 +020040 * - in bash mouse clicks are inserting characters.
41 * - mouse scroll: when over other window, scroll that window.
Bram Moolenaard85f2712017-07-28 21:51:57 +020042 * - For the scrollback buffer store lines in the buffer, only attributes in
43 * tl_scrollback.
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020044 * - When the job ends:
Bram Moolenaar21554412017-07-24 21:44:43 +020045 * - Need an option or argument to drop the window+buffer right away, to be
Bram Moolenaar423802d2017-07-30 16:52:24 +020046 * used for a shell or Vim. 'termfinish'; "close", "open" (open window when
47 * job finishes).
48 * - add option values to the command:
49 * :term <24x80> <close> vim notes.txt
Bram Moolenaar12d93ee2017-07-30 19:02:02 +020050 * - support different cursor shapes, colors and attributes
51 * - make term_getcursor() return type (none/block/bar/underline) and
52 * attributes (color, blink, etc.)
Bram Moolenaard85f2712017-07-28 21:51:57 +020053 * - To set BS correctly, check get_stty(); Pass the fd of the pty.
Bram Moolenaar423802d2017-07-30 16:52:24 +020054 * - do not store terminal window in viminfo. Or prefix term:// ?
Bram Moolenaar21554412017-07-24 21:44:43 +020055 * - add a character in :ls output
Bram Moolenaar43c007f2017-07-30 17:45:37 +020056 * - add 't' to mode()
Bram Moolenaar12d853f2017-08-01 18:04:04 +020057 * - When making a change after the job has ended, make the buffer a normal
58 * buffer; needs to be written.
Bram Moolenaar938783d2017-07-16 20:13:26 +020059 * - when closing window and job has not ended, make terminal hidden?
Bram Moolenaard85f2712017-07-28 21:51:57 +020060 * - when closing window and job has ended, make buffer hidden?
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +020061 * - don't allow exiting Vim when a terminal is still running a job
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020062 * - use win_del_lines() to make scroll-up efficient.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020063 * - add test for giving error for invalid 'termsize' value.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020064 * - support minimal size when 'termsize' is "rows*cols".
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020065 * - support minimal size when 'termsize' is empty?
Bram Moolenaar5a1feb82017-07-22 18:04:08 +020066 * - implement "term" for job_start(): more job options when starting a
67 * terminal.
Bram Moolenaar423802d2017-07-30 16:52:24 +020068 * - if the job in the terminal does not support the mouse, we can use the
69 * mouse in the Terminal window for copy/paste.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020070 * - when 'encoding' is not utf-8, or the job is using another encoding, setup
71 * conversions.
Bram Moolenaarc9456ce2017-07-30 21:46:04 +020072 * - update ":help function-list" for terminal functions.
Bram Moolenaardbe948d2017-07-23 22:50:51 +020073 * - In the GUI use a terminal emulator for :!cmd.
Bram Moolenaar12d853f2017-08-01 18:04:04 +020074 * - Copy text in the vterm to the Vim buffer once in a while, so that
75 * completion works.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020076 */
77
78#include "vim.h"
79
Bram Moolenaarc6df10e2017-07-29 20:15:08 +020080#if defined(FEAT_TERMINAL) || defined(PROTO)
Bram Moolenaare4f25e42017-07-07 11:54:15 +020081
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020082#ifdef WIN3264
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020083# define MIN(x,y) (x < y ? x : y)
84# define MAX(x,y) (x > y ? x : y)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +020085#endif
Bram Moolenaare4f25e42017-07-07 11:54:15 +020086
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +020087#include "libvterm/include/vterm.h"
88
Bram Moolenaard85f2712017-07-28 21:51:57 +020089typedef struct sb_line_S {
90 int sb_cols; /* can differ per line */
91 VTermScreenCell *sb_cells; /* allocated */
92} sb_line_T;
93
Bram Moolenaare4f25e42017-07-07 11:54:15 +020094/* typedef term_T in structs.h */
95struct terminal_S {
96 term_T *tl_next;
97
Bram Moolenaar423802d2017-07-30 16:52:24 +020098 VTerm *tl_vterm;
99 job_T *tl_job;
100 buf_T *tl_buffer;
101
102 int tl_terminal_mode;
103 int tl_channel_closed;
104
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200105#ifdef WIN3264
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200106 void *tl_winpty_config;
107 void *tl_winpty;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200108#endif
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200109
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200110 /* last known vterm size */
111 int tl_rows;
112 int tl_cols;
113 /* vterm size does not follow window size */
114 int tl_rows_fixed;
115 int tl_cols_fixed;
116
Bram Moolenaar21554412017-07-24 21:44:43 +0200117 char_u *tl_title; /* NULL or allocated */
118 char_u *tl_status_text; /* NULL or allocated */
119
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200120 /* Range of screen rows to update. Zero based. */
121 int tl_dirty_row_start; /* -1 if nothing dirty */
122 int tl_dirty_row_end; /* row below last one to update */
123
Bram Moolenaard85f2712017-07-28 21:51:57 +0200124 garray_T tl_scrollback;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200125 int tl_scrollback_scrolled;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200126
Bram Moolenaar22aad2f2017-07-30 18:19:46 +0200127 VTermPos tl_cursor_pos;
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200128 int tl_cursor_visible;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200129};
130
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200131/*
132 * List of all active terminals.
133 */
134static term_T *first_term = NULL;
135
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200136
137#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
138#define KEY_BUF_LEN 200
139
140/*
141 * Functions with separate implementation for MS-Windows and Unix-like systems.
142 */
143static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd);
Bram Moolenaar43da3e32017-07-23 17:27:54 +0200144static void term_report_winsize(term_T *term, int rows, int cols);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200145static void term_free_vterm(term_T *term);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200146
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200147/**************************************
148 * 1. Generic code for all systems.
149 */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200150
151/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200152 * Determine the terminal size from 'termsize' and the current window.
153 * Assumes term->tl_rows and term->tl_cols are zero.
154 */
155 static void
156set_term_and_win_size(term_T *term)
157{
158 if (*curwin->w_p_tms != NUL)
159 {
160 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
161
162 term->tl_rows = atoi((char *)curwin->w_p_tms);
163 term->tl_cols = atoi((char *)p);
164 }
165 if (term->tl_rows == 0)
166 term->tl_rows = curwin->w_height;
167 else
168 {
169 win_setheight_win(term->tl_rows, curwin);
170 term->tl_rows_fixed = TRUE;
171 }
172 if (term->tl_cols == 0)
173 term->tl_cols = curwin->w_width;
174 else
175 {
176 win_setwidth_win(term->tl_cols, curwin);
177 term->tl_cols_fixed = TRUE;
178 }
179}
180
181/*
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200182 * ":terminal": open a terminal window and execute a job in it.
183 */
184 void
185ex_terminal(exarg_T *eap)
186{
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200187 exarg_T split_ea;
188 win_T *old_curwin = curwin;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200189 term_T *term;
Bram Moolenaare173fd02017-07-22 19:03:32 +0200190 char_u *cmd = eap->arg;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200191
192 if (check_restricted() || check_secure())
193 return;
194
195 term = (term_T *)alloc_clear(sizeof(term_T));
196 if (term == NULL)
197 return;
198 term->tl_dirty_row_end = MAX_ROW;
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200199 term->tl_cursor_visible = TRUE;
Bram Moolenaard85f2712017-07-28 21:51:57 +0200200 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200201
202 /* Open a new window or tab. */
203 vim_memset(&split_ea, 0, sizeof(split_ea));
204 split_ea.cmdidx = CMD_new;
205 split_ea.cmd = (char_u *)"new";
206 split_ea.arg = (char_u *)"";
207 ex_splitview(&split_ea);
208 if (curwin == old_curwin)
209 {
210 /* split failed */
211 vim_free(term);
212 return;
213 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200214 term->tl_buffer = curbuf;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200215 curbuf->b_term = term;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200216
217 /* Link the new terminal in the list of active terminals. */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200218 term->tl_next = first_term;
219 first_term = term;
220
Bram Moolenaar293424c2017-07-26 23:11:01 +0200221 if (cmd == NULL || *cmd == NUL)
222 cmd = p_sh;
223
Bram Moolenaar1f2903c2017-07-23 19:51:01 +0200224 if (buflist_findname(cmd) == NULL)
225 curbuf->b_ffname = vim_strsave(cmd);
226 else
227 {
228 int i;
229 size_t len = STRLEN(cmd) + 10;
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200230 char_u *p = alloc((int)len);
Bram Moolenaar1f2903c2017-07-23 19:51:01 +0200231
232 for (i = 1; p != NULL; ++i)
233 {
234 vim_snprintf((char *)p, len, "%s (%d)", cmd, i);
235 if (buflist_findname(p) == NULL)
236 {
237 curbuf->b_ffname = p;
238 break;
239 }
240 }
241 }
242 curbuf->b_fname = curbuf->b_ffname;
243
244 /* Mark the buffer as changed, so that it's not easy to abandon the job. */
245 curbuf->b_changed = TRUE;
246 curbuf->b_p_ma = FALSE;
247 set_string_option_direct((char_u *)"buftype", -1,
248 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200249
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200250 set_term_and_win_size(term);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200251
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200252 /* System dependent: setup the vterm and start the job in it. */
Bram Moolenaare173fd02017-07-22 19:03:32 +0200253 if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd) == OK)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200254 {
255 /* store the size we ended up with */
256 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200257 }
258 else
259 {
Bram Moolenaard85f2712017-07-28 21:51:57 +0200260 free_terminal(curbuf);
Bram Moolenaar61a66052017-07-22 18:39:00 +0200261
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200262 /* Wiping out the buffer will also close the window and call
263 * free_terminal(). */
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200264 do_buffer(DOBUF_WIPE, DOBUF_CURRENT, FORWARD, 0, TRUE);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200265 }
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200266
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200267 /* TODO: Setup pty, see mch_call_shell(). */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200268}
269
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200270/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +0200271 * Free the scrollback buffer for "term".
272 */
273 static void
274free_scrollback(term_T *term)
275{
276 int i;
277
278 for (i = 0; i < term->tl_scrollback.ga_len; ++i)
279 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells);
280 ga_clear(&term->tl_scrollback);
281}
282
283/*
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200284 * Free a terminal and everything it refers to.
285 * Kills the job if there is one.
286 * Called when wiping out a buffer.
287 */
288 void
Bram Moolenaard85f2712017-07-28 21:51:57 +0200289free_terminal(buf_T *buf)
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200290{
Bram Moolenaard85f2712017-07-28 21:51:57 +0200291 term_T *term = buf->b_term;
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200292 term_T *tp;
293
294 if (term == NULL)
295 return;
296 if (first_term == term)
297 first_term = term->tl_next;
298 else
299 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next)
300 if (tp->tl_next == term)
301 {
302 tp->tl_next = term->tl_next;
303 break;
304 }
305
306 if (term->tl_job != NULL)
307 {
Bram Moolenaar61a66052017-07-22 18:39:00 +0200308 if (term->tl_job->jv_status != JOB_ENDED
309 && term->tl_job->jv_status != JOB_FAILED)
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200310 job_stop(term->tl_job, NULL, "kill");
311 job_unref(term->tl_job);
312 }
313
Bram Moolenaar63ecdda2017-07-28 22:29:35 +0200314 free_scrollback(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200315
316 term_free_vterm(term);
Bram Moolenaar21554412017-07-24 21:44:43 +0200317 vim_free(term->tl_title);
318 vim_free(term->tl_status_text);
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200319 vim_free(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200320 buf->b_term = NULL;
Bram Moolenaar96ca27a2017-07-17 23:20:24 +0200321}
322
323/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200324 * Write job output "msg[len]" to the vterm.
325 */
326 static void
327term_write_job_output(term_T *term, char_u *msg, size_t len)
328{
329 VTerm *vterm = term->tl_vterm;
330 char_u *p;
331 size_t done;
332 size_t len_now;
333
334 for (done = 0; done < len; done += len_now)
335 {
336 for (p = msg + done; p < msg + len; )
337 {
338 if (*p == NL)
339 break;
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200340 p += utf_ptr2len_len(p, (int)(len - (p - msg)));
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200341 }
342 len_now = p - msg - done;
343 vterm_input_write(vterm, (char *)msg + done, len_now);
344 if (p < msg + len && *p == NL)
345 {
346 /* Convert NL to CR-NL, that appears to work best. */
347 vterm_input_write(vterm, "\r\n", 2);
348 ++len_now;
349 }
350 }
351
352 /* this invokes the damage callbacks */
353 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
354}
355
Bram Moolenaar1c844932017-07-24 23:36:41 +0200356 static void
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200357update_cursor(term_T *term, int redraw)
Bram Moolenaar1c844932017-07-24 23:36:41 +0200358{
Bram Moolenaar392d1bf2017-07-31 21:18:58 +0200359 if (term->tl_terminal_mode)
360 return;
Bram Moolenaar1c844932017-07-24 23:36:41 +0200361 setcursor();
Bram Moolenaar4cc93dc2017-07-26 21:49:37 +0200362 if (redraw && term->tl_buffer == curbuf)
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200363 {
Bram Moolenaar4cc93dc2017-07-26 21:49:37 +0200364 if (term->tl_cursor_visible)
365 cursor_on();
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200366 out_flush();
Bram Moolenaar1c844932017-07-24 23:36:41 +0200367#ifdef FEAT_GUI
Bram Moolenaar12d93ee2017-07-30 19:02:02 +0200368 if (gui.in_use)
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200369 gui_update_cursor(FALSE, FALSE);
Bram Moolenaar1c844932017-07-24 23:36:41 +0200370#endif
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200371 }
Bram Moolenaar1c844932017-07-24 23:36:41 +0200372}
373
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200374/*
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200375 * Invoked when "msg" output from a job was received. Write it to the terminal
376 * of "buffer".
377 */
378 void
379write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
380{
381 size_t len = STRLEN(msg);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200382 term_T *term = buffer->b_term;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200383
Bram Moolenaard85f2712017-07-28 21:51:57 +0200384 if (term->tl_vterm == NULL)
385 {
386 ch_logn(channel, "NOT writing %d bytes to terminal", (int)len);
387 return;
388 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200389 ch_logn(channel, "writing %d bytes to terminal", (int)len);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200390 term_write_job_output(term, msg, len);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200391
Bram Moolenaar392d1bf2017-07-31 21:18:58 +0200392 if (!term->tl_terminal_mode)
393 {
394 /* TODO: only update once in a while. */
395 update_screen(0);
396 update_cursor(term, TRUE);
397 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200398}
399
400/*
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200401 * Send a mouse position and click to the vterm
402 */
403 static int
404term_send_mouse(VTerm *vterm, int button, int pressed)
405{
406 VTermModifier mod = VTERM_MOD_NONE;
407
408 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin),
409 mouse_col - W_WINCOL(curwin), mod);
410 vterm_mouse_button(vterm, button, pressed, mod);
411 return TRUE;
412}
413
414/*
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200415 * Convert typed key "c" into bytes to send to the job.
416 * Return the number of bytes in "buf".
417 */
418 static int
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200419term_convert_key(term_T *term, int c, char *buf)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200420{
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200421 VTerm *vterm = term->tl_vterm;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200422 VTermKey key = VTERM_KEY_NONE;
423 VTermModifier mod = VTERM_MOD_NONE;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200424 int mouse = FALSE;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200425
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200426 switch (c)
427 {
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200428 case CAR: key = VTERM_KEY_ENTER; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200429 case ESC: key = VTERM_KEY_ESCAPE; break;
Bram Moolenaare906ae82017-07-21 21:10:01 +0200430 /* VTERM_KEY_BACKSPACE becomes 0x7f DEL */
431 case K_BS: c = BS; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200432 case K_DEL: key = VTERM_KEY_DEL; break;
433 case K_DOWN: key = VTERM_KEY_DOWN; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200434 case K_S_DOWN: mod = VTERM_MOD_SHIFT;
435 key = VTERM_KEY_DOWN; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200436 case K_END: key = VTERM_KEY_END; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200437 case K_S_END: mod = VTERM_MOD_SHIFT;
438 key = VTERM_KEY_END; break;
439 case K_C_END: mod = VTERM_MOD_CTRL;
440 key = VTERM_KEY_END; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200441 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
442 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
443 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
444 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
445 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
446 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
447 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
448 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
449 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
450 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
451 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
452 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
453 case K_HOME: key = VTERM_KEY_HOME; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200454 case K_S_HOME: mod = VTERM_MOD_SHIFT;
455 key = VTERM_KEY_HOME; break;
456 case K_C_HOME: mod = VTERM_MOD_CTRL;
457 key = VTERM_KEY_HOME; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200458 case K_INS: key = VTERM_KEY_INS; break;
459 case K_K0: key = VTERM_KEY_KP_0; break;
460 case K_K1: key = VTERM_KEY_KP_1; break;
461 case K_K2: key = VTERM_KEY_KP_2; break;
462 case K_K3: key = VTERM_KEY_KP_3; break;
463 case K_K4: key = VTERM_KEY_KP_4; break;
464 case K_K5: key = VTERM_KEY_KP_5; break;
465 case K_K6: key = VTERM_KEY_KP_6; break;
466 case K_K7: key = VTERM_KEY_KP_7; break;
467 case K_K8: key = VTERM_KEY_KP_8; break;
468 case K_K9: key = VTERM_KEY_KP_9; break;
469 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
470 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
471 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
472 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
473 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
474 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
475 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
476 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
477 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
478 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
479 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
480 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
481 case K_LEFT: key = VTERM_KEY_LEFT; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200482 case K_S_LEFT: mod = VTERM_MOD_SHIFT;
483 key = VTERM_KEY_LEFT; break;
484 case K_C_LEFT: mod = VTERM_MOD_CTRL;
485 key = VTERM_KEY_LEFT; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200486 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
487 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
488 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200489 case K_S_RIGHT: mod = VTERM_MOD_SHIFT;
490 key = VTERM_KEY_RIGHT; break;
491 case K_C_RIGHT: mod = VTERM_MOD_CTRL;
492 key = VTERM_KEY_RIGHT; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200493 case K_UP: key = VTERM_KEY_UP; break;
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200494 case K_S_UP: mod = VTERM_MOD_SHIFT;
495 key = VTERM_KEY_UP; break;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200496 case TAB: key = VTERM_KEY_TAB; break;
Bram Moolenaare825d8b2017-07-19 23:20:19 +0200497
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200498 case K_MOUSEUP: mouse = term_send_mouse(vterm, 5, 1); break;
499 case K_MOUSEDOWN: mouse = term_send_mouse(vterm, 4, 1); break;
500 case K_MOUSELEFT: /* TODO */ return 0;
501 case K_MOUSERIGHT: /* TODO */ return 0;
Bram Moolenaare825d8b2017-07-19 23:20:19 +0200502
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200503 case K_LEFTMOUSE:
504 case K_LEFTMOUSE_NM: mouse = term_send_mouse(vterm, 1, 1); break;
505 case K_LEFTDRAG: mouse = term_send_mouse(vterm, 1, 1); break;
506 case K_LEFTRELEASE:
507 case K_LEFTRELEASE_NM: mouse = term_send_mouse(vterm, 1, 0); break;
508 case K_MIDDLEMOUSE: mouse = term_send_mouse(vterm, 2, 1); break;
509 case K_MIDDLEDRAG: mouse = term_send_mouse(vterm, 2, 1); break;
510 case K_MIDDLERELEASE: mouse = term_send_mouse(vterm, 2, 0); break;
511 case K_RIGHTMOUSE: mouse = term_send_mouse(vterm, 3, 1); break;
512 case K_RIGHTDRAG: mouse = term_send_mouse(vterm, 3, 1); break;
513 case K_RIGHTRELEASE: mouse = term_send_mouse(vterm, 3, 0); break;
514 case K_X1MOUSE: /* TODO */ return 0;
515 case K_X1DRAG: /* TODO */ return 0;
516 case K_X1RELEASE: /* TODO */ return 0;
517 case K_X2MOUSE: /* TODO */ return 0;
518 case K_X2DRAG: /* TODO */ return 0;
519 case K_X2RELEASE: /* TODO */ return 0;
Bram Moolenaare825d8b2017-07-19 23:20:19 +0200520
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200521 case K_IGNORE: return 0;
522 case K_NOP: return 0;
523 case K_UNDO: return 0;
524 case K_HELP: return 0;
525 case K_XF1: key = VTERM_KEY_FUNCTION(1); break;
526 case K_XF2: key = VTERM_KEY_FUNCTION(2); break;
527 case K_XF3: key = VTERM_KEY_FUNCTION(3); break;
528 case K_XF4: key = VTERM_KEY_FUNCTION(4); break;
529 case K_SELECT: return 0;
530#ifdef FEAT_GUI
531 case K_VER_SCROLLBAR: return 0;
532 case K_HOR_SCROLLBAR: return 0;
533#endif
534#ifdef FEAT_GUI_TABLINE
535 case K_TABLINE: return 0;
536 case K_TABMENU: return 0;
537#endif
538#ifdef FEAT_NETBEANS_INTG
539 case K_F21: key = VTERM_KEY_FUNCTION(21); break;
540#endif
541#ifdef FEAT_DND
542 case K_DROP: return 0;
543#endif
544#ifdef FEAT_AUTOCMD
545 case K_CURSORHOLD: return 0;
546#endif
547 case K_PS: vterm_keyboard_start_paste(vterm); return 0;
548 case K_PE: vterm_keyboard_end_paste(vterm); return 0;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200549 }
550
551 /*
552 * Convert special keys to vterm keys:
553 * - Write keys to vterm: vterm_keyboard_key()
554 * - Write output to channel.
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200555 * TODO: use mod_mask
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200556 */
557 if (key != VTERM_KEY_NONE)
558 /* Special key, let vterm convert it. */
559 vterm_keyboard_key(vterm, key, mod);
Bram Moolenaar6e1ef282017-07-29 22:23:40 +0200560 else if (!mouse)
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200561 /* Normal character, let vterm convert it. */
562 vterm_keyboard_unichar(vterm, c, mod);
563
564 /* Read back the converted escape sequence. */
Bram Moolenaara1b5b092017-07-26 21:29:34 +0200565 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN);
Bram Moolenaar8c0095c2017-07-18 22:53:21 +0200566}
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200567
Bram Moolenaar938783d2017-07-16 20:13:26 +0200568/*
Bram Moolenaarb000e322017-07-30 19:38:21 +0200569 * Return TRUE if the job for "term" is still running.
Bram Moolenaard85f2712017-07-28 21:51:57 +0200570 */
571 static int
572term_job_running(term_T *term)
573{
Bram Moolenaar1e8340b2017-07-29 15:53:39 +0200574 /* Also consider the job finished when the channel is closed, to avoid a
575 * race condition when updating the title. */
576 return term->tl_job != NULL
577 && term->tl_job->jv_status == JOB_STARTED
578 && channel_is_open(term->tl_job->jv_channel);
Bram Moolenaard85f2712017-07-28 21:51:57 +0200579}
580
581/*
Bram Moolenaar423802d2017-07-30 16:52:24 +0200582 * Add the last line of the scrollback buffer to the buffer in the window.
583 */
584 static void
585add_scrollback_line_to_buffer(term_T *term)
586{
587 linenr_T lnum = term->tl_scrollback.ga_len - 1;
588 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
589 garray_T ga;
590 int c;
591 int col;
592 int i;
593
594 ga_init2(&ga, 1, 100);
595 for (col = 0; col < line->sb_cols; col += line->sb_cells[col].width)
596 {
597 if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
598 goto failed;
599 for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i)
600 ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
601 (char_u *)ga.ga_data + ga.ga_len);
602 }
603 if (ga_grow(&ga, 1) == FAIL)
604 goto failed;
605 *((char_u *)ga.ga_data + ga.ga_len) = NUL;
606 ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE);
607
608 if (lnum == 0)
609 {
610 /* Delete the empty line that was in the empty buffer. */
611 curbuf = term->tl_buffer;
612 ml_delete(2, FALSE);
613 curbuf = curwin->w_buffer;
614 }
615
616failed:
617 ga_clear(&ga);
618}
619
620/*
621 * Add the current lines of the terminal to scrollback and to the buffer.
622 * Called after the job has ended and when switching to Terminal mode.
623 */
624 static void
625move_terminal_to_buffer(term_T *term)
626{
627 win_T *wp;
628 int len;
629 int lines_skipped = 0;
630 VTermPos pos;
631 VTermScreenCell cell;
632 VTermScreenCell *p;
633 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
634
635 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
636 {
637 len = 0;
638 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
639 if (vterm_screen_get_cell(screen, pos, &cell) != 0
640 && cell.chars[0] != NUL)
641 len = pos.col + 1;
642
643 if (len == 0)
644 ++lines_skipped;
645 else
646 {
647 while (lines_skipped > 0)
648 {
649 /* Line was skipped, add an empty line. */
650 --lines_skipped;
651 if (ga_grow(&term->tl_scrollback, 1) == OK)
652 {
653 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
654 + term->tl_scrollback.ga_len;
655
656 line->sb_cols = 0;
657 line->sb_cells = NULL;
658 ++term->tl_scrollback.ga_len;
659
660 add_scrollback_line_to_buffer(term);
661 }
662 }
663
664 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
665 if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK)
666 {
667 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
668 + term->tl_scrollback.ga_len;
669
670 for (pos.col = 0; pos.col < len; ++pos.col)
671 {
672 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
673 vim_memset(p + pos.col, 0, sizeof(cell));
674 else
675 p[pos.col] = cell;
676 }
677 line->sb_cols = len;
678 line->sb_cells = p;
679 ++term->tl_scrollback.ga_len;
680
681 add_scrollback_line_to_buffer(term);
682 }
683 else
684 vim_free(p);
685 }
686 }
687
688 FOR_ALL_WINDOWS(wp)
689 {
690 if (wp->w_buffer == term->tl_buffer)
691 {
692 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
693 wp->w_cursor.col = 0;
694 wp->w_valid = 0;
695 redraw_win_later(wp, NOT_VALID);
696 }
697 }
698}
699
700 static void
701set_terminal_mode(term_T *term, int on)
702{
703 term->tl_terminal_mode = on;
704 vim_free(term->tl_status_text);
705 term->tl_status_text = NULL;
706 if (term->tl_buffer == curbuf)
707 maketitle();
708}
709
710/*
711 * Called after the job if finished and Terminal mode is not active:
712 * Move the vterm contents into the scrollback buffer and free the vterm.
713 */
714 static void
715cleanup_vterm(term_T *term)
716{
717 move_terminal_to_buffer(term);
718 term_free_vterm(term);
719 set_terminal_mode(term, FALSE);
720}
721
722/*
723 * Switch from sending keys to the job to Terminal-Normal mode.
724 * Suspends updating the terminal window.
725 */
726 static void
727term_enter_terminal_mode()
728{
729 term_T *term = curbuf->b_term;
730
731 /* Append the current terminal contents to the buffer. */
732 move_terminal_to_buffer(term);
733
734 set_terminal_mode(term, TRUE);
735}
736
737/*
738 * Returns TRUE if the current window contains a terminal and we are in
739 * Terminal-Normal mode.
740 */
741 int
742term_in_terminal_mode()
743{
744 term_T *term = curbuf->b_term;
745
746 return term != NULL && term->tl_terminal_mode;
747}
748
749/*
750 * Switch from Terminal-Normal mode to sending keys to the job.
751 * Restores updating the terminal window.
752 */
753 void
754term_leave_terminal_mode()
755{
756 term_T *term = curbuf->b_term;
757 sb_line_T *line;
758 garray_T *gap;
759
760 /* Remove the terminal contents from the scrollback and the buffer. */
761 gap = &term->tl_scrollback;
762 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled)
763 {
764 ml_delete(curbuf->b_ml.ml_line_count, FALSE);
765 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1;
766 vim_free(line->sb_cells);
767 --gap->ga_len;
768 if (gap->ga_len == 0)
769 break;
770 }
771 check_cursor();
772
773 set_terminal_mode(term, FALSE);
774
775 if (term->tl_channel_closed)
776 cleanup_vterm(term);
777 redraw_buf_and_status_later(curbuf, NOT_VALID);
778}
779
780/*
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200781 * Get a key from the user without mapping.
782 * TODO: use terminal mode mappings.
783 */
784 static int
785term_vgetc()
786{
787 int c;
788
789 ++no_mapping;
790 ++allow_keys;
791 got_int = FALSE;
792 c = vgetc();
Bram Moolenaar43c007f2017-07-30 17:45:37 +0200793 got_int = FALSE;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200794 --no_mapping;
795 --allow_keys;
796 return c;
797}
798
799/*
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200800 * Send keys to terminal.
801 */
802 static int
803send_keys_to_term(term_T *term, int c, int typed)
804{
805 char msg[KEY_BUF_LEN];
806 size_t len;
807 static int mouse_was_outside = FALSE;
808 int dragging_outside = FALSE;
809
810 /* Catch keys that need to be handled as in Normal mode. */
811 switch (c)
812 {
813 case NUL:
814 case K_ZERO:
815 if (typed)
816 stuffcharReadbuff(c);
817 return FAIL;
818
819 case K_IGNORE:
820 return FAIL;
821
822 case K_LEFTDRAG:
823 case K_MIDDLEDRAG:
824 case K_RIGHTDRAG:
825 case K_X1DRAG:
826 case K_X2DRAG:
827 dragging_outside = mouse_was_outside;
828 /* FALLTHROUGH */
829 case K_LEFTMOUSE:
830 case K_LEFTMOUSE_NM:
831 case K_LEFTRELEASE:
832 case K_LEFTRELEASE_NM:
833 case K_MIDDLEMOUSE:
834 case K_MIDDLERELEASE:
835 case K_RIGHTMOUSE:
836 case K_RIGHTRELEASE:
837 case K_X1MOUSE:
838 case K_X1RELEASE:
839 case K_X2MOUSE:
840 case K_X2RELEASE:
841 if (mouse_row < W_WINROW(curwin)
842 || mouse_row >= (W_WINROW(curwin) + curwin->w_height)
843 || mouse_col < W_WINCOL(curwin)
844 || mouse_col >= W_ENDCOL(curwin)
845 || dragging_outside)
846 {
847 /* click outside the current window */
848 if (typed)
849 {
850 stuffcharReadbuff(c);
851 mouse_was_outside = TRUE;
852 }
853 return FAIL;
854 }
855 }
856 if (typed)
857 mouse_was_outside = FALSE;
858
859 /* Convert the typed key to a sequence of bytes for the job. */
860 len = term_convert_key(term, c, msg);
861 if (len > 0)
862 /* TODO: if FAIL is returned, stop? */
863 channel_send(term->tl_job->jv_channel, PART_IN,
864 (char_u *)msg, (int)len, NULL);
865
866 return OK;
867}
868
Bram Moolenaar0e23e9c2017-07-30 18:47:19 +0200869 static void
870position_cursor(win_T *wp, VTermPos *pos)
871{
872 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1));
873 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1));
874 wp->w_valid |= (VALID_WCOL|VALID_WROW);
875}
876
Bram Moolenaarc6df10e2017-07-29 20:15:08 +0200877/*
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200878 * Handle CTRL-W "": send register contents to the job.
879 */
880 static void
881term_paste_register(int prev_c UNUSED)
882{
883 int c;
884 list_T *l;
885 listitem_T *item;
886 long reglen = 0;
887 int type;
888
889#ifdef FEAT_CMDL_INFO
890 if (add_to_showcmd(prev_c))
891 if (add_to_showcmd('"'))
892 out_flush();
893#endif
894 c = term_vgetc();
895#ifdef FEAT_CMDL_INFO
896 clear_showcmd();
897#endif
898
899 /* CTRL-W "= prompt for expression to evaluate. */
900 if (c == '=' && get_expr_register() != '=')
901 return;
902
903 l = (list_T *)get_reg_contents(c, GREG_LIST);
904 if (l != NULL)
905 {
906 type = get_reg_type(c, &reglen);
907 for (item = l->lv_first; item != NULL; item = item->li_next)
908 {
909 char_u *s = get_tv_string(&item->li_tv);
910
911 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
912 s, STRLEN(s), NULL);
913 if (item->li_next != NULL || type == MLINE)
914 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
915 (char_u *)"\r", 1, NULL);
916 }
917 list_free(l);
918 }
919}
920
921/*
Bram Moolenaar423802d2017-07-30 16:52:24 +0200922 * Returns TRUE if the current window contains a terminal and we are sending
923 * keys to the job.
924 */
925 int
926term_use_loop()
927{
928 term_T *term = curbuf->b_term;
929
930 return term != NULL
931 && !term->tl_terminal_mode
932 && term->tl_vterm != NULL
933 && term_job_running(term);
934}
935
936/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200937 * Wait for input and send it to the job.
938 * Return when the start of a CTRL-W command is typed or anything else that
939 * should be handled as a Normal mode command.
Bram Moolenaard85f2712017-07-28 21:51:57 +0200940 * Returns OK if a typed character is to be handled in Normal mode, FAIL if
941 * the terminal was closed.
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200942 */
Bram Moolenaard85f2712017-07-28 21:51:57 +0200943 int
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200944terminal_loop(void)
945{
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200946 int c;
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200947 int termkey = 0;
948
949 if (*curwin->w_p_tk != NUL)
950 termkey = string_to_key(curwin->w_p_tk, TRUE);
Bram Moolenaar0e23e9c2017-07-30 18:47:19 +0200951 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200952
953 for (;;)
954 {
955 /* TODO: skip screen update when handling a sequence of keys. */
Bram Moolenaar43c007f2017-07-30 17:45:37 +0200956 /* Repeat redrawing in case a message is received while redrawing. */
957 while (curwin->w_redr_type != 0)
958 update_screen(0);
Bram Moolenaarfc716d72017-07-25 23:08:47 +0200959 update_cursor(curbuf->b_term, FALSE);
Bram Moolenaar423802d2017-07-30 16:52:24 +0200960
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200961 c = term_vgetc();
Bram Moolenaard85f2712017-07-28 21:51:57 +0200962 if (curbuf->b_term->tl_vterm == NULL
963 || !term_job_running(curbuf->b_term))
964 /* job finished while waiting for a character */
965 break;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +0200966
Bram Moolenaardbe948d2017-07-23 22:50:51 +0200967 if (c == (termkey == 0 ? Ctrl_W : termkey))
968 {
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200969 int prev_c = c;
970
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200971#ifdef FEAT_CMDL_INFO
972 if (add_to_showcmd(c))
973 out_flush();
974#endif
975 c = term_vgetc();
976#ifdef FEAT_CMDL_INFO
977 clear_showcmd();
978#endif
Bram Moolenaard85f2712017-07-28 21:51:57 +0200979 if (curbuf->b_term->tl_vterm == NULL
980 || !term_job_running(curbuf->b_term))
981 /* job finished while waiting for a character */
982 break;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200983
984 if (termkey == 0 && c == '.')
Bram Moolenaar423802d2017-07-30 16:52:24 +0200985 {
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200986 /* "CTRL-W .": send CTRL-W to the job */
987 c = Ctrl_W;
Bram Moolenaar423802d2017-07-30 16:52:24 +0200988 }
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200989 else if (c == 'N')
Bram Moolenaar423802d2017-07-30 16:52:24 +0200990 {
991 term_enter_terminal_mode();
992 return FAIL;
993 }
Bram Moolenaarc9456ce2017-07-30 21:46:04 +0200994 else if (c == '"')
995 {
996 term_paste_register(prev_c);
997 continue;
998 }
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +0200999 else if (termkey == 0 || c != termkey)
1000 {
1001 stuffcharReadbuff(Ctrl_W);
1002 stuffcharReadbuff(c);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001003 return OK;
Bram Moolenaar1f28b4c2017-07-28 13:48:34 +02001004 }
Bram Moolenaardbe948d2017-07-23 22:50:51 +02001005 }
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001006 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK)
1007 return OK;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001008 }
Bram Moolenaard85f2712017-07-28 21:51:57 +02001009 return FAIL;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001010}
1011
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001012/*
1013 * Called when a job has finished.
Bram Moolenaar423802d2017-07-30 16:52:24 +02001014 * This updates the title and status, but does not close the vter, because
1015 * there might still be pending output in the channel.
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001016 */
1017 void
1018term_job_ended(job_T *job)
1019{
1020 term_T *term;
1021 int did_one = FALSE;
1022
1023 for (term = first_term; term != NULL; term = term->tl_next)
1024 if (term->tl_job == job)
1025 {
1026 vim_free(term->tl_title);
1027 term->tl_title = NULL;
1028 vim_free(term->tl_status_text);
1029 term->tl_status_text = NULL;
1030 redraw_buf_and_status_later(term->tl_buffer, VALID);
1031 did_one = TRUE;
1032 }
1033 if (did_one)
1034 redraw_statuslines();
1035 if (curbuf->b_term != NULL)
1036 {
1037 if (curbuf->b_term->tl_job == job)
1038 maketitle();
1039 update_cursor(curbuf->b_term, TRUE);
1040 }
1041}
1042
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001043 static void
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001044may_toggle_cursor(term_T *term)
1045{
1046 if (curbuf == term->tl_buffer)
1047 {
1048 if (term->tl_cursor_visible)
1049 cursor_on();
1050 else
1051 cursor_off();
1052 }
1053}
1054
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001055 static int
1056handle_damage(VTermRect rect, void *user)
1057{
1058 term_T *term = (term_T *)user;
1059
1060 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
1061 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
1062 redraw_buf_later(term->tl_buffer, NOT_VALID);
1063 return 1;
1064}
1065
1066 static int
1067handle_moverect(VTermRect dest UNUSED, VTermRect src UNUSED, void *user)
1068{
1069 term_T *term = (term_T *)user;
1070
1071 /* TODO */
1072 redraw_buf_later(term->tl_buffer, NOT_VALID);
1073 return 1;
1074}
1075
1076 static int
1077handle_movecursor(
1078 VTermPos pos,
1079 VTermPos oldpos UNUSED,
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001080 int visible,
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001081 void *user)
1082{
1083 term_T *term = (term_T *)user;
1084 win_T *wp;
Bram Moolenaar22aad2f2017-07-30 18:19:46 +02001085
1086 term->tl_cursor_pos = pos;
1087 term->tl_cursor_visible = visible;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001088
1089 FOR_ALL_WINDOWS(wp)
1090 {
1091 if (wp->w_buffer == term->tl_buffer)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001092 position_cursor(wp, &pos);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001093 }
Bram Moolenaar392d1bf2017-07-31 21:18:58 +02001094 if (term->tl_buffer == curbuf && !term->tl_terminal_mode)
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001095 {
1096 may_toggle_cursor(term);
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001097 update_cursor(term, term->tl_cursor_visible);
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001098 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001099
1100 return 1;
1101}
1102
Bram Moolenaar21554412017-07-24 21:44:43 +02001103 static int
1104handle_settermprop(
1105 VTermProp prop,
1106 VTermValue *value,
1107 void *user)
1108{
1109 term_T *term = (term_T *)user;
1110
1111 switch (prop)
1112 {
1113 case VTERM_PROP_TITLE:
1114 vim_free(term->tl_title);
1115 term->tl_title = vim_strsave((char_u *)value->string);
1116 vim_free(term->tl_status_text);
1117 term->tl_status_text = NULL;
1118 if (term == curbuf->b_term)
1119 maketitle();
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001120 break;
1121
1122 case VTERM_PROP_CURSORVISIBLE:
1123 term->tl_cursor_visible = value->boolean;
1124 may_toggle_cursor(term);
1125 out_flush();
1126 break;
1127
Bram Moolenaar21554412017-07-24 21:44:43 +02001128 default:
1129 break;
1130 }
Bram Moolenaarfc716d72017-07-25 23:08:47 +02001131 /* Always return 1, otherwise vterm doesn't store the value internally. */
1132 return 1;
Bram Moolenaar21554412017-07-24 21:44:43 +02001133}
1134
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001135/*
1136 * The job running in the terminal resized the terminal.
1137 */
1138 static int
1139handle_resize(int rows, int cols, void *user)
1140{
1141 term_T *term = (term_T *)user;
1142 win_T *wp;
1143
1144 term->tl_rows = rows;
1145 term->tl_cols = cols;
1146 FOR_ALL_WINDOWS(wp)
1147 {
1148 if (wp->w_buffer == term->tl_buffer)
1149 {
1150 win_setheight_win(rows, wp);
1151 win_setwidth_win(cols, wp);
1152 }
1153 }
1154
1155 redraw_buf_later(term->tl_buffer, NOT_VALID);
1156 return 1;
1157}
1158
Bram Moolenaard85f2712017-07-28 21:51:57 +02001159/*
1160 * Handle a line that is pushed off the top of the screen.
1161 */
1162 static int
1163handle_pushline(int cols, const VTermScreenCell *cells, void *user)
1164{
1165 term_T *term = (term_T *)user;
1166
1167 /* TODO: Limit the number of lines that are stored. */
1168 /* TODO: put the text in the buffer. */
1169 if (ga_grow(&term->tl_scrollback, 1) == OK)
1170 {
Bram Moolenaar696d00f2017-07-29 14:52:43 +02001171 VTermScreenCell *p = NULL;
1172 int len = 0;
1173 int i;
1174 sb_line_T *line;
Bram Moolenaard85f2712017-07-28 21:51:57 +02001175
1176 /* do not store empty cells at the end */
1177 for (i = 0; i < cols; ++i)
1178 if (cells[i].chars[0] != 0)
1179 len = i + 1;
1180
Bram Moolenaar696d00f2017-07-29 14:52:43 +02001181 if (len > 0)
1182 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001183 if (p != NULL)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001184 mch_memmove(p, cells, sizeof(VTermScreenCell) * len);
Bram Moolenaar696d00f2017-07-29 14:52:43 +02001185
1186 line = (sb_line_T *)term->tl_scrollback.ga_data
1187 + term->tl_scrollback.ga_len;
1188 line->sb_cols = len;
1189 line->sb_cells = p;
1190 ++term->tl_scrollback.ga_len;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001191 ++term->tl_scrollback_scrolled;
Bram Moolenaar423802d2017-07-30 16:52:24 +02001192
1193 add_scrollback_line_to_buffer(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001194 }
1195 return 0; /* ignored */
1196}
1197
Bram Moolenaar21554412017-07-24 21:44:43 +02001198static VTermScreenCallbacks screen_callbacks = {
1199 handle_damage, /* damage */
1200 handle_moverect, /* moverect */
1201 handle_movecursor, /* movecursor */
1202 handle_settermprop, /* settermprop */
1203 NULL, /* bell */
1204 handle_resize, /* resize */
Bram Moolenaard85f2712017-07-28 21:51:57 +02001205 handle_pushline, /* sb_pushline */
Bram Moolenaar21554412017-07-24 21:44:43 +02001206 NULL /* sb_popline */
1207};
1208
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001209/*
Bram Moolenaard85f2712017-07-28 21:51:57 +02001210 * Called when a channel has been closed.
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001211 * If this was a channel for a terminal window then finish it up.
Bram Moolenaard85f2712017-07-28 21:51:57 +02001212 */
1213 void
1214term_channel_closed(channel_T *ch)
1215{
1216 term_T *term;
1217 int did_one = FALSE;
1218
1219 for (term = first_term; term != NULL; term = term->tl_next)
1220 if (term->tl_job == ch->ch_job)
1221 {
Bram Moolenaar423802d2017-07-30 16:52:24 +02001222 term->tl_channel_closed = TRUE;
1223
Bram Moolenaard85f2712017-07-28 21:51:57 +02001224 vim_free(term->tl_title);
1225 term->tl_title = NULL;
1226 vim_free(term->tl_status_text);
1227 term->tl_status_text = NULL;
1228
Bram Moolenaar423802d2017-07-30 16:52:24 +02001229 /* Unless in Terminal-Normal mode: clear the vterm. */
1230 if (!term->tl_terminal_mode)
1231 cleanup_vterm(term);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001232
1233 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
1234 did_one = TRUE;
1235 }
1236 if (did_one)
1237 {
1238 redraw_statuslines();
1239
1240 /* Need to break out of vgetc(). */
1241 ins_char_typebuf(K_IGNORE);
1242
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001243 term = curbuf->b_term;
1244 if (term != NULL)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001245 {
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001246 if (term->tl_job == ch->ch_job)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001247 maketitle();
Bram Moolenaar12d93ee2017-07-30 19:02:02 +02001248 update_cursor(term, term->tl_cursor_visible);
Bram Moolenaard85f2712017-07-28 21:51:57 +02001249 }
1250 }
1251}
1252
1253/*
Bram Moolenaareeac6772017-07-23 15:48:37 +02001254 * Reverse engineer the RGB value into a cterm color index.
1255 * First color is 1. Return 0 if no match found.
1256 */
1257 static int
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001258color2index(VTermColor *color, int fg, int *boldp)
Bram Moolenaareeac6772017-07-23 15:48:37 +02001259{
1260 int red = color->red;
1261 int blue = color->blue;
1262 int green = color->green;
1263
Bram Moolenaarb41bf8e2017-07-28 15:11:38 +02001264 /* The argument for lookup_color() is for the color_names[] table. */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001265 if (red == 0)
1266 {
1267 if (green == 0)
1268 {
1269 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001270 return lookup_color(0, fg, boldp) + 1; /* black */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001271 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001272 return lookup_color(1, fg, boldp) + 1; /* dark blue */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001273 }
1274 else if (green == 224)
1275 {
1276 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001277 return lookup_color(2, fg, boldp) + 1; /* dark green */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001278 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001279 return lookup_color(3, fg, boldp) + 1; /* dark cyan */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001280 }
1281 }
1282 else if (red == 224)
1283 {
1284 if (green == 0)
1285 {
1286 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001287 return lookup_color(4, fg, boldp) + 1; /* dark red */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001288 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001289 return lookup_color(5, fg, boldp) + 1; /* dark magenta */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001290 }
1291 else if (green == 224)
1292 {
1293 if (blue == 0)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001294 return lookup_color(6, fg, boldp) + 1; /* dark yellow / brown */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001295 if (blue == 224)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001296 return lookup_color(8, fg, boldp) + 1; /* white / light grey */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001297 }
1298 }
1299 else if (red == 128)
1300 {
1301 if (green == 128 && blue == 128)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001302 return lookup_color(12, fg, boldp) + 1; /* high intensity black / dark grey */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001303 }
1304 else if (red == 255)
1305 {
1306 if (green == 64)
1307 {
1308 if (blue == 64)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001309 return lookup_color(20, fg, boldp) + 1; /* light red */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001310 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001311 return lookup_color(22, fg, boldp) + 1; /* light magenta */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001312 }
1313 else if (green == 255)
1314 {
1315 if (blue == 64)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001316 return lookup_color(24, fg, boldp) + 1; /* yellow */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001317 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001318 return lookup_color(26, fg, boldp) + 1; /* white */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001319 }
1320 }
1321 else if (red == 64)
1322 {
1323 if (green == 64)
1324 {
1325 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001326 return lookup_color(14, fg, boldp) + 1; /* light blue */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001327 }
1328 else if (green == 255)
1329 {
1330 if (blue == 64)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001331 return lookup_color(16, fg, boldp) + 1; /* light green */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001332 if (blue == 255)
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001333 return lookup_color(18, fg, boldp) + 1; /* light cyan */
Bram Moolenaareeac6772017-07-23 15:48:37 +02001334 }
1335 }
1336 if (t_colors >= 256)
1337 {
1338 if (red == blue && red == green)
1339 {
1340 /* 24-color greyscale */
1341 static int cutoff[23] = {
1342 0x05, 0x10, 0x1B, 0x26, 0x31, 0x3C, 0x47, 0x52,
1343 0x5D, 0x68, 0x73, 0x7F, 0x8A, 0x95, 0xA0, 0xAB,
1344 0xB6, 0xC1, 0xCC, 0xD7, 0xE2, 0xED, 0xF9};
1345 int i;
1346
1347 for (i = 0; i < 23; ++i)
1348 if (red < cutoff[i])
1349 return i + 233;
1350 return 256;
1351 }
1352
1353 /* 216-color cube */
1354 return 17 + ((red + 25) / 0x33) * 36
1355 + ((green + 25) / 0x33) * 6
1356 + (blue + 25) / 0x33;
1357 }
1358 return 0;
1359}
1360
1361/*
1362 * Convert the attributes of a vterm cell into an attribute index.
1363 */
1364 static int
1365cell2attr(VTermScreenCell *cell)
1366{
1367 int attr = 0;
1368
1369 if (cell->attrs.bold)
1370 attr |= HL_BOLD;
1371 if (cell->attrs.underline)
1372 attr |= HL_UNDERLINE;
1373 if (cell->attrs.italic)
1374 attr |= HL_ITALIC;
1375 if (cell->attrs.strike)
1376 attr |= HL_STANDOUT;
1377 if (cell->attrs.reverse)
1378 attr |= HL_INVERSE;
Bram Moolenaareeac6772017-07-23 15:48:37 +02001379
1380#ifdef FEAT_GUI
1381 if (gui.in_use)
1382 {
Bram Moolenaar26af85d2017-07-23 16:45:10 +02001383 guicolor_T fg, bg;
1384
1385 fg = gui_mch_get_rgb_color(cell->fg.red, cell->fg.green, cell->fg.blue);
1386 bg = gui_mch_get_rgb_color(cell->bg.red, cell->bg.green, cell->bg.blue);
1387 return get_gui_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001388 }
1389 else
1390#endif
1391#ifdef FEAT_TERMGUICOLORS
1392 if (p_tgc)
1393 {
Bram Moolenaar065f41c2017-07-23 18:07:56 +02001394 guicolor_T fg, bg;
1395
1396 fg = gui_get_rgb_color_cmn(cell->fg.red, cell->fg.green, cell->fg.blue);
1397 bg = gui_get_rgb_color_cmn(cell->bg.red, cell->bg.green, cell->bg.blue);
1398
1399 return get_tgc_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001400 }
Bram Moolenaar065f41c2017-07-23 18:07:56 +02001401 else
Bram Moolenaareeac6772017-07-23 15:48:37 +02001402#endif
1403 {
Bram Moolenaar12d853f2017-08-01 18:04:04 +02001404 int bold = MAYBE;
1405 int fg = color2index(&cell->fg, TRUE, &bold);
1406 int bg = color2index(&cell->bg, FALSE, &bold);
1407
1408 /* with 8 colors set the bold attribute to get a bright foreground */
1409 if (bold == TRUE)
1410 attr |= HL_BOLD;
1411 return get_cterm_attr_idx(attr, fg, bg);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001412 }
1413 return 0;
1414}
1415
1416/*
Bram Moolenaard85f2712017-07-28 21:51:57 +02001417 * Called to update the window that contains a terminal.
1418 * Returns FAIL when there is no terminal running in this window.
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001419 */
Bram Moolenaard85f2712017-07-28 21:51:57 +02001420 int
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001421term_update_window(win_T *wp)
Bram Moolenaar938783d2017-07-16 20:13:26 +02001422{
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001423 term_T *term = wp->w_buffer->b_term;
Bram Moolenaard85f2712017-07-28 21:51:57 +02001424 VTerm *vterm;
1425 VTermScreen *screen;
1426 VTermState *state;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001427 VTermPos pos;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001428
Bram Moolenaar423802d2017-07-30 16:52:24 +02001429 if (term == NULL || term->tl_vterm == NULL || term->tl_terminal_mode)
Bram Moolenaard85f2712017-07-28 21:51:57 +02001430 return FAIL;
Bram Moolenaar423802d2017-07-30 16:52:24 +02001431
Bram Moolenaard85f2712017-07-28 21:51:57 +02001432 vterm = term->tl_vterm;
1433 screen = vterm_obtain_screen(vterm);
1434 state = vterm_obtain_state(vterm);
1435
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001436 /*
1437 * If the window was resized a redraw will be triggered and we get here.
1438 * Adjust the size of the vterm unless 'termsize' specifies a fixed size.
1439 */
1440 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height)
1441 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width))
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001442 {
Bram Moolenaar96ad8c92017-07-28 14:17:34 +02001443 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height;
1444 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width;
1445 win_T *twp;
1446
1447 FOR_ALL_WINDOWS(twp)
1448 {
1449 /* When more than one window shows the same terminal, use the
1450 * smallest size. */
1451 if (twp->w_buffer == term->tl_buffer)
1452 {
1453 if (!term->tl_rows_fixed && rows > twp->w_height)
1454 rows = twp->w_height;
1455 if (!term->tl_cols_fixed && cols > twp->w_width)
1456 cols = twp->w_width;
1457 }
1458 }
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001459
1460 vterm_set_size(vterm, rows, cols);
1461 ch_logn(term->tl_job->jv_channel, "Resizing terminal to %d lines",
1462 rows);
Bram Moolenaar43da3e32017-07-23 17:27:54 +02001463 term_report_winsize(term, rows, cols);
Bram Moolenaarb13501f2017-07-22 22:32:56 +02001464 }
Bram Moolenaar58556cd2017-07-20 23:04:46 +02001465
1466 /* The cursor may have been moved when resizing. */
1467 vterm_state_get_cursorpos(state, &pos);
1468 position_cursor(wp, &pos);
1469
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001470 /* TODO: Only redraw what changed. */
1471 for (pos.row = 0; pos.row < wp->w_height; ++pos.row)
Bram Moolenaar938783d2017-07-16 20:13:26 +02001472 {
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001473 int off = screen_get_current_line_off();
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001474 int max_col = MIN(wp->w_width, term->tl_cols);
Bram Moolenaar938783d2017-07-16 20:13:26 +02001475
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001476 if (pos.row < term->tl_rows)
1477 {
1478 for (pos.col = 0; pos.col < max_col; )
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001479 {
1480 VTermScreenCell cell;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001481 int c;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001482
Bram Moolenaareeac6772017-07-23 15:48:37 +02001483 if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1484 vim_memset(&cell, 0, sizeof(cell));
1485
1486 /* TODO: composing chars */
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001487 c = cell.chars[0];
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001488 if (c == NUL)
1489 {
1490 ScreenLines[off] = ' ';
Bram Moolenaar8a773062017-07-24 22:29:21 +02001491#if defined(FEAT_MBYTE)
1492 if (enc_utf8)
1493 ScreenLinesUC[off] = NUL;
1494#endif
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001495 }
1496 else
1497 {
1498#if defined(FEAT_MBYTE)
1499 if (enc_utf8 && c >= 0x80)
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001500 {
1501 ScreenLines[off] = ' ';
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001502 ScreenLinesUC[off] = c;
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001503 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001504 else
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001505 {
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001506 ScreenLines[off] = c;
Bram Moolenaar8a773062017-07-24 22:29:21 +02001507 if (enc_utf8)
1508 ScreenLinesUC[off] = NUL;
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001509 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001510#else
1511 ScreenLines[off] = c;
1512#endif
1513 }
Bram Moolenaareeac6772017-07-23 15:48:37 +02001514 ScreenAttrs[off] = cell2attr(&cell);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001515
1516 ++pos.col;
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001517 ++off;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001518 if (cell.width == 2)
1519 {
Bram Moolenaar9f1f49b2017-07-22 18:14:17 +02001520 ScreenLines[off] = NUL;
Bram Moolenaar8a773062017-07-24 22:29:21 +02001521#if defined(FEAT_MBYTE)
1522 if (enc_utf8)
1523 ScreenLinesUC[off] = NUL;
1524#endif
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001525 ++pos.col;
1526 ++off;
1527 }
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02001528 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001529 }
Bram Moolenaare825d8b2017-07-19 23:20:19 +02001530 else
1531 pos.col = 0;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001532
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001533 screen_line(wp->w_winrow + pos.row, wp->w_wincol,
1534 pos.col, wp->w_width, FALSE);
Bram Moolenaar938783d2017-07-16 20:13:26 +02001535 }
Bram Moolenaard85f2712017-07-28 21:51:57 +02001536
1537 return OK;
Bram Moolenaar938783d2017-07-16 20:13:26 +02001538}
Bram Moolenaare4f25e42017-07-07 11:54:15 +02001539
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001540/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001541 * Return TRUE if "wp" is a terminal window where the job has finished.
1542 */
1543 int
1544term_is_finished(buf_T *buf)
1545{
1546 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL;
1547}
1548
1549/*
Bram Moolenaar423802d2017-07-30 16:52:24 +02001550 * Return TRUE if "wp" is a terminal window where the job has finished or we
1551 * are in Terminal-Normal mode.
1552 */
1553 int
1554term_show_buffer(buf_T *buf)
1555{
1556 term_T *term = buf->b_term;
1557
1558 return term != NULL && (term->tl_vterm == NULL || term->tl_terminal_mode);
1559}
1560
1561/*
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001562 * The current buffer is going to be changed. If there is terminal
1563 * highlighting remove it now.
1564 */
1565 void
1566term_change_in_curbuf(void)
1567{
1568 term_T *term = curbuf->b_term;
1569
1570 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0)
1571 {
1572 free_scrollback(term);
1573 redraw_buf_later(term->tl_buffer, NOT_VALID);
1574 }
1575}
1576
1577/*
1578 * Get the screen attribute for a position in the buffer.
1579 */
1580 int
1581term_get_attr(buf_T *buf, linenr_T lnum, int col)
1582{
1583 term_T *term = buf->b_term;
1584 sb_line_T *line;
1585
Bram Moolenaar70229f92017-07-29 16:01:53 +02001586 if (lnum > term->tl_scrollback.ga_len)
Bram Moolenaar63ecdda2017-07-28 22:29:35 +02001587 return 0;
1588 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1;
1589 if (col >= line->sb_cols)
1590 return 0;
1591 return cell2attr(line->sb_cells + col);
1592}
1593
1594/*
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001595 * Set job options common for Unix and MS-Windows.
1596 */
1597 static void
1598setup_job_options(jobopt_T *opt, int rows, int cols)
1599{
1600 clear_job_options(opt);
1601 opt->jo_mode = MODE_RAW;
1602 opt->jo_out_mode = MODE_RAW;
1603 opt->jo_err_mode = MODE_RAW;
1604 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001605
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001606 opt->jo_io[PART_OUT] = JIO_BUFFER;
1607 opt->jo_io[PART_ERR] = JIO_BUFFER;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001608 opt->jo_set |= JO_OUT_IO + JO_ERR_IO;
1609
1610 opt->jo_modifiable[PART_OUT] = 0;
1611 opt->jo_modifiable[PART_ERR] = 0;
1612 opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE;
1613
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001614 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum;
1615 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum;
Bram Moolenaar5a1feb82017-07-22 18:04:08 +02001616 opt->jo_pty = TRUE;
Bram Moolenaar1f2903c2017-07-23 19:51:01 +02001617 opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF;
1618
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001619 opt->jo_term_rows = rows;
1620 opt->jo_term_cols = cols;
1621}
1622
1623/*
1624 * Create a new vterm and initialize it.
1625 */
1626 static void
1627create_vterm(term_T *term, int rows, int cols)
1628{
1629 VTerm *vterm;
1630 VTermScreen *screen;
1631
1632 vterm = vterm_new(rows, cols);
1633 term->tl_vterm = vterm;
1634 screen = vterm_obtain_screen(vterm);
1635 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
1636 /* TODO: depends on 'encoding'. */
1637 vterm_set_utf8(vterm, 1);
Bram Moolenaareeac6772017-07-23 15:48:37 +02001638
1639 /* Vterm uses a default black background. Set it to white when
1640 * 'background' is "light". */
1641 if (*p_bg == 'l')
1642 {
1643 VTermColor fg, bg;
1644
1645 fg.red = fg.green = fg.blue = 0;
1646 bg.red = bg.green = bg.blue = 255;
1647 vterm_state_set_default_colors(vterm_obtain_state(vterm), &fg, &bg);
1648 }
1649
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02001650 /* Required to initialize most things. */
1651 vterm_screen_reset(screen, 1 /* hard */);
1652}
1653
Bram Moolenaar21554412017-07-24 21:44:43 +02001654/*
1655 * Return the text to show for the buffer name and status.
1656 */
1657 char_u *
1658term_get_status_text(term_T *term)
1659{
1660 if (term->tl_status_text == NULL)
1661 {
1662 char_u *txt;
1663 size_t len;
1664
Bram Moolenaar423802d2017-07-30 16:52:24 +02001665 if (term->tl_terminal_mode)
1666 {
1667 if (term_job_running(term))
1668 txt = (char_u *)_("Terminal");
1669 else
1670 txt = (char_u *)_("Terminal-finished");
1671 }
1672 else if (term->tl_title != NULL)
Bram Moolenaar21554412017-07-24 21:44:43 +02001673 txt = term->tl_title;
1674 else if (term_job_running(term))
1675 txt = (char_u *)_("running");
1676 else
1677 txt = (char_u *)_("finished");
1678 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt);
Bram Moolenaara1b5b092017-07-26 21:29:34 +02001679 term->tl_status_text = alloc((int)len);
Bram Moolenaar21554412017-07-24 21:44:43 +02001680 if (term->tl_status_text != NULL)
1681 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]",
1682 term->tl_buffer->b_fname, txt);
1683 }
1684 return term->tl_status_text;
1685}
1686
Bram Moolenaarf86eea92017-07-28 13:51:30 +02001687/*
1688 * Mark references in jobs of terminals.
1689 */
1690 int
1691set_ref_in_term(int copyID)
1692{
1693 int abort = FALSE;
1694 term_T *term;
1695 typval_T tv;
1696
1697 for (term = first_term; term != NULL; term = term->tl_next)
1698 if (term->tl_job != NULL)
1699 {
1700 tv.v_type = VAR_JOB;
1701 tv.vval.v_job = term->tl_job;
1702 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL);
1703 }
1704 return abort;
1705}
1706
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001707/*
Bram Moolenaar97870002017-07-30 18:28:38 +02001708 * Get the buffer from the first argument in "argvars".
1709 * Returns NULL when the buffer is not for a terminal window.
1710 */
1711 static buf_T *
1712term_get_buf(typval_T *argvars)
1713{
1714 buf_T *buf;
1715
1716 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */
1717 ++emsg_off;
1718 buf = get_buf_tv(&argvars[0], FALSE);
1719 --emsg_off;
1720 if (buf == NULL || buf->b_term == NULL)
1721 return NULL;
1722 return buf;
1723}
1724
1725/*
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001726 * "term_getattr(attr, name)" function
1727 */
1728 void
1729f_term_getattr(typval_T *argvars, typval_T *rettv)
1730{
1731 int attr;
1732 size_t i;
1733 char_u *name;
1734
1735 static struct {
1736 char *name;
1737 int attr;
1738 } attrs[] = {
1739 {"bold", HL_BOLD},
1740 {"italic", HL_ITALIC},
1741 {"underline", HL_UNDERLINE},
1742 {"strike", HL_STANDOUT},
1743 {"reverse", HL_INVERSE},
1744 };
1745
1746 attr = get_tv_number(&argvars[0]);
1747 name = get_tv_string_chk(&argvars[1]);
1748 if (name == NULL)
1749 return;
1750
1751 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i)
1752 if (STRCMP(name, attrs[i].name) == 0)
1753 {
1754 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0;
1755 break;
1756 }
1757}
1758
1759/*
Bram Moolenaar97870002017-07-30 18:28:38 +02001760 * "term_getcursor(buf)" function
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001761 */
Bram Moolenaar97870002017-07-30 18:28:38 +02001762 void
1763f_term_getcursor(typval_T *argvars, typval_T *rettv)
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001764{
Bram Moolenaar97870002017-07-30 18:28:38 +02001765 buf_T *buf = term_get_buf(argvars);
1766 list_T *l;
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001767
Bram Moolenaar97870002017-07-30 18:28:38 +02001768 if (rettv_list_alloc(rettv) == FAIL)
1769 return;
1770 if (buf == NULL)
1771 return;
1772
1773 l = rettv->vval.v_list;
Bram Moolenaarc2ce52c2017-08-01 18:35:38 +02001774 list_append_number(l, buf->b_term->tl_cursor_pos.row + 1);
1775 list_append_number(l, buf->b_term->tl_cursor_pos.col + 1);
Bram Moolenaar97870002017-07-30 18:28:38 +02001776 list_append_number(l, buf->b_term->tl_cursor_visible);
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001777}
1778
1779/*
1780 * "term_getjob(buf)" function
1781 */
1782 void
1783f_term_getjob(typval_T *argvars, typval_T *rettv)
1784{
1785 buf_T *buf = term_get_buf(argvars);
1786
1787 rettv->v_type = VAR_JOB;
1788 rettv->vval.v_job = NULL;
1789 if (buf == NULL)
1790 return;
1791
1792 rettv->vval.v_job = buf->b_term->tl_job;
1793 if (rettv->vval.v_job != NULL)
1794 ++rettv->vval.v_job->jv_refcount;
1795}
1796
Bram Moolenaarc2ce52c2017-08-01 18:35:38 +02001797 static int
1798get_row_number(typval_T *tv, term_T *term)
1799{
1800 if (tv->v_type == VAR_STRING
1801 && tv->vval.v_string != NULL
1802 && STRCMP(tv->vval.v_string, ".") == 0)
1803 return term->tl_cursor_pos.row;
1804 return (int)get_tv_number(tv) - 1;
1805}
1806
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001807/*
1808 * "term_getline(buf, row)" function
1809 */
1810 void
1811f_term_getline(typval_T *argvars, typval_T *rettv)
1812{
1813 buf_T *buf = term_get_buf(argvars);
1814 term_T *term;
1815 int row;
1816
1817 rettv->v_type = VAR_STRING;
1818 if (buf == NULL)
1819 return;
1820 term = buf->b_term;
Bram Moolenaarc2ce52c2017-08-01 18:35:38 +02001821 row = get_row_number(&argvars[1], term);
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001822
1823 if (term->tl_vterm == NULL)
1824 {
1825 linenr_T lnum = row + term->tl_scrollback_scrolled + 1;
1826
1827 /* vterm is finished, get the text from the buffer */
1828 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count)
1829 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE));
1830 }
1831 else
1832 {
1833 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
1834 VTermRect rect;
1835 int len;
1836 char_u *p;
1837
1838 len = term->tl_cols * MB_MAXBYTES + 1;
1839 p = alloc(len);
1840 if (p == NULL)
1841 return;
1842 rettv->vval.v_string = p;
1843
1844 rect.start_col = 0;
1845 rect.end_col = term->tl_cols;
1846 rect.start_row = row;
1847 rect.end_row = row + 1;
1848 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL;
1849 }
1850}
1851
1852/*
1853 * "term_getsize(buf)" function
1854 */
1855 void
1856f_term_getsize(typval_T *argvars, typval_T *rettv)
1857{
1858 buf_T *buf = term_get_buf(argvars);
1859 list_T *l;
1860
1861 if (rettv_list_alloc(rettv) == FAIL)
1862 return;
1863 if (buf == NULL)
1864 return;
1865
1866 l = rettv->vval.v_list;
1867 list_append_number(l, buf->b_term->tl_rows);
1868 list_append_number(l, buf->b_term->tl_cols);
1869}
1870
1871/*
Bram Moolenaarb000e322017-07-30 19:38:21 +02001872 * "term_getstatus(buf)" function
1873 */
1874 void
1875f_term_getstatus(typval_T *argvars, typval_T *rettv)
1876{
1877 buf_T *buf = term_get_buf(argvars);
1878 term_T *term;
1879 char_u val[100];
1880
1881 rettv->v_type = VAR_STRING;
1882 if (buf == NULL)
1883 return;
1884 term = buf->b_term;
1885
1886 if (term_job_running(term))
1887 STRCPY(val, "running");
1888 else
1889 STRCPY(val, "finished");
1890 if (term->tl_terminal_mode)
1891 STRCAT(val, ",terminal");
1892 rettv->vval.v_string = vim_strsave(val);
1893}
1894
1895/*
1896 * "term_gettitle(buf)" function
1897 */
1898 void
1899f_term_gettitle(typval_T *argvars, typval_T *rettv)
1900{
1901 buf_T *buf = term_get_buf(argvars);
1902
1903 rettv->v_type = VAR_STRING;
1904 if (buf == NULL)
1905 return;
1906
1907 if (buf->b_term->tl_title != NULL)
1908 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title);
1909}
1910
1911/*
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001912 * "term_list()" function
1913 */
1914 void
1915f_term_list(typval_T *argvars UNUSED, typval_T *rettv)
1916{
1917 term_T *tp;
1918 list_T *l;
1919
1920 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL)
1921 return;
1922
1923 l = rettv->vval.v_list;
1924 for (tp = first_term; tp != NULL; tp = tp->tl_next)
1925 if (tp != NULL && tp->tl_buffer != NULL)
1926 if (list_append_number(l,
1927 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL)
1928 return;
1929}
1930
1931/*
1932 * "term_scrape(buf, row)" function
1933 */
1934 void
1935f_term_scrape(typval_T *argvars, typval_T *rettv)
1936{
1937 buf_T *buf = term_get_buf(argvars);
1938 VTermScreen *screen = NULL;
1939 VTermPos pos;
1940 list_T *l;
1941 term_T *term;
1942
1943 if (rettv_list_alloc(rettv) == FAIL)
1944 return;
1945 if (buf == NULL)
1946 return;
1947 term = buf->b_term;
1948 if (term->tl_vterm != NULL)
1949 screen = vterm_obtain_screen(term->tl_vterm);
1950
1951 l = rettv->vval.v_list;
Bram Moolenaarc2ce52c2017-08-01 18:35:38 +02001952 pos.row = get_row_number(&argvars[1], term);
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02001953 for (pos.col = 0; pos.col < term->tl_cols; )
1954 {
1955 dict_T *dcell;
1956 VTermScreenCell cell;
1957 char_u rgb[8];
1958 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1];
1959 int off = 0;
1960 int i;
1961
1962 if (screen == NULL)
1963 {
1964 linenr_T lnum = pos.row + term->tl_scrollback_scrolled;
1965 sb_line_T *line;
1966
1967 /* vterm has finished, get the cell from scrollback */
1968 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len)
1969 break;
1970 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
1971 if (pos.col >= line->sb_cols)
1972 break;
1973 cell = line->sb_cells[pos.col];
1974 }
1975 else if (vterm_screen_get_cell(screen, pos, &cell) == 0)
1976 break;
1977 dcell = dict_alloc();
1978 list_append_dict(l, dcell);
1979
1980 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i)
1981 {
1982 if (cell.chars[i] == 0)
1983 break;
1984 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off);
1985 }
1986 mbs[off] = NUL;
1987 dict_add_nr_str(dcell, "chars", 0, mbs);
1988
1989 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
1990 cell.fg.red, cell.fg.green, cell.fg.blue);
1991 dict_add_nr_str(dcell, "fg", 0, rgb);
1992 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x",
1993 cell.bg.red, cell.bg.green, cell.bg.blue);
1994 dict_add_nr_str(dcell, "bg", 0, rgb);
1995
1996 dict_add_nr_str(dcell, "attr", cell2attr(&cell), NULL);
1997 dict_add_nr_str(dcell, "width", cell.width, NULL);
1998
1999 ++pos.col;
2000 if (cell.width == 2)
2001 ++pos.col;
2002 }
2003}
2004
2005/*
2006 * "term_sendkeys(buf, keys)" function
2007 */
2008 void
2009f_term_sendkeys(typval_T *argvars, typval_T *rettv)
2010{
2011 buf_T *buf = term_get_buf(argvars);
2012 char_u *msg;
2013 term_T *term;
2014
2015 rettv->v_type = VAR_UNKNOWN;
2016 if (buf == NULL)
2017 return;
2018
2019 msg = get_tv_string_chk(&argvars[1]);
2020 if (msg == NULL)
2021 return;
2022 term = buf->b_term;
2023 if (term->tl_vterm == NULL)
2024 return;
2025
2026 while (*msg != NUL)
2027 {
2028 send_keys_to_term(term, PTR2CHAR(msg), FALSE);
2029 msg += MB_PTR2LEN(msg);
2030 }
2031
Bram Moolenaar392d1bf2017-07-31 21:18:58 +02002032 if (!term->tl_terminal_mode)
2033 {
2034 /* TODO: only update once in a while. */
2035 update_screen(0);
2036 if (buf == curbuf)
2037 update_cursor(term, TRUE);
2038 }
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02002039}
2040
2041/*
2042 * "term_start(command, options)" function
2043 */
2044 void
2045f_term_start(typval_T *argvars, typval_T *rettv)
2046{
2047 char_u *cmd = get_tv_string_chk(&argvars[0]);
2048 exarg_T ea;
2049
2050 if (cmd == NULL)
2051 return;
2052 ea.arg = cmd;
2053 ex_terminal(&ea);
2054
2055 if (curbuf->b_term != NULL)
2056 rettv->vval.v_number = curbuf->b_fnum;
2057}
2058
2059/*
2060 * "term_wait" function
2061 */
2062 void
2063f_term_wait(typval_T *argvars, typval_T *rettv UNUSED)
2064{
2065 buf_T *buf = term_get_buf(argvars);
2066
2067 if (buf == NULL)
2068 return;
2069
2070 /* Get the job status, this will detect a job that finished. */
2071 if (buf->b_term->tl_job != NULL)
2072 (void)job_status(buf->b_term->tl_job);
2073
2074 /* Check for any pending channel I/O. */
2075 vpeekc_any();
2076 ui_delay(10L, FALSE);
2077
2078 /* Flushing messages on channels is hopefully sufficient.
2079 * TODO: is there a better way? */
2080 parse_queued_messages();
2081}
2082
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002083# ifdef WIN3264
2084
Bram Moolenaarc6df10e2017-07-29 20:15:08 +02002085/**************************************
2086 * 2. MS-Windows implementation.
2087 */
2088
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002089#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul
2090#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
2091
Bram Moolenaar8a773062017-07-24 22:29:21 +02002092void* (*winpty_config_new)(UINT64, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002093void* (*winpty_open)(void*, void*);
Bram Moolenaar8a773062017-07-24 22:29:21 +02002094void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002095BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*);
2096void (*winpty_config_set_initial_size)(void*, int, int);
2097LPCWSTR (*winpty_conin_name)(void*);
2098LPCWSTR (*winpty_conout_name)(void*);
2099LPCWSTR (*winpty_conerr_name)(void*);
2100void (*winpty_free)(void*);
2101void (*winpty_config_free)(void*);
2102void (*winpty_spawn_config_free)(void*);
2103void (*winpty_error_free)(void*);
2104LPCWSTR (*winpty_error_msg)(void*);
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002105BOOL (*winpty_set_size)(void*, int, int, void*);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002106
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002107#define WINPTY_DLL "winpty.dll"
2108
2109static HINSTANCE hWinPtyDLL = NULL;
2110
2111 int
2112dyn_winpty_init(void)
2113{
2114 int i;
2115 static struct
2116 {
2117 char *name;
2118 FARPROC *ptr;
2119 } winpty_entry[] =
2120 {
2121 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name},
2122 {"winpty_config_free", (FARPROC*)&winpty_config_free},
2123 {"winpty_config_new", (FARPROC*)&winpty_config_new},
2124 {"winpty_config_set_initial_size", (FARPROC*)&winpty_config_set_initial_size},
2125 {"winpty_conin_name", (FARPROC*)&winpty_conin_name},
2126 {"winpty_conout_name", (FARPROC*)&winpty_conout_name},
2127 {"winpty_error_free", (FARPROC*)&winpty_error_free},
2128 {"winpty_free", (FARPROC*)&winpty_free},
2129 {"winpty_open", (FARPROC*)&winpty_open},
2130 {"winpty_spawn", (FARPROC*)&winpty_spawn},
2131 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free},
2132 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new},
2133 {"winpty_error_msg", (FARPROC*)&winpty_error_msg},
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002134 {"winpty_set_size", (FARPROC*)&winpty_set_size},
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002135 {NULL, NULL}
2136 };
2137
2138 /* No need to initialize twice. */
2139 if (hWinPtyDLL)
2140 return 1;
2141 /* Load winpty.dll */
2142 hWinPtyDLL = vimLoadLib(WINPTY_DLL);
2143 if (!hWinPtyDLL)
2144 {
2145 EMSG2(_(e_loadlib), WINPTY_DLL);
2146 return 0;
2147 }
2148 for (i = 0; winpty_entry[i].name != NULL
2149 && winpty_entry[i].ptr != NULL; ++i)
2150 {
2151 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL,
2152 winpty_entry[i].name)) == NULL)
2153 {
2154 EMSG2(_(e_loadfunc), winpty_entry[i].name);
2155 return 0;
2156 }
2157 }
2158
2159 return 1;
2160}
2161
2162/*
2163 * Create a new terminal of "rows" by "cols" cells.
2164 * Store a reference in "term".
2165 * Return OK or FAIL.
2166 */
2167 static int
2168term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
2169{
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002170 WCHAR *p;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002171 channel_T *channel = NULL;
2172 job_T *job = NULL;
2173 jobopt_T opt;
2174 DWORD error;
2175 HANDLE jo = NULL, child_process_handle, child_thread_handle;
2176 void *winpty_err;
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002177 void *spawn_config = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002178
2179 if (!dyn_winpty_init())
2180 return FAIL;
2181
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002182 p = enc_to_utf16(cmd, NULL);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002183 if (p == NULL)
2184 return FAIL;
2185
2186 job = job_alloc();
2187 if (job == NULL)
2188 goto failed;
2189
2190 channel = add_channel();
2191 if (channel == NULL)
2192 goto failed;
2193
2194 term->tl_winpty_config = winpty_config_new(0, &winpty_err);
2195 if (term->tl_winpty_config == NULL)
2196 goto failed;
2197
2198 winpty_config_set_initial_size(term->tl_winpty_config, cols, rows);
2199 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err);
2200 if (term->tl_winpty == NULL)
2201 goto failed;
2202
2203 spawn_config = winpty_spawn_config_new(
2204 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN |
2205 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN,
2206 NULL,
2207 p,
2208 NULL,
2209 NULL,
2210 &winpty_err);
2211 if (spawn_config == NULL)
2212 goto failed;
2213
2214 channel = add_channel();
2215 if (channel == NULL)
2216 goto failed;
2217
2218 job = job_alloc();
2219 if (job == NULL)
2220 goto failed;
2221
2222 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle,
2223 &child_thread_handle, &error, &winpty_err))
2224 goto failed;
2225
2226 channel_set_pipes(channel,
2227 (sock_T) CreateFileW(
2228 winpty_conin_name(term->tl_winpty),
2229 GENERIC_WRITE, 0, NULL,
2230 OPEN_EXISTING, 0, NULL),
2231 (sock_T) CreateFileW(
2232 winpty_conout_name(term->tl_winpty),
2233 GENERIC_READ, 0, NULL,
2234 OPEN_EXISTING, 0, NULL),
2235 (sock_T) CreateFileW(
2236 winpty_conerr_name(term->tl_winpty),
2237 GENERIC_READ, 0, NULL,
2238 OPEN_EXISTING, 0, NULL));
2239
2240 jo = CreateJobObject(NULL, NULL);
2241 if (jo == NULL)
2242 goto failed;
2243
2244 if (!AssignProcessToJobObject(jo, child_process_handle))
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002245 {
2246 /* Failed, switch the way to terminate process with TerminateProcess. */
2247 CloseHandle(jo);
2248 jo = NULL;
2249 }
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002250
2251 winpty_spawn_config_free(spawn_config);
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002252 vim_free(p);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002253
2254 create_vterm(term, rows, cols);
2255
2256 setup_job_options(&opt, rows, cols);
2257 channel_set_job(channel, job, &opt);
2258
2259 job->jv_channel = channel;
2260 job->jv_proc_info.hProcess = child_process_handle;
2261 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle);
2262 job->jv_job_object = jo;
2263 job->jv_status = JOB_STARTED;
Bram Moolenaar0e83f022017-07-27 22:07:35 +02002264 ++job->jv_refcount;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002265 term->tl_job = job;
2266
2267 return OK;
2268
2269failed:
Bram Moolenaarab6eec32017-07-27 21:46:43 +02002270 if (spawn_config != NULL)
2271 winpty_spawn_config_free(spawn_config);
2272 vim_free(p);
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002273 if (channel != NULL)
2274 channel_clear(channel);
2275 if (job != NULL)
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002276 {
2277 job->jv_channel = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002278 job_cleanup(job);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002279 }
2280 term->tl_job = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002281 if (jo != NULL)
2282 CloseHandle(jo);
2283 if (term->tl_winpty != NULL)
2284 winpty_free(term->tl_winpty);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002285 term->tl_winpty = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002286 if (term->tl_winpty_config != NULL)
2287 winpty_config_free(term->tl_winpty_config);
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002288 term->tl_winpty_config = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002289 if (winpty_err != NULL)
2290 {
2291 char_u *msg = utf16_to_enc(
2292 (short_u *)winpty_error_msg(winpty_err), NULL);
2293
2294 EMSG(msg);
2295 winpty_error_free(winpty_err);
2296 }
2297 return FAIL;
2298}
2299
2300/*
2301 * Free the terminal emulator part of "term".
2302 */
2303 static void
Bram Moolenaard85f2712017-07-28 21:51:57 +02002304term_free_vterm(term_T *term)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002305{
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002306 if (term->tl_winpty != NULL)
2307 winpty_free(term->tl_winpty);
Bram Moolenaard85f2712017-07-28 21:51:57 +02002308 term->tl_winpty = NULL;
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002309 if (term->tl_winpty_config != NULL)
2310 winpty_config_free(term->tl_winpty_config);
Bram Moolenaard85f2712017-07-28 21:51:57 +02002311 term->tl_winpty_config = NULL;
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002312 if (term->tl_vterm != NULL)
2313 vterm_free(term->tl_vterm);
Bram Moolenaardcbfa332017-07-28 23:16:13 +02002314 term->tl_vterm = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002315}
2316
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002317/*
2318 * Request size to terminal.
2319 */
2320 static void
2321term_report_winsize(term_T *term, int rows, int cols)
2322{
2323 winpty_set_size(term->tl_winpty, cols, rows, NULL);
2324}
2325
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002326# else
2327
2328/**************************************
2329 * 3. Unix-like implementation.
2330 */
2331
2332/*
2333 * Create a new terminal of "rows" by "cols" cells.
2334 * Start job for "cmd".
2335 * Store the pointers in "term".
2336 * Return OK or FAIL.
2337 */
2338 static int
2339term_and_job_init(term_T *term, int rows, int cols, char_u *cmd)
2340{
2341 typval_T argvars[2];
2342 jobopt_T opt;
2343
2344 create_vterm(term, rows, cols);
2345
2346 argvars[0].v_type = VAR_STRING;
2347 argvars[0].vval.v_string = cmd;
2348 setup_job_options(&opt, rows, cols);
2349 term->tl_job = job_start(argvars, &opt);
Bram Moolenaar0e83f022017-07-27 22:07:35 +02002350 if (term->tl_job != NULL)
2351 ++term->tl_job->jv_refcount;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002352
Bram Moolenaar61a66052017-07-22 18:39:00 +02002353 return term->tl_job != NULL
2354 && term->tl_job->jv_channel != NULL
2355 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002356}
2357
2358/*
2359 * Free the terminal emulator part of "term".
2360 */
2361 static void
Bram Moolenaard85f2712017-07-28 21:51:57 +02002362term_free_vterm(term_T *term)
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002363{
Bram Moolenaarcdeae992017-07-23 17:22:35 +02002364 if (term->tl_vterm != NULL)
2365 vterm_free(term->tl_vterm);
Bram Moolenaard85f2712017-07-28 21:51:57 +02002366 term->tl_vterm = NULL;
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002367}
Bram Moolenaar43da3e32017-07-23 17:27:54 +02002368
2369/*
2370 * Request size to terminal.
2371 */
2372 static void
2373term_report_winsize(term_T *term, int rows, int cols)
2374{
2375 /* Use an ioctl() to report the new window size to the job. */
2376 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL)
2377 {
2378 int fd = -1;
2379 int part;
2380
2381 for (part = PART_OUT; part < PART_COUNT; ++part)
2382 {
2383 fd = term->tl_job->jv_channel->ch_part[part].ch_fd;
2384 if (isatty(fd))
2385 break;
2386 }
2387 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK)
2388 mch_stop_job(term->tl_job, (char_u *)"winch");
2389 }
2390}
2391
Bram Moolenaar8f84c3a2017-07-22 16:14:44 +02002392# endif
Bram Moolenaar8c0095c2017-07-18 22:53:21 +02002393
Bram Moolenaare4f25e42017-07-07 11:54:15 +02002394#endif /* FEAT_TERMINAL */