blob: 63bc6abb1d4787d47aed374589763c6170ba28b0 [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 *
13 * For a terminal one VTerm is constructed. This uses libvterm. A copy of
14 * that library is in the libvterm directory.
15 *
16 * The VTerm invokes callbacks when its screen contents changes. The line
17 * range is stored in tl_dirty_row_start and tl_dirty_row_end. Once in a
Bram Moolenaar938783d2017-07-16 20:13:26 +020018 * while, if the terminal window is visible, the screen contents is drawn.
Bram Moolenaare4f25e42017-07-07 11:54:15 +020019 *
20 * If the terminal window has keyboard focus, typed keys are converted to the
21 * terminal encoding and writting to the job over a channel.
22 *
23 * If the job produces output, it is written to the VTerm.
24 * This will result in screen updates.
25 *
26 * TODO:
Bram Moolenaar938783d2017-07-16 20:13:26 +020027 * - pressing Enter sends two CR and/or NL characters to "bash -i"?
Bram Moolenaare4f25e42017-07-07 11:54:15 +020028 * - free b_term when closing terminal.
29 * - remove term from first_term list when closing terminal.
30 * - set buffer options to be scratch, hidden, nomodifiable, etc.
31 * - set buffer name to command, add (1) to avoid duplicates.
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +020032 * - if buffer is wiped, cleanup terminal, may stop job.
33 * - if the job ends, write "-- JOB ENDED --" in the terminal
Bram Moolenaar938783d2017-07-16 20:13:26 +020034 * - when closing window and job ended, delete the terminal
35 * - when closing window and job has not ended, make terminal hidden?
36 * - Use a pty for I/O with the job.
37 * - Windows implementation:
38 * (WiP): https://github.com/mattn/vim/tree/terminal
39 * src/os_win32.c mch_open_terminal()
40 Using winpty ?
41 * - command line completion for :terminal
Bram Moolenaare4f25e42017-07-07 11:54:15 +020042 * - support fixed size when 'termsize' is "rowsXcols".
43 * - support minimal size when 'termsize' is "rows*cols".
44 * - support minimal size when 'termsize' is empty.
45 * - implement ":buf {term-buf-name}"
46 * - implement term_getsize()
47 * - implement term_setsize()
48 * - implement term_sendkeys() send keystrokes to a terminal
49 * - implement term_wait() wait for screen to be updated
50 * - implement term_scrape() inspect terminal screen
51 * - implement term_open() open terminal window
52 * - implement term_getjob()
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +020053 * - implement 'termkey'
Bram Moolenaare4f25e42017-07-07 11:54:15 +020054 */
55
56#include "vim.h"
57
58#ifdef FEAT_TERMINAL
59
60#include "libvterm/include/vterm.h"
61
62/* typedef term_T in structs.h */
63struct terminal_S {
64 term_T *tl_next;
65
66 VTerm *tl_vterm;
67 job_T *tl_job;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +020068 buf_T *tl_buffer;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020069
70 /* Range of screen rows to update. Zero based. */
71 int tl_dirty_row_start; /* -1 if nothing dirty */
72 int tl_dirty_row_end; /* row below last one to update */
73
74 pos_T tl_cursor;
75};
76
77#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
78
79/*
80 * List of all active terminals.
81 */
82static term_T *first_term = NULL;
83
84static int handle_damage(VTermRect rect, void *user);
85static int handle_moverect(VTermRect dest, VTermRect src, void *user);
86static int handle_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user);
87static int handle_resize(int rows, int cols, void *user);
88
89static VTermScreenCallbacks screen_callbacks = {
90 handle_damage, /* damage */
91 handle_moverect, /* moverect */
92 handle_movecursor, /* movecursor */
93 NULL, /* settermprop */
94 NULL, /* bell */
95 handle_resize, /* resize */
96 NULL, /* sb_pushline */
97 NULL /* sb_popline */
98};
99
100/*
101 * ":terminal": open a terminal window and execute a job in it.
102 */
103 void
104ex_terminal(exarg_T *eap)
105{
106 int rows;
107 int cols;
108 exarg_T split_ea;
109 win_T *old_curwin = curwin;
110 typval_T argvars[2];
111 term_T *term;
112 VTerm *vterm;
113 VTermScreen *screen;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200114 jobopt_T opt;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200115
116 if (check_restricted() || check_secure())
117 return;
118
119 term = (term_T *)alloc_clear(sizeof(term_T));
120 if (term == NULL)
121 return;
122 term->tl_dirty_row_end = MAX_ROW;
123
124 /* Open a new window or tab. */
125 vim_memset(&split_ea, 0, sizeof(split_ea));
126 split_ea.cmdidx = CMD_new;
127 split_ea.cmd = (char_u *)"new";
128 split_ea.arg = (char_u *)"";
129 ex_splitview(&split_ea);
130 if (curwin == old_curwin)
131 {
132 /* split failed */
133 vim_free(term);
134 return;
135 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200136 term->tl_buffer = curbuf;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200137
138 curbuf->b_term = term;
139 term->tl_next = first_term;
140 first_term = term;
141
142 /* TODO: set buffer type, hidden, etc. */
143
144 if (*curwin->w_p_tms != NUL)
145 {
146 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
147
148 rows = atoi((char *)curwin->w_p_tms);
149 cols = atoi((char *)p);
150 /* TODO: resize window if possible. */
151 }
152 else
153 {
154 rows = curwin->w_height;
155 cols = curwin->w_width;
156 }
157
158 vterm = vterm_new(rows, cols);
159 term->tl_vterm = vterm;
160 screen = vterm_obtain_screen(vterm);
161 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200162 /* TODO: depends on 'encoding'. */
163 vterm_set_utf8(vterm, 1);
164 /* Required to initialize most things. */
165 vterm_screen_reset(screen, 1 /* hard */);
166
167 /* By default NL means CR-NL. */
Bram Moolenaar938783d2017-07-16 20:13:26 +0200168 /* TODO: this causes two prompts when using ":term bash -i". */
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200169 vterm_input_write(vterm, "\x1b[20h", 5);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200170
171 argvars[0].v_type = VAR_STRING;
172 argvars[0].vval.v_string = eap->arg;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200173
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200174 clear_job_options(&opt);
175 opt.jo_mode = MODE_RAW;
176 opt.jo_out_mode = MODE_RAW;
177 opt.jo_err_mode = MODE_RAW;
178 opt.jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
179 opt.jo_io[PART_OUT] = JIO_BUFFER;
180 opt.jo_io[PART_ERR] = JIO_BUFFER;
181 opt.jo_set |= JO_OUT_IO + (JO_OUT_IO << (PART_ERR - PART_OUT));
182 opt.jo_io_buf[PART_OUT] = curbuf->b_fnum;
183 opt.jo_io_buf[PART_ERR] = curbuf->b_fnum;
184 opt.jo_set |= JO_OUT_BUF + (JO_OUT_BUF << (PART_ERR - PART_OUT));
185
186 term->tl_job = job_start(argvars, &opt);
187
188 /* TODO: setup channel to job */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200189 /* Setup pty, see mch_call_shell(). */
190}
191
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200192/*
193 * Invoked when "msg" output from a job was received. Write it to the terminal
194 * of "buffer".
195 */
196 void
197write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
198{
199 size_t len = STRLEN(msg);
200 VTerm *vterm = buffer->b_term->tl_vterm;
201
202 ch_logn(channel, "writing %d bytes to terminal", (int)len);
203 vterm_input_write(vterm, (char *)msg, len);
204 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
205
206 /* TODO: only update once in a while. */
207 update_screen(0);
208 setcursor();
209 out_flush();
210}
211
212/*
213 * Called to update the window that contains the terminal.
214 */
215 void
216term_update_window(win_T *wp)
217{
218 int vterm_rows;
219 int vterm_cols;
220 VTerm *vterm = wp->w_buffer->b_term->tl_vterm;
221 VTermScreen *screen = vterm_obtain_screen(vterm);
222 VTermPos pos;
223
224 vterm_get_size(vterm, &vterm_rows, &vterm_cols);
225
226 /* TODO: Only redraw what changed. */
227 for (pos.row = 0; pos.row < wp->w_height; ++pos.row)
228 {
229 int off = screen_get_current_line_off();
230
231 if (pos.row < vterm_rows)
232 for (pos.col = 0; pos.col < wp->w_width && pos.col < vterm_cols;
233 ++pos.col)
234 {
235 VTermScreenCell cell;
236 int c;
237
238 vterm_screen_get_cell(screen, pos, &cell);
239 /* TODO: use cell.attrs and colors */
240 /* TODO: use cell.width */
241 /* TODO: multi-byte chars */
242 c = cell.chars[0];
243 ScreenLines[off] = c == NUL ? ' ' : c;
244 ScreenAttrs[off] = 0;
245 ++off;
246 }
247
248 screen_line(wp->w_winrow + pos.row, wp->w_wincol, pos.col, wp->w_width,
249 FALSE);
250 }
251}
252
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200253 static int
254handle_damage(VTermRect rect, void *user)
255{
256 term_T *term = (term_T *)user;
257
258 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
259 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200260 redraw_buf_later(term->tl_buffer, NOT_VALID);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200261 return 1;
262}
263
264 static int
265handle_moverect(VTermRect dest, VTermRect src, void *user)
266{
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200267 term_T *term = (term_T *)user;
268
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200269 /* TODO */
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200270 redraw_buf_later(term->tl_buffer, NOT_VALID);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200271 return 1;
272}
273
274 static int
275handle_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
276{
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200277 term_T *term = (term_T *)user;
278 win_T *wp;
279 int is_current = FALSE;
280
281 FOR_ALL_WINDOWS(wp)
282 {
283 if (wp->w_buffer == term->tl_buffer)
284 {
285 /* TODO: limit to window size? */
286 wp->w_wrow = pos.row;
287 wp->w_wcol = pos.col;
288 if (wp == curwin)
289 is_current = TRUE;
290 }
291 }
292
293 if (is_current)
294 {
295 setcursor();
296 out_flush();
297 }
298
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200299 return 1;
300}
301
302 static int
303handle_resize(int rows, int cols, void *user)
304{
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200305 term_T *term = (term_T *)user;
306
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200307 /* TODO: handle terminal resize. */
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200308 redraw_buf_later(term->tl_buffer, NOT_VALID);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200309 return 1;
310}
311
312/* TODO: Use win_del_lines() to make scroll up efficient. */
313
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200314
Bram Moolenaar938783d2017-07-16 20:13:26 +0200315/*
316 * Wait for input and send it to the job.
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200317 * Return when a CTRL-W command is typed that moves to another window.
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200318 */
Bram Moolenaar938783d2017-07-16 20:13:26 +0200319 void
320terminal_loop(void)
321{
322 VTerm *vterm = curbuf->b_term->tl_vterm;
323 char buf[200];
324
325 for (;;)
326 {
327 int c;
328 VTermKey key = VTERM_KEY_NONE;
329 VTermModifier mod = VTERM_MOD_NONE;
330 size_t len;
331
332 update_screen(0);
333 setcursor();
334 out_flush();
335
336 c = vgetc();
337 switch (c)
338 {
339 case Ctrl_W:
340 stuffcharReadbuff(Ctrl_W);
341 return;
342
343 case CAR: key = VTERM_KEY_ENTER; break;
344 case ESC: key = VTERM_KEY_ESCAPE; break;
345 case K_BS: key = VTERM_KEY_BACKSPACE; break;
346 case K_DEL: key = VTERM_KEY_DEL; break;
347 case K_DOWN: key = VTERM_KEY_DOWN; break;
348 case K_END: key = VTERM_KEY_END; break;
349 case K_F10: key = VTERM_KEY_FUNCTION(10); break;
350 case K_F11: key = VTERM_KEY_FUNCTION(11); break;
351 case K_F12: key = VTERM_KEY_FUNCTION(12); break;
352 case K_F1: key = VTERM_KEY_FUNCTION(1); break;
353 case K_F2: key = VTERM_KEY_FUNCTION(2); break;
354 case K_F3: key = VTERM_KEY_FUNCTION(3); break;
355 case K_F4: key = VTERM_KEY_FUNCTION(4); break;
356 case K_F5: key = VTERM_KEY_FUNCTION(5); break;
357 case K_F6: key = VTERM_KEY_FUNCTION(6); break;
358 case K_F7: key = VTERM_KEY_FUNCTION(7); break;
359 case K_F8: key = VTERM_KEY_FUNCTION(8); break;
360 case K_F9: key = VTERM_KEY_FUNCTION(9); break;
361 case K_HOME: key = VTERM_KEY_HOME; break;
362 case K_INS: key = VTERM_KEY_INS; break;
363 case K_K0: key = VTERM_KEY_KP_0; break;
364 case K_K1: key = VTERM_KEY_KP_1; break;
365 case K_K2: key = VTERM_KEY_KP_2; break;
366 case K_K3: key = VTERM_KEY_KP_3; break;
367 case K_K4: key = VTERM_KEY_KP_4; break;
368 case K_K5: key = VTERM_KEY_KP_5; break;
369 case K_K6: key = VTERM_KEY_KP_6; break;
370 case K_K7: key = VTERM_KEY_KP_7; break;
371 case K_K8: key = VTERM_KEY_KP_8; break;
372 case K_K9: key = VTERM_KEY_KP_9; break;
373 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */
374 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break;
375 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */
376 case K_KENTER: key = VTERM_KEY_KP_ENTER; break;
377 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */
378 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */
379 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break;
380 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break;
381 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */
382 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */
383 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break;
384 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break;
385 case K_LEFT: key = VTERM_KEY_LEFT; break;
386 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break;
387 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break;
388 case K_RIGHT: key = VTERM_KEY_RIGHT; break;
389 case K_UP: key = VTERM_KEY_UP; break;
390 case TAB: key = VTERM_KEY_TAB; break;
391 }
392
393 /*
394 * Convert special keys to vterm keys:
395 * - Write keys to vterm: vterm_keyboard_key()
396 * - Write output to channel.
397 */
398 if (key != VTERM_KEY_NONE)
399 /* Special key, let vterm convert it. */
400 vterm_keyboard_key(vterm, key, mod);
401 else
402 /* Normal character, let vterm convert it. */
403 vterm_keyboard_unichar(vterm, c, mod);
404
405 /* Read back the converted escape sequence. */
406 len = vterm_output_read(vterm, buf, sizeof(buf));
407
408 /* TODO: if FAIL is returned, stop? */
409 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
410 (char_u *)buf, len, NULL);
411 }
412}
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200413
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200414#endif /* FEAT_TERMINAL */