blob: f0cf21d98135aeef9fa68b90274cb695a5656bc1 [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
18 * while, if the window is visible, the screen contents is drawn.
19 *
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 Moolenaare4f25e42017-07-07 11:54:15 +020027 * - free b_term when closing terminal.
28 * - remove term from first_term list when closing terminal.
29 * - set buffer options to be scratch, hidden, nomodifiable, etc.
30 * - set buffer name to command, add (1) to avoid duplicates.
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +020031 * - if buffer is wiped, cleanup terminal, may stop job.
32 * - if the job ends, write "-- JOB ENDED --" in the terminal
Bram Moolenaare4f25e42017-07-07 11:54:15 +020033 * - command line completion (command name)
34 * - support fixed size when 'termsize' is "rowsXcols".
35 * - support minimal size when 'termsize' is "rows*cols".
36 * - support minimal size when 'termsize' is empty.
37 * - implement ":buf {term-buf-name}"
38 * - implement term_getsize()
39 * - implement term_setsize()
40 * - implement term_sendkeys() send keystrokes to a terminal
41 * - implement term_wait() wait for screen to be updated
42 * - implement term_scrape() inspect terminal screen
43 * - implement term_open() open terminal window
44 * - implement term_getjob()
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +020045 * - implement 'termkey'
Bram Moolenaare4f25e42017-07-07 11:54:15 +020046 */
47
48#include "vim.h"
49
50#ifdef FEAT_TERMINAL
51
52#include "libvterm/include/vterm.h"
53
54/* typedef term_T in structs.h */
55struct terminal_S {
56 term_T *tl_next;
57
58 VTerm *tl_vterm;
59 job_T *tl_job;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +020060 buf_T *tl_buffer;
Bram Moolenaare4f25e42017-07-07 11:54:15 +020061
62 /* Range of screen rows to update. Zero based. */
63 int tl_dirty_row_start; /* -1 if nothing dirty */
64 int tl_dirty_row_end; /* row below last one to update */
65
66 pos_T tl_cursor;
67};
68
69#define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */
70
71/*
72 * List of all active terminals.
73 */
74static term_T *first_term = NULL;
75
76static int handle_damage(VTermRect rect, void *user);
77static int handle_moverect(VTermRect dest, VTermRect src, void *user);
78static int handle_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user);
79static int handle_resize(int rows, int cols, void *user);
80
81static VTermScreenCallbacks screen_callbacks = {
82 handle_damage, /* damage */
83 handle_moverect, /* moverect */
84 handle_movecursor, /* movecursor */
85 NULL, /* settermprop */
86 NULL, /* bell */
87 handle_resize, /* resize */
88 NULL, /* sb_pushline */
89 NULL /* sb_popline */
90};
91
92/*
93 * ":terminal": open a terminal window and execute a job in it.
94 */
95 void
96ex_terminal(exarg_T *eap)
97{
98 int rows;
99 int cols;
100 exarg_T split_ea;
101 win_T *old_curwin = curwin;
102 typval_T argvars[2];
103 term_T *term;
104 VTerm *vterm;
105 VTermScreen *screen;
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200106 jobopt_T opt;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200107
108 if (check_restricted() || check_secure())
109 return;
110
111 term = (term_T *)alloc_clear(sizeof(term_T));
112 if (term == NULL)
113 return;
114 term->tl_dirty_row_end = MAX_ROW;
115
116 /* Open a new window or tab. */
117 vim_memset(&split_ea, 0, sizeof(split_ea));
118 split_ea.cmdidx = CMD_new;
119 split_ea.cmd = (char_u *)"new";
120 split_ea.arg = (char_u *)"";
121 ex_splitview(&split_ea);
122 if (curwin == old_curwin)
123 {
124 /* split failed */
125 vim_free(term);
126 return;
127 }
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200128 term->tl_buffer = curbuf;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200129
130 curbuf->b_term = term;
131 term->tl_next = first_term;
132 first_term = term;
133
134 /* TODO: set buffer type, hidden, etc. */
135
136 if (*curwin->w_p_tms != NUL)
137 {
138 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1;
139
140 rows = atoi((char *)curwin->w_p_tms);
141 cols = atoi((char *)p);
142 /* TODO: resize window if possible. */
143 }
144 else
145 {
146 rows = curwin->w_height;
147 cols = curwin->w_width;
148 }
149
150 vterm = vterm_new(rows, cols);
151 term->tl_vterm = vterm;
152 screen = vterm_obtain_screen(vterm);
153 vterm_screen_set_callbacks(screen, &screen_callbacks, term);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200154 /* TODO: depends on 'encoding'. */
155 vterm_set_utf8(vterm, 1);
156 /* Required to initialize most things. */
157 vterm_screen_reset(screen, 1 /* hard */);
158
159 /* By default NL means CR-NL. */
160 vterm_input_write(vterm, "\x1b[20h", 5);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200161
162 argvars[0].v_type = VAR_STRING;
163 argvars[0].vval.v_string = eap->arg;
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200164
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200165 clear_job_options(&opt);
166 opt.jo_mode = MODE_RAW;
167 opt.jo_out_mode = MODE_RAW;
168 opt.jo_err_mode = MODE_RAW;
169 opt.jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE;
170 opt.jo_io[PART_OUT] = JIO_BUFFER;
171 opt.jo_io[PART_ERR] = JIO_BUFFER;
172 opt.jo_set |= JO_OUT_IO + (JO_OUT_IO << (PART_ERR - PART_OUT));
173 opt.jo_io_buf[PART_OUT] = curbuf->b_fnum;
174 opt.jo_io_buf[PART_ERR] = curbuf->b_fnum;
175 opt.jo_set |= JO_OUT_BUF + (JO_OUT_BUF << (PART_ERR - PART_OUT));
176
177 term->tl_job = job_start(argvars, &opt);
178
179 /* TODO: setup channel to job */
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200180 /* Setup pty, see mch_call_shell(). */
181}
182
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200183/*
184 * Invoked when "msg" output from a job was received. Write it to the terminal
185 * of "buffer".
186 */
187 void
188write_to_term(buf_T *buffer, char_u *msg, channel_T *channel)
189{
190 size_t len = STRLEN(msg);
191 VTerm *vterm = buffer->b_term->tl_vterm;
192
193 ch_logn(channel, "writing %d bytes to terminal", (int)len);
194 vterm_input_write(vterm, (char *)msg, len);
195 vterm_screen_flush_damage(vterm_obtain_screen(vterm));
196
197 /* TODO: only update once in a while. */
198 update_screen(0);
199 setcursor();
200 out_flush();
201}
202
203/*
204 * Called to update the window that contains the terminal.
205 */
206 void
207term_update_window(win_T *wp)
208{
209 int vterm_rows;
210 int vterm_cols;
211 VTerm *vterm = wp->w_buffer->b_term->tl_vterm;
212 VTermScreen *screen = vterm_obtain_screen(vterm);
213 VTermPos pos;
214
215 vterm_get_size(vterm, &vterm_rows, &vterm_cols);
216
217 /* TODO: Only redraw what changed. */
218 for (pos.row = 0; pos.row < wp->w_height; ++pos.row)
219 {
220 int off = screen_get_current_line_off();
221
222 if (pos.row < vterm_rows)
223 for (pos.col = 0; pos.col < wp->w_width && pos.col < vterm_cols;
224 ++pos.col)
225 {
226 VTermScreenCell cell;
227 int c;
228
229 vterm_screen_get_cell(screen, pos, &cell);
230 /* TODO: use cell.attrs and colors */
231 /* TODO: use cell.width */
232 /* TODO: multi-byte chars */
233 c = cell.chars[0];
234 ScreenLines[off] = c == NUL ? ' ' : c;
235 ScreenAttrs[off] = 0;
236 ++off;
237 }
238
239 screen_line(wp->w_winrow + pos.row, wp->w_wincol, pos.col, wp->w_width,
240 FALSE);
241 }
242}
243
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200244 static int
245handle_damage(VTermRect rect, void *user)
246{
247 term_T *term = (term_T *)user;
248
249 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row);
250 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row);
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200251 redraw_buf_later(term->tl_buffer, NOT_VALID);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200252 return 1;
253}
254
255 static int
256handle_moverect(VTermRect dest, VTermRect src, void *user)
257{
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200258 term_T *term = (term_T *)user;
259
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200260 /* TODO */
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200261 redraw_buf_later(term->tl_buffer, NOT_VALID);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200262 return 1;
263}
264
265 static int
266handle_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
267{
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200268 term_T *term = (term_T *)user;
269 win_T *wp;
270 int is_current = FALSE;
271
272 FOR_ALL_WINDOWS(wp)
273 {
274 if (wp->w_buffer == term->tl_buffer)
275 {
276 /* TODO: limit to window size? */
277 wp->w_wrow = pos.row;
278 wp->w_wcol = pos.col;
279 if (wp == curwin)
280 is_current = TRUE;
281 }
282 }
283
284 if (is_current)
285 {
286 setcursor();
287 out_flush();
288 }
289
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200290 return 1;
291}
292
293 static int
294handle_resize(int rows, int cols, void *user)
295{
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200296 term_T *term = (term_T *)user;
297
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200298 /* TODO: handle terminal resize. */
Bram Moolenaarcb8bbe92017-07-16 13:48:22 +0200299 redraw_buf_later(term->tl_buffer, NOT_VALID);
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200300 return 1;
301}
302
303/* TODO: Use win_del_lines() to make scroll up efficient. */
304
305/* TODO: function to update the window.
306 * Get the screen contents from vterm with vterm_screen_get_cell().
307 * put in current_ScreenLine and call screen_line().
308 */
309
310/* TODO: function to wait for input and send it to the job.
311 * Return when a CTRL-W command is typed that moves to another window.
312 * Convert special keys to vterm keys:
313 * - Write keys to vterm: vterm_keyboard_key()
314 * - read the output (xterm escape sequences): vterm_output_read()
315 * - Write output to channel.
316 */
317
Bram Moolenaare4f25e42017-07-07 11:54:15 +0200318#endif /* FEAT_TERMINAL */