1 /* 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 * There are three parts: 14 * 1. Generic code for all systems. 15 * Uses libvterm for the terminal emulator. 16 * 2. The MS-Windows implementation. 17 * Uses winpty. 18 * 3. The Unix-like implementation. 19 * Uses pseudo-tty's (pty's). 20 * 21 * For each terminal one VTerm is constructed. This uses libvterm. A copy of 22 * this library is in the libvterm directory. 23 * 24 * When a terminal window is opened, a job is started that will be connected to 25 * the terminal emulator. 26 * 27 * If the terminal window has keyboard focus, typed keys are converted to the 28 * terminal encoding and writing to the job over a channel. 29 * 30 * 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. 34 * 35 * 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 * 38 * TODO: 39 * - Add argument to term_wait() for waiting time. 40 * - For the scrollback buffer store lines in the buffer, only attributes in 41 * tl_scrollback. 42 * - When the job ends: 43 * - Need an option or argument to drop the window+buffer right away, to be 44 * used for a shell or Vim. 'termfinish'; "close", "open" (open window when 45 * job finishes). 46 * - add option values to the command: 47 * :term <24x80> <close> vim notes.txt 48 * - support different cursor shapes, colors and attributes 49 * - MS-Windows: no redraw for 'updatetime' #1915 50 * - make term_getcursor() return type (none/block/bar/underline) and 51 * attributes (color, blink, etc.) 52 * - To set BS correctly, check get_stty(); Pass the fd of the pty. 53 * For the GUI fill termios with default values, perhaps like pangoterm: 54 * http://bazaar.launchpad.net/~leonerd/pangoterm/trunk/view/head:/main.c#L134 55 * Also get the NL behavior from there. 56 * - do not store terminal window in viminfo. Or prefix term:// ? 57 * - add a character in :ls output 58 * - add 't' to mode() 59 * - set 'filetype' to "terminal"? 60 * - use win_del_lines() to make scroll-up efficient. 61 * - Make StatusLineTerm adjust UserN highlighting like StatusLineNC does, see 62 * use of hightlight_stlnc[]. 63 * - implement term_setsize() 64 * - add test for giving error for invalid 'termsize' value. 65 * - support minimal size when 'termsize' is "rows*cols". 66 * - support minimal size when 'termsize' is empty? 67 * - implement "term" for job_start(): more job options when starting a 68 * terminal. Allow: 69 * "in_io", "in_top", "in_bot", "in_name", "in_buf" 70 "out_io", "out_name", "out_buf", "out_modifiable", "out_msg" 71 "err_io", "err_name", "err_buf", "err_modifiable", "err_msg" 72 * Check that something is connected to the terminal. 73 * Test: "cat" reading from a file or buffer 74 * "ls" writing stdout to a file or buffer 75 * shell writing stderr to a file or buffer 76 * - support ":term NONE" to open a terminal with a pty but not running a job 77 * in it. The pty can be passed to gdb to run the executable in. 78 * - if the job in the terminal does not support the mouse, we can use the 79 * mouse in the Terminal window for copy/paste. 80 * - when 'encoding' is not utf-8, or the job is using another encoding, setup 81 * conversions. 82 * - update ":help function-list" for terminal functions. 83 * - In the GUI use a terminal emulator for :!cmd. 84 * - Copy text in the vterm to the Vim buffer once in a while, so that 85 * completion works. 86 */ 87 88 #include "vim.h" 89 90 #if defined(FEAT_TERMINAL) || defined(PROTO) 91 92 #ifndef MIN 93 # define MIN(x,y) ((x) < (y) ? (x) : (y)) 94 #endif 95 #ifndef MAX 96 # define MAX(x,y) ((x) > (y) ? (x) : (y)) 97 #endif 98 99 #include "libvterm/include/vterm.h" 100 101 typedef struct sb_line_S { 102 int sb_cols; /* can differ per line */ 103 VTermScreenCell *sb_cells; /* allocated */ 104 } sb_line_T; 105 106 /* typedef term_T in structs.h */ 107 struct terminal_S { 108 term_T *tl_next; 109 110 VTerm *tl_vterm; 111 job_T *tl_job; 112 buf_T *tl_buffer; 113 114 /* used when tl_job is NULL and only a pty was created */ 115 int tl_tty_fd; 116 char_u *tl_tty_name; 117 118 int tl_normal_mode; /* TRUE: Terminal-Normal mode */ 119 int tl_channel_closed; 120 121 #ifdef WIN3264 122 void *tl_winpty_config; 123 void *tl_winpty; 124 #endif 125 126 /* last known vterm size */ 127 int tl_rows; 128 int tl_cols; 129 /* vterm size does not follow window size */ 130 int tl_rows_fixed; 131 int tl_cols_fixed; 132 133 char_u *tl_title; /* NULL or allocated */ 134 char_u *tl_status_text; /* NULL or allocated */ 135 136 /* Range of screen rows to update. Zero based. */ 137 int tl_dirty_row_start; /* -1 if nothing dirty */ 138 int tl_dirty_row_end; /* row below last one to update */ 139 140 garray_T tl_scrollback; 141 int tl_scrollback_scrolled; 142 143 VTermPos tl_cursor_pos; 144 int tl_cursor_visible; 145 }; 146 147 #define TMODE_ONCE 1 /* CTRL-\ CTRL-N used */ 148 #define TMODE_LOOP 2 /* CTRL-W N used */ 149 150 /* 151 * List of all active terminals. 152 */ 153 static term_T *first_term = NULL; 154 155 156 #define MAX_ROW 999999 /* used for tl_dirty_row_end to update all rows */ 157 #define KEY_BUF_LEN 200 158 159 /* 160 * Functions with separate implementation for MS-Windows and Unix-like systems. 161 */ 162 static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt); 163 static void term_report_winsize(term_T *term, int rows, int cols); 164 static void term_free_vterm(term_T *term); 165 166 /************************************** 167 * 1. Generic code for all systems. 168 */ 169 170 /* 171 * Determine the terminal size from 'termsize' and the current window. 172 * Assumes term->tl_rows and term->tl_cols are zero. 173 */ 174 static void 175 set_term_and_win_size(term_T *term) 176 { 177 if (*curwin->w_p_tms != NUL) 178 { 179 char_u *p = vim_strchr(curwin->w_p_tms, 'x') + 1; 180 181 term->tl_rows = atoi((char *)curwin->w_p_tms); 182 term->tl_cols = atoi((char *)p); 183 } 184 if (term->tl_rows == 0) 185 term->tl_rows = curwin->w_height; 186 else 187 { 188 win_setheight_win(term->tl_rows, curwin); 189 term->tl_rows_fixed = TRUE; 190 } 191 if (term->tl_cols == 0) 192 term->tl_cols = curwin->w_width; 193 else 194 { 195 win_setwidth_win(term->tl_cols, curwin); 196 term->tl_cols_fixed = TRUE; 197 } 198 } 199 200 /* 201 * Initialize job options for a terminal job. 202 * Caller may overrule some of them. 203 */ 204 static void 205 init_job_options(jobopt_T *opt) 206 { 207 clear_job_options(opt); 208 209 opt->jo_mode = MODE_RAW; 210 opt->jo_out_mode = MODE_RAW; 211 opt->jo_err_mode = MODE_RAW; 212 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE; 213 214 opt->jo_io[PART_OUT] = JIO_BUFFER; 215 opt->jo_io[PART_ERR] = JIO_BUFFER; 216 opt->jo_set |= JO_OUT_IO + JO_ERR_IO; 217 218 opt->jo_modifiable[PART_OUT] = 0; 219 opt->jo_modifiable[PART_ERR] = 0; 220 opt->jo_set |= JO_OUT_MODIFIABLE + JO_ERR_MODIFIABLE; 221 222 opt->jo_set |= JO_OUT_BUF + JO_ERR_BUF; 223 } 224 225 /* 226 * Set job options mandatory for a terminal job. 227 */ 228 static void 229 setup_job_options(jobopt_T *opt, int rows, int cols) 230 { 231 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum; 232 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum; 233 opt->jo_pty = TRUE; 234 opt->jo_term_rows = rows; 235 opt->jo_term_cols = cols; 236 } 237 238 static void 239 term_start(char_u *cmd, jobopt_T *opt) 240 { 241 exarg_T split_ea; 242 win_T *old_curwin = curwin; 243 term_T *term; 244 245 if (check_restricted() || check_secure()) 246 return; 247 248 term = (term_T *)alloc_clear(sizeof(term_T)); 249 if (term == NULL) 250 return; 251 term->tl_dirty_row_end = MAX_ROW; 252 term->tl_cursor_visible = TRUE; 253 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300); 254 255 /* Open a new window or tab. */ 256 vim_memset(&split_ea, 0, sizeof(split_ea)); 257 split_ea.cmdidx = CMD_new; 258 split_ea.cmd = (char_u *)"new"; 259 split_ea.arg = (char_u *)""; 260 if (opt->jo_term_rows > 0 && !(cmdmod.split & WSP_VERT)) 261 { 262 split_ea.line2 = opt->jo_term_rows; 263 split_ea.addr_count = 1; 264 } 265 if (opt->jo_term_cols > 0 && (cmdmod.split & WSP_VERT)) 266 { 267 split_ea.line2 = opt->jo_term_cols; 268 split_ea.addr_count = 1; 269 } 270 271 ex_splitview(&split_ea); 272 if (curwin == old_curwin) 273 { 274 /* split failed */ 275 vim_free(term); 276 return; 277 } 278 term->tl_buffer = curbuf; 279 curbuf->b_term = term; 280 281 /* only one size was taken care of with :new, do the other one */ 282 if (opt->jo_term_rows > 0 && (cmdmod.split & WSP_VERT)) 283 win_setheight(opt->jo_term_rows); 284 if (opt->jo_term_cols > 0 && !(cmdmod.split & WSP_VERT)) 285 win_setwidth(opt->jo_term_cols); 286 287 /* Link the new terminal in the list of active terminals. */ 288 term->tl_next = first_term; 289 first_term = term; 290 291 if (cmd == NULL || *cmd == NUL) 292 cmd = p_sh; 293 294 if (opt->jo_term_name != NULL) 295 curbuf->b_ffname = vim_strsave(opt->jo_term_name); 296 else 297 { 298 int i; 299 size_t len = STRLEN(cmd) + 10; 300 char_u *p = alloc((int)len); 301 302 for (i = 0; p != NULL; ++i) 303 { 304 /* Prepend a ! to the command name to avoid the buffer name equals 305 * the executable, otherwise ":w!" would overwrite it. */ 306 if (i == 0) 307 vim_snprintf((char *)p, len, "!%s", cmd); 308 else 309 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i); 310 if (buflist_findname(p) == NULL) 311 { 312 curbuf->b_ffname = p; 313 break; 314 } 315 } 316 } 317 curbuf->b_fname = curbuf->b_ffname; 318 319 set_string_option_direct((char_u *)"buftype", -1, 320 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0); 321 322 /* Mark the buffer as not modifiable. It can only be made modifiable after 323 * the job finished. */ 324 curbuf->b_p_ma = FALSE; 325 326 /* Set 'bufhidden' to "hide": allow closing the window. */ 327 set_string_option_direct((char_u *)"bufhidden", -1, 328 (char_u *)"hide", OPT_FREE|OPT_LOCAL, 0); 329 330 set_term_and_win_size(term); 331 setup_job_options(opt, term->tl_rows, term->tl_cols); 332 333 /* System dependent: setup the vterm and start the job in it. */ 334 if (term_and_job_init(term, term->tl_rows, term->tl_cols, cmd, opt) == OK) 335 { 336 /* store the size we ended up with */ 337 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols); 338 } 339 else 340 { 341 free_terminal(curbuf); 342 343 /* Wiping out the buffer will also close the window and call 344 * free_terminal(). */ 345 do_buffer(DOBUF_WIPE, DOBUF_CURRENT, FORWARD, 0, TRUE); 346 } 347 } 348 349 /* 350 * ":terminal": open a terminal window and execute a job in it. 351 */ 352 void 353 ex_terminal(exarg_T *eap) 354 { 355 jobopt_T opt; 356 357 init_job_options(&opt); 358 359 if (eap->addr_count == 2) 360 { 361 opt.jo_term_rows = eap->line1; 362 opt.jo_term_cols = eap->line2; 363 } 364 else if (eap->addr_count == 1) 365 { 366 if (cmdmod.split & WSP_VERT) 367 opt.jo_term_cols = eap->line2; 368 else 369 opt.jo_term_rows = eap->line2; 370 } 371 /* TODO: get more options from before the command */ 372 373 term_start(eap->arg, &opt); 374 } 375 376 /* 377 * Free the scrollback buffer for "term". 378 */ 379 static void 380 free_scrollback(term_T *term) 381 { 382 int i; 383 384 for (i = 0; i < term->tl_scrollback.ga_len; ++i) 385 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells); 386 ga_clear(&term->tl_scrollback); 387 } 388 389 /* 390 * Free a terminal and everything it refers to. 391 * Kills the job if there is one. 392 * Called when wiping out a buffer. 393 */ 394 void 395 free_terminal(buf_T *buf) 396 { 397 term_T *term = buf->b_term; 398 term_T *tp; 399 400 if (term == NULL) 401 return; 402 if (first_term == term) 403 first_term = term->tl_next; 404 else 405 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next) 406 if (tp->tl_next == term) 407 { 408 tp->tl_next = term->tl_next; 409 break; 410 } 411 412 if (term->tl_job != NULL) 413 { 414 if (term->tl_job->jv_status != JOB_ENDED 415 && term->tl_job->jv_status != JOB_FAILED) 416 job_stop(term->tl_job, NULL, "kill"); 417 job_unref(term->tl_job); 418 } 419 420 free_scrollback(term); 421 422 term_free_vterm(term); 423 vim_free(term->tl_title); 424 vim_free(term->tl_status_text); 425 vim_free(term); 426 buf->b_term = NULL; 427 } 428 429 /* 430 * Write job output "msg[len]" to the vterm. 431 */ 432 static void 433 term_write_job_output(term_T *term, char_u *msg, size_t len) 434 { 435 VTerm *vterm = term->tl_vterm; 436 char_u *p; 437 size_t done; 438 size_t len_now; 439 440 for (done = 0; done < len; done += len_now) 441 { 442 for (p = msg + done; p < msg + len; ) 443 { 444 if (*p == NL) 445 break; 446 p += utf_ptr2len_len(p, (int)(len - (p - msg))); 447 } 448 len_now = p - msg - done; 449 vterm_input_write(vterm, (char *)msg + done, len_now); 450 if (p < msg + len && *p == NL) 451 { 452 /* Convert NL to CR-NL, that appears to work best. */ 453 vterm_input_write(vterm, "\r\n", 2); 454 ++len_now; 455 } 456 } 457 458 /* this invokes the damage callbacks */ 459 vterm_screen_flush_damage(vterm_obtain_screen(vterm)); 460 } 461 462 static void 463 update_cursor(term_T *term, int redraw) 464 { 465 if (term->tl_normal_mode) 466 return; 467 setcursor(); 468 if (redraw && term->tl_buffer == curbuf) 469 { 470 if (term->tl_cursor_visible) 471 cursor_on(); 472 out_flush(); 473 #ifdef FEAT_GUI 474 if (gui.in_use) 475 gui_update_cursor(FALSE, FALSE); 476 #endif 477 } 478 } 479 480 /* 481 * Invoked when "msg" output from a job was received. Write it to the terminal 482 * of "buffer". 483 */ 484 void 485 write_to_term(buf_T *buffer, char_u *msg, channel_T *channel) 486 { 487 size_t len = STRLEN(msg); 488 term_T *term = buffer->b_term; 489 490 if (term->tl_vterm == NULL) 491 { 492 ch_log(channel, "NOT writing %d bytes to terminal", (int)len); 493 return; 494 } 495 ch_log(channel, "writing %d bytes to terminal", (int)len); 496 term_write_job_output(term, msg, len); 497 498 if (!term->tl_normal_mode) 499 { 500 /* TODO: only update once in a while. */ 501 update_screen(0); 502 update_cursor(term, TRUE); 503 } 504 } 505 506 /* 507 * Send a mouse position and click to the vterm 508 */ 509 static int 510 term_send_mouse(VTerm *vterm, int button, int pressed) 511 { 512 VTermModifier mod = VTERM_MOD_NONE; 513 514 vterm_mouse_move(vterm, mouse_row - W_WINROW(curwin), 515 mouse_col - W_WINCOL(curwin), mod); 516 vterm_mouse_button(vterm, button, pressed, mod); 517 return TRUE; 518 } 519 520 /* 521 * Convert typed key "c" into bytes to send to the job. 522 * Return the number of bytes in "buf". 523 */ 524 static int 525 term_convert_key(term_T *term, int c, char *buf) 526 { 527 VTerm *vterm = term->tl_vterm; 528 VTermKey key = VTERM_KEY_NONE; 529 VTermModifier mod = VTERM_MOD_NONE; 530 int mouse = FALSE; 531 532 switch (c) 533 { 534 case CAR: key = VTERM_KEY_ENTER; break; 535 case ESC: key = VTERM_KEY_ESCAPE; break; 536 /* VTERM_KEY_BACKSPACE becomes 0x7f DEL */ 537 case K_BS: c = BS; break; 538 case K_DEL: key = VTERM_KEY_DEL; break; 539 case K_DOWN: key = VTERM_KEY_DOWN; break; 540 case K_S_DOWN: mod = VTERM_MOD_SHIFT; 541 key = VTERM_KEY_DOWN; break; 542 case K_END: key = VTERM_KEY_END; break; 543 case K_S_END: mod = VTERM_MOD_SHIFT; 544 key = VTERM_KEY_END; break; 545 case K_C_END: mod = VTERM_MOD_CTRL; 546 key = VTERM_KEY_END; break; 547 case K_F10: key = VTERM_KEY_FUNCTION(10); break; 548 case K_F11: key = VTERM_KEY_FUNCTION(11); break; 549 case K_F12: key = VTERM_KEY_FUNCTION(12); break; 550 case K_F1: key = VTERM_KEY_FUNCTION(1); break; 551 case K_F2: key = VTERM_KEY_FUNCTION(2); break; 552 case K_F3: key = VTERM_KEY_FUNCTION(3); break; 553 case K_F4: key = VTERM_KEY_FUNCTION(4); break; 554 case K_F5: key = VTERM_KEY_FUNCTION(5); break; 555 case K_F6: key = VTERM_KEY_FUNCTION(6); break; 556 case K_F7: key = VTERM_KEY_FUNCTION(7); break; 557 case K_F8: key = VTERM_KEY_FUNCTION(8); break; 558 case K_F9: key = VTERM_KEY_FUNCTION(9); break; 559 case K_HOME: key = VTERM_KEY_HOME; break; 560 case K_S_HOME: mod = VTERM_MOD_SHIFT; 561 key = VTERM_KEY_HOME; break; 562 case K_C_HOME: mod = VTERM_MOD_CTRL; 563 key = VTERM_KEY_HOME; break; 564 case K_INS: key = VTERM_KEY_INS; break; 565 case K_K0: key = VTERM_KEY_KP_0; break; 566 case K_K1: key = VTERM_KEY_KP_1; break; 567 case K_K2: key = VTERM_KEY_KP_2; break; 568 case K_K3: key = VTERM_KEY_KP_3; break; 569 case K_K4: key = VTERM_KEY_KP_4; break; 570 case K_K5: key = VTERM_KEY_KP_5; break; 571 case K_K6: key = VTERM_KEY_KP_6; break; 572 case K_K7: key = VTERM_KEY_KP_7; break; 573 case K_K8: key = VTERM_KEY_KP_8; break; 574 case K_K9: key = VTERM_KEY_KP_9; break; 575 case K_KDEL: key = VTERM_KEY_DEL; break; /* TODO */ 576 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break; 577 case K_KEND: key = VTERM_KEY_KP_1; break; /* TODO */ 578 case K_KENTER: key = VTERM_KEY_KP_ENTER; break; 579 case K_KHOME: key = VTERM_KEY_KP_7; break; /* TODO */ 580 case K_KINS: key = VTERM_KEY_KP_0; break; /* TODO */ 581 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break; 582 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break; 583 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; /* TODO */ 584 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; /* TODO */ 585 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break; 586 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break; 587 case K_LEFT: key = VTERM_KEY_LEFT; break; 588 case K_S_LEFT: mod = VTERM_MOD_SHIFT; 589 key = VTERM_KEY_LEFT; break; 590 case K_C_LEFT: mod = VTERM_MOD_CTRL; 591 key = VTERM_KEY_LEFT; break; 592 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break; 593 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break; 594 case K_RIGHT: key = VTERM_KEY_RIGHT; break; 595 case K_S_RIGHT: mod = VTERM_MOD_SHIFT; 596 key = VTERM_KEY_RIGHT; break; 597 case K_C_RIGHT: mod = VTERM_MOD_CTRL; 598 key = VTERM_KEY_RIGHT; break; 599 case K_UP: key = VTERM_KEY_UP; break; 600 case K_S_UP: mod = VTERM_MOD_SHIFT; 601 key = VTERM_KEY_UP; break; 602 case TAB: key = VTERM_KEY_TAB; break; 603 604 case K_MOUSEUP: mouse = term_send_mouse(vterm, 5, 1); break; 605 case K_MOUSEDOWN: mouse = term_send_mouse(vterm, 4, 1); break; 606 case K_MOUSELEFT: /* TODO */ return 0; 607 case K_MOUSERIGHT: /* TODO */ return 0; 608 609 case K_LEFTMOUSE: 610 case K_LEFTMOUSE_NM: mouse = term_send_mouse(vterm, 1, 1); break; 611 case K_LEFTDRAG: mouse = term_send_mouse(vterm, 1, 1); break; 612 case K_LEFTRELEASE: 613 case K_LEFTRELEASE_NM: mouse = term_send_mouse(vterm, 1, 0); break; 614 case K_MIDDLEMOUSE: mouse = term_send_mouse(vterm, 2, 1); break; 615 case K_MIDDLEDRAG: mouse = term_send_mouse(vterm, 2, 1); break; 616 case K_MIDDLERELEASE: mouse = term_send_mouse(vterm, 2, 0); break; 617 case K_RIGHTMOUSE: mouse = term_send_mouse(vterm, 3, 1); break; 618 case K_RIGHTDRAG: mouse = term_send_mouse(vterm, 3, 1); break; 619 case K_RIGHTRELEASE: mouse = term_send_mouse(vterm, 3, 0); break; 620 case K_X1MOUSE: /* TODO */ return 0; 621 case K_X1DRAG: /* TODO */ return 0; 622 case K_X1RELEASE: /* TODO */ return 0; 623 case K_X2MOUSE: /* TODO */ return 0; 624 case K_X2DRAG: /* TODO */ return 0; 625 case K_X2RELEASE: /* TODO */ return 0; 626 627 case K_IGNORE: return 0; 628 case K_NOP: return 0; 629 case K_UNDO: return 0; 630 case K_HELP: return 0; 631 case K_XF1: key = VTERM_KEY_FUNCTION(1); break; 632 case K_XF2: key = VTERM_KEY_FUNCTION(2); break; 633 case K_XF3: key = VTERM_KEY_FUNCTION(3); break; 634 case K_XF4: key = VTERM_KEY_FUNCTION(4); break; 635 case K_SELECT: return 0; 636 #ifdef FEAT_GUI 637 case K_VER_SCROLLBAR: return 0; 638 case K_HOR_SCROLLBAR: return 0; 639 #endif 640 #ifdef FEAT_GUI_TABLINE 641 case K_TABLINE: return 0; 642 case K_TABMENU: return 0; 643 #endif 644 #ifdef FEAT_NETBEANS_INTG 645 case K_F21: key = VTERM_KEY_FUNCTION(21); break; 646 #endif 647 #ifdef FEAT_DND 648 case K_DROP: return 0; 649 #endif 650 #ifdef FEAT_AUTOCMD 651 case K_CURSORHOLD: return 0; 652 #endif 653 case K_PS: vterm_keyboard_start_paste(vterm); return 0; 654 case K_PE: vterm_keyboard_end_paste(vterm); return 0; 655 } 656 657 /* 658 * Convert special keys to vterm keys: 659 * - Write keys to vterm: vterm_keyboard_key() 660 * - Write output to channel. 661 * TODO: use mod_mask 662 */ 663 if (key != VTERM_KEY_NONE) 664 /* Special key, let vterm convert it. */ 665 vterm_keyboard_key(vterm, key, mod); 666 else if (!mouse) 667 /* Normal character, let vterm convert it. */ 668 vterm_keyboard_unichar(vterm, c, mod); 669 670 /* Read back the converted escape sequence. */ 671 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN); 672 } 673 674 /* 675 * Return TRUE if the job for "term" is still running. 676 */ 677 int 678 term_job_running(term_T *term) 679 { 680 /* Also consider the job finished when the channel is closed, to avoid a 681 * race condition when updating the title. */ 682 return term != NULL 683 && term->tl_job != NULL 684 && term->tl_job->jv_status == JOB_STARTED 685 && channel_is_open(term->tl_job->jv_channel); 686 } 687 688 /* 689 * Add the last line of the scrollback buffer to the buffer in the window. 690 */ 691 static void 692 add_scrollback_line_to_buffer(term_T *term) 693 { 694 linenr_T lnum = term->tl_scrollback.ga_len - 1; 695 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum; 696 garray_T ga; 697 int c; 698 int col; 699 int i; 700 701 ga_init2(&ga, 1, 100); 702 for (col = 0; col < line->sb_cols; col += line->sb_cells[col].width) 703 { 704 if (ga_grow(&ga, MB_MAXBYTES) == FAIL) 705 goto failed; 706 for (i = 0; (c = line->sb_cells[col].chars[i]) > 0 || i == 0; ++i) 707 ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c, 708 (char_u *)ga.ga_data + ga.ga_len); 709 } 710 if (ga_grow(&ga, 1) == FAIL) 711 goto failed; 712 *((char_u *)ga.ga_data + ga.ga_len) = NUL; 713 ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE); 714 715 if (lnum == 0) 716 { 717 /* Delete the empty line that was in the empty buffer. */ 718 curbuf = term->tl_buffer; 719 ml_delete(2, FALSE); 720 curbuf = curwin->w_buffer; 721 } 722 723 failed: 724 ga_clear(&ga); 725 } 726 727 /* 728 * Add the current lines of the terminal to scrollback and to the buffer. 729 * Called after the job has ended and when switching to Terminal mode. 730 */ 731 static void 732 move_terminal_to_buffer(term_T *term) 733 { 734 win_T *wp; 735 int len; 736 int lines_skipped = 0; 737 VTermPos pos; 738 VTermScreenCell cell; 739 VTermScreenCell *p; 740 VTermScreen *screen; 741 742 if (term->tl_vterm == NULL) 743 return; 744 screen = vterm_obtain_screen(term->tl_vterm); 745 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row) 746 { 747 len = 0; 748 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col) 749 if (vterm_screen_get_cell(screen, pos, &cell) != 0 750 && cell.chars[0] != NUL) 751 len = pos.col + 1; 752 753 if (len == 0) 754 ++lines_skipped; 755 else 756 { 757 while (lines_skipped > 0) 758 { 759 /* Line was skipped, add an empty line. */ 760 --lines_skipped; 761 if (ga_grow(&term->tl_scrollback, 1) == OK) 762 { 763 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data 764 + term->tl_scrollback.ga_len; 765 766 line->sb_cols = 0; 767 line->sb_cells = NULL; 768 ++term->tl_scrollback.ga_len; 769 770 add_scrollback_line_to_buffer(term); 771 } 772 } 773 774 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len); 775 if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK) 776 { 777 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data 778 + term->tl_scrollback.ga_len; 779 780 for (pos.col = 0; pos.col < len; ++pos.col) 781 { 782 if (vterm_screen_get_cell(screen, pos, &cell) == 0) 783 vim_memset(p + pos.col, 0, sizeof(cell)); 784 else 785 p[pos.col] = cell; 786 } 787 line->sb_cols = len; 788 line->sb_cells = p; 789 ++term->tl_scrollback.ga_len; 790 791 add_scrollback_line_to_buffer(term); 792 } 793 else 794 vim_free(p); 795 } 796 } 797 798 FOR_ALL_WINDOWS(wp) 799 { 800 if (wp->w_buffer == term->tl_buffer) 801 { 802 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count; 803 wp->w_cursor.col = 0; 804 wp->w_valid = 0; 805 redraw_win_later(wp, NOT_VALID); 806 } 807 } 808 } 809 810 static void 811 set_terminal_mode(term_T *term, int normal_mode) 812 { 813 term->tl_normal_mode = normal_mode; 814 vim_free(term->tl_status_text); 815 term->tl_status_text = NULL; 816 if (term->tl_buffer == curbuf) 817 maketitle(); 818 } 819 820 /* 821 * Called after the job if finished and Terminal mode is not active: 822 * Move the vterm contents into the scrollback buffer and free the vterm. 823 */ 824 static void 825 cleanup_vterm(term_T *term) 826 { 827 move_terminal_to_buffer(term); 828 term_free_vterm(term); 829 set_terminal_mode(term, FALSE); 830 } 831 832 /* 833 * Switch from Terminal-Job mode to Terminal-Normal mode. 834 * Suspends updating the terminal window. 835 */ 836 static void 837 term_enter_normal_mode(void) 838 { 839 term_T *term = curbuf->b_term; 840 841 /* Append the current terminal contents to the buffer. */ 842 move_terminal_to_buffer(term); 843 844 set_terminal_mode(term, TRUE); 845 846 /* Move the window cursor to the position of the cursor in the 847 * terminal. */ 848 curwin->w_cursor.lnum = term->tl_scrollback_scrolled 849 + term->tl_cursor_pos.row + 1; 850 check_cursor(); 851 coladvance(term->tl_cursor_pos.col); 852 853 /* Display the same lines as in the terminal. */ 854 curwin->w_topline = term->tl_scrollback_scrolled + 1; 855 } 856 857 /* 858 * Returns TRUE if the current window contains a terminal and we are in 859 * Terminal-Normal mode. 860 */ 861 int 862 term_in_normal_mode(void) 863 { 864 term_T *term = curbuf->b_term; 865 866 return term != NULL && term->tl_normal_mode; 867 } 868 869 /* 870 * Switch from Terminal-Normal mode to Terminal-Job mode. 871 * Restores updating the terminal window. 872 */ 873 void 874 term_enter_job_mode() 875 { 876 term_T *term = curbuf->b_term; 877 sb_line_T *line; 878 garray_T *gap; 879 880 /* Remove the terminal contents from the scrollback and the buffer. */ 881 gap = &term->tl_scrollback; 882 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled) 883 { 884 ml_delete(curbuf->b_ml.ml_line_count, FALSE); 885 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1; 886 vim_free(line->sb_cells); 887 --gap->ga_len; 888 if (gap->ga_len == 0) 889 break; 890 } 891 check_cursor(); 892 893 set_terminal_mode(term, FALSE); 894 895 if (term->tl_channel_closed) 896 cleanup_vterm(term); 897 redraw_buf_and_status_later(curbuf, NOT_VALID); 898 } 899 900 /* 901 * Get a key from the user without mapping. 902 * TODO: use terminal mode mappings. 903 */ 904 static int 905 term_vgetc() 906 { 907 int c; 908 909 ++no_mapping; 910 ++allow_keys; 911 got_int = FALSE; 912 c = vgetc(); 913 got_int = FALSE; 914 --no_mapping; 915 --allow_keys; 916 return c; 917 } 918 919 /* 920 * Send keys to terminal. 921 * Return FAIL when the key needs to be handled in Normal mode. 922 * Return OK when the key was dropped or sent to the terminal. 923 */ 924 int 925 send_keys_to_term(term_T *term, int c, int typed) 926 { 927 char msg[KEY_BUF_LEN]; 928 size_t len; 929 static int mouse_was_outside = FALSE; 930 int dragging_outside = FALSE; 931 932 /* Catch keys that need to be handled as in Normal mode. */ 933 switch (c) 934 { 935 case NUL: 936 case K_ZERO: 937 if (typed) 938 stuffcharReadbuff(c); 939 return FAIL; 940 941 case K_IGNORE: 942 return FAIL; 943 944 case K_LEFTDRAG: 945 case K_MIDDLEDRAG: 946 case K_RIGHTDRAG: 947 case K_X1DRAG: 948 case K_X2DRAG: 949 dragging_outside = mouse_was_outside; 950 /* FALLTHROUGH */ 951 case K_LEFTMOUSE: 952 case K_LEFTMOUSE_NM: 953 case K_LEFTRELEASE: 954 case K_LEFTRELEASE_NM: 955 case K_MIDDLEMOUSE: 956 case K_MIDDLERELEASE: 957 case K_RIGHTMOUSE: 958 case K_RIGHTRELEASE: 959 case K_X1MOUSE: 960 case K_X1RELEASE: 961 case K_X2MOUSE: 962 case K_X2RELEASE: 963 964 case K_MOUSEUP: 965 case K_MOUSEDOWN: 966 case K_MOUSELEFT: 967 case K_MOUSERIGHT: 968 if (mouse_row < W_WINROW(curwin) 969 || mouse_row >= (W_WINROW(curwin) + curwin->w_height) 970 || mouse_col < W_WINCOL(curwin) 971 || mouse_col >= W_ENDCOL(curwin) 972 || dragging_outside) 973 { 974 /* click or scroll outside the current window */ 975 if (typed) 976 { 977 stuffcharReadbuff(c); 978 mouse_was_outside = TRUE; 979 } 980 return FAIL; 981 } 982 } 983 if (typed) 984 mouse_was_outside = FALSE; 985 986 /* Convert the typed key to a sequence of bytes for the job. */ 987 len = term_convert_key(term, c, msg); 988 if (len > 0) 989 /* TODO: if FAIL is returned, stop? */ 990 channel_send(term->tl_job->jv_channel, PART_IN, 991 (char_u *)msg, (int)len, NULL); 992 993 return OK; 994 } 995 996 static void 997 position_cursor(win_T *wp, VTermPos *pos) 998 { 999 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1)); 1000 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1)); 1001 wp->w_valid |= (VALID_WCOL|VALID_WROW); 1002 } 1003 1004 /* 1005 * Handle CTRL-W "": send register contents to the job. 1006 */ 1007 static void 1008 term_paste_register(int prev_c UNUSED) 1009 { 1010 int c; 1011 list_T *l; 1012 listitem_T *item; 1013 long reglen = 0; 1014 int type; 1015 1016 #ifdef FEAT_CMDL_INFO 1017 if (add_to_showcmd(prev_c)) 1018 if (add_to_showcmd('"')) 1019 out_flush(); 1020 #endif 1021 c = term_vgetc(); 1022 #ifdef FEAT_CMDL_INFO 1023 clear_showcmd(); 1024 #endif 1025 1026 /* CTRL-W "= prompt for expression to evaluate. */ 1027 if (c == '=' && get_expr_register() != '=') 1028 return; 1029 1030 l = (list_T *)get_reg_contents(c, GREG_LIST); 1031 if (l != NULL) 1032 { 1033 type = get_reg_type(c, ®len); 1034 for (item = l->lv_first; item != NULL; item = item->li_next) 1035 { 1036 char_u *s = get_tv_string(&item->li_tv); 1037 1038 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN, 1039 s, STRLEN(s), NULL); 1040 if (item->li_next != NULL || type == MLINE) 1041 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN, 1042 (char_u *)"\r", 1, NULL); 1043 } 1044 list_free(l); 1045 } 1046 } 1047 1048 /* 1049 * Returns TRUE if the current window contains a terminal and we are sending 1050 * keys to the job. 1051 */ 1052 int 1053 term_use_loop(void) 1054 { 1055 term_T *term = curbuf->b_term; 1056 1057 return term != NULL 1058 && !term->tl_normal_mode 1059 && term->tl_vterm != NULL 1060 && term_job_running(term); 1061 } 1062 1063 /* 1064 * Wait for input and send it to the job. 1065 * Return when the start of a CTRL-W command is typed or anything else that 1066 * should be handled as a Normal mode command. 1067 * Returns OK if a typed character is to be handled in Normal mode, FAIL if 1068 * the terminal was closed. 1069 */ 1070 int 1071 terminal_loop(void) 1072 { 1073 int c; 1074 int termkey = 0; 1075 1076 if (*curwin->w_p_tk != NUL) 1077 termkey = string_to_key(curwin->w_p_tk, TRUE); 1078 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos); 1079 1080 for (;;) 1081 { 1082 /* TODO: skip screen update when handling a sequence of keys. */ 1083 /* Repeat redrawing in case a message is received while redrawing. */ 1084 while (curwin->w_redr_type != 0) 1085 update_screen(0); 1086 update_cursor(curbuf->b_term, FALSE); 1087 1088 c = term_vgetc(); 1089 if (!term_use_loop()) 1090 /* job finished while waiting for a character */ 1091 break; 1092 1093 #ifdef UNIX 1094 may_send_sigint(c, curbuf->b_term->tl_job->jv_pid, 0); 1095 #endif 1096 #ifdef WIN3264 1097 if (c == Ctrl_C) 1098 /* We don't know if the job can handle CTRL-C itself or not, this 1099 * may kill the shell instead of killing the command running in the 1100 * shell. */ 1101 mch_stop_job(curbuf->b_term->tl_job, (char_u *)"quit"); 1102 #endif 1103 1104 if (c == (termkey == 0 ? Ctrl_W : termkey) || c == Ctrl_BSL) 1105 { 1106 int prev_c = c; 1107 1108 #ifdef FEAT_CMDL_INFO 1109 if (add_to_showcmd(c)) 1110 out_flush(); 1111 #endif 1112 c = term_vgetc(); 1113 #ifdef FEAT_CMDL_INFO 1114 clear_showcmd(); 1115 #endif 1116 if (!term_use_loop()) 1117 /* job finished while waiting for a character */ 1118 break; 1119 1120 if (prev_c == Ctrl_BSL) 1121 { 1122 if (c == Ctrl_N) 1123 { 1124 /* CTRL-\ CTRL-N : go to Terminal-Normal mode. */ 1125 term_enter_normal_mode(); 1126 return FAIL; 1127 } 1128 /* Send both keys to the terminal. */ 1129 send_keys_to_term(curbuf->b_term, prev_c, TRUE); 1130 } 1131 else if (termkey == 0 && c == '.') 1132 { 1133 /* "CTRL-W .": send CTRL-W to the job */ 1134 c = Ctrl_W; 1135 } 1136 else if (c == 'N') 1137 { 1138 /* CTRL-W N : go to Terminal-Normal mode. */ 1139 term_enter_normal_mode(); 1140 return FAIL; 1141 } 1142 else if (c == '"') 1143 { 1144 term_paste_register(prev_c); 1145 continue; 1146 } 1147 else if (termkey == 0 || c != termkey) 1148 { 1149 stuffcharReadbuff(Ctrl_W); 1150 stuffcharReadbuff(c); 1151 return OK; 1152 } 1153 } 1154 if (send_keys_to_term(curbuf->b_term, c, TRUE) != OK) 1155 return OK; 1156 } 1157 return FAIL; 1158 } 1159 1160 /* 1161 * Called when a job has finished. 1162 * This updates the title and status, but does not close the vter, because 1163 * there might still be pending output in the channel. 1164 */ 1165 void 1166 term_job_ended(job_T *job) 1167 { 1168 term_T *term; 1169 int did_one = FALSE; 1170 1171 for (term = first_term; term != NULL; term = term->tl_next) 1172 if (term->tl_job == job) 1173 { 1174 vim_free(term->tl_title); 1175 term->tl_title = NULL; 1176 vim_free(term->tl_status_text); 1177 term->tl_status_text = NULL; 1178 redraw_buf_and_status_later(term->tl_buffer, VALID); 1179 did_one = TRUE; 1180 } 1181 if (did_one) 1182 redraw_statuslines(); 1183 if (curbuf->b_term != NULL) 1184 { 1185 if (curbuf->b_term->tl_job == job) 1186 maketitle(); 1187 update_cursor(curbuf->b_term, TRUE); 1188 } 1189 } 1190 1191 static void 1192 may_toggle_cursor(term_T *term) 1193 { 1194 if (curbuf == term->tl_buffer) 1195 { 1196 if (term->tl_cursor_visible) 1197 cursor_on(); 1198 else 1199 cursor_off(); 1200 } 1201 } 1202 1203 static int 1204 handle_damage(VTermRect rect, void *user) 1205 { 1206 term_T *term = (term_T *)user; 1207 1208 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row); 1209 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row); 1210 redraw_buf_later(term->tl_buffer, NOT_VALID); 1211 return 1; 1212 } 1213 1214 static int 1215 handle_moverect(VTermRect dest UNUSED, VTermRect src UNUSED, void *user) 1216 { 1217 term_T *term = (term_T *)user; 1218 1219 /* TODO */ 1220 redraw_buf_later(term->tl_buffer, NOT_VALID); 1221 return 1; 1222 } 1223 1224 static int 1225 handle_movecursor( 1226 VTermPos pos, 1227 VTermPos oldpos UNUSED, 1228 int visible, 1229 void *user) 1230 { 1231 term_T *term = (term_T *)user; 1232 win_T *wp; 1233 1234 term->tl_cursor_pos = pos; 1235 term->tl_cursor_visible = visible; 1236 1237 FOR_ALL_WINDOWS(wp) 1238 { 1239 if (wp->w_buffer == term->tl_buffer) 1240 position_cursor(wp, &pos); 1241 } 1242 if (term->tl_buffer == curbuf && !term->tl_normal_mode) 1243 { 1244 may_toggle_cursor(term); 1245 update_cursor(term, term->tl_cursor_visible); 1246 } 1247 1248 return 1; 1249 } 1250 1251 static int 1252 handle_settermprop( 1253 VTermProp prop, 1254 VTermValue *value, 1255 void *user) 1256 { 1257 term_T *term = (term_T *)user; 1258 1259 switch (prop) 1260 { 1261 case VTERM_PROP_TITLE: 1262 vim_free(term->tl_title); 1263 term->tl_title = vim_strsave((char_u *)value->string); 1264 vim_free(term->tl_status_text); 1265 term->tl_status_text = NULL; 1266 if (term == curbuf->b_term) 1267 maketitle(); 1268 break; 1269 1270 case VTERM_PROP_CURSORVISIBLE: 1271 term->tl_cursor_visible = value->boolean; 1272 may_toggle_cursor(term); 1273 out_flush(); 1274 break; 1275 1276 default: 1277 break; 1278 } 1279 /* Always return 1, otherwise vterm doesn't store the value internally. */ 1280 return 1; 1281 } 1282 1283 /* 1284 * The job running in the terminal resized the terminal. 1285 */ 1286 static int 1287 handle_resize(int rows, int cols, void *user) 1288 { 1289 term_T *term = (term_T *)user; 1290 win_T *wp; 1291 1292 term->tl_rows = rows; 1293 term->tl_cols = cols; 1294 FOR_ALL_WINDOWS(wp) 1295 { 1296 if (wp->w_buffer == term->tl_buffer) 1297 { 1298 win_setheight_win(rows, wp); 1299 win_setwidth_win(cols, wp); 1300 } 1301 } 1302 1303 redraw_buf_later(term->tl_buffer, NOT_VALID); 1304 return 1; 1305 } 1306 1307 /* 1308 * Handle a line that is pushed off the top of the screen. 1309 */ 1310 static int 1311 handle_pushline(int cols, const VTermScreenCell *cells, void *user) 1312 { 1313 term_T *term = (term_T *)user; 1314 1315 /* TODO: Limit the number of lines that are stored. */ 1316 /* TODO: put the text in the buffer. */ 1317 if (ga_grow(&term->tl_scrollback, 1) == OK) 1318 { 1319 VTermScreenCell *p = NULL; 1320 int len = 0; 1321 int i; 1322 sb_line_T *line; 1323 1324 /* do not store empty cells at the end */ 1325 for (i = 0; i < cols; ++i) 1326 if (cells[i].chars[0] != 0) 1327 len = i + 1; 1328 1329 if (len > 0) 1330 p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len); 1331 if (p != NULL) 1332 mch_memmove(p, cells, sizeof(VTermScreenCell) * len); 1333 1334 line = (sb_line_T *)term->tl_scrollback.ga_data 1335 + term->tl_scrollback.ga_len; 1336 line->sb_cols = len; 1337 line->sb_cells = p; 1338 ++term->tl_scrollback.ga_len; 1339 ++term->tl_scrollback_scrolled; 1340 1341 add_scrollback_line_to_buffer(term); 1342 } 1343 return 0; /* ignored */ 1344 } 1345 1346 static VTermScreenCallbacks screen_callbacks = { 1347 handle_damage, /* damage */ 1348 handle_moverect, /* moverect */ 1349 handle_movecursor, /* movecursor */ 1350 handle_settermprop, /* settermprop */ 1351 NULL, /* bell */ 1352 handle_resize, /* resize */ 1353 handle_pushline, /* sb_pushline */ 1354 NULL /* sb_popline */ 1355 }; 1356 1357 /* 1358 * Called when a channel has been closed. 1359 * If this was a channel for a terminal window then finish it up. 1360 */ 1361 void 1362 term_channel_closed(channel_T *ch) 1363 { 1364 term_T *term; 1365 int did_one = FALSE; 1366 1367 for (term = first_term; term != NULL; term = term->tl_next) 1368 if (term->tl_job == ch->ch_job) 1369 { 1370 term->tl_channel_closed = TRUE; 1371 1372 vim_free(term->tl_title); 1373 term->tl_title = NULL; 1374 vim_free(term->tl_status_text); 1375 term->tl_status_text = NULL; 1376 1377 /* Unless in Terminal-Normal mode: clear the vterm. */ 1378 if (!term->tl_normal_mode) 1379 cleanup_vterm(term); 1380 1381 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID); 1382 did_one = TRUE; 1383 } 1384 if (did_one) 1385 { 1386 redraw_statuslines(); 1387 1388 /* Need to break out of vgetc(). */ 1389 ins_char_typebuf(K_IGNORE); 1390 typebuf_was_filled = TRUE; 1391 1392 term = curbuf->b_term; 1393 if (term != NULL) 1394 { 1395 if (term->tl_job == ch->ch_job) 1396 maketitle(); 1397 update_cursor(term, term->tl_cursor_visible); 1398 } 1399 } 1400 } 1401 1402 /* 1403 * Reverse engineer the RGB value into a cterm color index. 1404 * First color is 1. Return 0 if no match found. 1405 */ 1406 static int 1407 color2index(VTermColor *color, int fg, int *boldp) 1408 { 1409 int red = color->red; 1410 int blue = color->blue; 1411 int green = color->green; 1412 1413 /* The argument for lookup_color() is for the color_names[] table. */ 1414 if (red == 0) 1415 { 1416 if (green == 0) 1417 { 1418 if (blue == 0) 1419 return lookup_color(0, fg, boldp) + 1; /* black */ 1420 if (blue == 224) 1421 return lookup_color(1, fg, boldp) + 1; /* dark blue */ 1422 } 1423 else if (green == 224) 1424 { 1425 if (blue == 0) 1426 return lookup_color(2, fg, boldp) + 1; /* dark green */ 1427 if (blue == 224) 1428 return lookup_color(3, fg, boldp) + 1; /* dark cyan */ 1429 } 1430 } 1431 else if (red == 224) 1432 { 1433 if (green == 0) 1434 { 1435 if (blue == 0) 1436 return lookup_color(4, fg, boldp) + 1; /* dark red */ 1437 if (blue == 224) 1438 return lookup_color(5, fg, boldp) + 1; /* dark magenta */ 1439 } 1440 else if (green == 224) 1441 { 1442 if (blue == 0) 1443 return lookup_color(6, fg, boldp) + 1; /* dark yellow / brown */ 1444 if (blue == 224) 1445 return lookup_color(8, fg, boldp) + 1; /* white / light grey */ 1446 } 1447 } 1448 else if (red == 128) 1449 { 1450 if (green == 128 && blue == 128) 1451 return lookup_color(12, fg, boldp) + 1; /* high intensity black / dark grey */ 1452 } 1453 else if (red == 255) 1454 { 1455 if (green == 64) 1456 { 1457 if (blue == 64) 1458 return lookup_color(20, fg, boldp) + 1; /* light red */ 1459 if (blue == 255) 1460 return lookup_color(22, fg, boldp) + 1; /* light magenta */ 1461 } 1462 else if (green == 255) 1463 { 1464 if (blue == 64) 1465 return lookup_color(24, fg, boldp) + 1; /* yellow */ 1466 if (blue == 255) 1467 return lookup_color(26, fg, boldp) + 1; /* white */ 1468 } 1469 } 1470 else if (red == 64) 1471 { 1472 if (green == 64) 1473 { 1474 if (blue == 255) 1475 return lookup_color(14, fg, boldp) + 1; /* light blue */ 1476 } 1477 else if (green == 255) 1478 { 1479 if (blue == 64) 1480 return lookup_color(16, fg, boldp) + 1; /* light green */ 1481 if (blue == 255) 1482 return lookup_color(18, fg, boldp) + 1; /* light cyan */ 1483 } 1484 } 1485 if (t_colors >= 256) 1486 { 1487 if (red == blue && red == green) 1488 { 1489 /* 24-color greyscale */ 1490 static int cutoff[23] = { 1491 0x05, 0x10, 0x1B, 0x26, 0x31, 0x3C, 0x47, 0x52, 1492 0x5D, 0x68, 0x73, 0x7F, 0x8A, 0x95, 0xA0, 0xAB, 1493 0xB6, 0xC1, 0xCC, 0xD7, 0xE2, 0xED, 0xF9}; 1494 int i; 1495 1496 for (i = 0; i < 23; ++i) 1497 if (red < cutoff[i]) 1498 return i + 233; 1499 return 256; 1500 } 1501 1502 /* 216-color cube */ 1503 return 17 + ((red + 25) / 0x33) * 36 1504 + ((green + 25) / 0x33) * 6 1505 + (blue + 25) / 0x33; 1506 } 1507 return 0; 1508 } 1509 1510 /* 1511 * Convert the attributes of a vterm cell into an attribute index. 1512 */ 1513 static int 1514 cell2attr(VTermScreenCell *cell) 1515 { 1516 int attr = 0; 1517 1518 if (cell->attrs.bold) 1519 attr |= HL_BOLD; 1520 if (cell->attrs.underline) 1521 attr |= HL_UNDERLINE; 1522 if (cell->attrs.italic) 1523 attr |= HL_ITALIC; 1524 if (cell->attrs.strike) 1525 attr |= HL_STANDOUT; 1526 if (cell->attrs.reverse) 1527 attr |= HL_INVERSE; 1528 1529 #ifdef FEAT_GUI 1530 if (gui.in_use) 1531 { 1532 guicolor_T fg, bg; 1533 1534 fg = gui_mch_get_rgb_color(cell->fg.red, cell->fg.green, cell->fg.blue); 1535 bg = gui_mch_get_rgb_color(cell->bg.red, cell->bg.green, cell->bg.blue); 1536 return get_gui_attr_idx(attr, fg, bg); 1537 } 1538 else 1539 #endif 1540 #ifdef FEAT_TERMGUICOLORS 1541 if (p_tgc) 1542 { 1543 guicolor_T fg, bg; 1544 1545 fg = gui_get_rgb_color_cmn(cell->fg.red, cell->fg.green, cell->fg.blue); 1546 bg = gui_get_rgb_color_cmn(cell->bg.red, cell->bg.green, cell->bg.blue); 1547 1548 return get_tgc_attr_idx(attr, fg, bg); 1549 } 1550 else 1551 #endif 1552 { 1553 int bold = MAYBE; 1554 int fg = color2index(&cell->fg, TRUE, &bold); 1555 int bg = color2index(&cell->bg, FALSE, &bold); 1556 1557 /* with 8 colors set the bold attribute to get a bright foreground */ 1558 if (bold == TRUE) 1559 attr |= HL_BOLD; 1560 return get_cterm_attr_idx(attr, fg, bg); 1561 } 1562 return 0; 1563 } 1564 1565 /* 1566 * Called to update a window that contains an active terminal. 1567 * Returns FAIL when there is no terminal running in this window or in 1568 * Terminal-Normal mode. 1569 */ 1570 int 1571 term_update_window(win_T *wp) 1572 { 1573 term_T *term = wp->w_buffer->b_term; 1574 VTerm *vterm; 1575 VTermScreen *screen; 1576 VTermState *state; 1577 VTermPos pos; 1578 1579 if (term == NULL || term->tl_vterm == NULL || term->tl_normal_mode) 1580 return FAIL; 1581 1582 vterm = term->tl_vterm; 1583 screen = vterm_obtain_screen(vterm); 1584 state = vterm_obtain_state(vterm); 1585 1586 /* 1587 * If the window was resized a redraw will be triggered and we get here. 1588 * Adjust the size of the vterm unless 'termsize' specifies a fixed size. 1589 */ 1590 if ((!term->tl_rows_fixed && term->tl_rows != wp->w_height) 1591 || (!term->tl_cols_fixed && term->tl_cols != wp->w_width)) 1592 { 1593 int rows = term->tl_rows_fixed ? term->tl_rows : wp->w_height; 1594 int cols = term->tl_cols_fixed ? term->tl_cols : wp->w_width; 1595 win_T *twp; 1596 1597 FOR_ALL_WINDOWS(twp) 1598 { 1599 /* When more than one window shows the same terminal, use the 1600 * smallest size. */ 1601 if (twp->w_buffer == term->tl_buffer) 1602 { 1603 if (!term->tl_rows_fixed && rows > twp->w_height) 1604 rows = twp->w_height; 1605 if (!term->tl_cols_fixed && cols > twp->w_width) 1606 cols = twp->w_width; 1607 } 1608 } 1609 1610 vterm_set_size(vterm, rows, cols); 1611 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines", 1612 rows); 1613 term_report_winsize(term, rows, cols); 1614 } 1615 1616 /* The cursor may have been moved when resizing. */ 1617 vterm_state_get_cursorpos(state, &pos); 1618 position_cursor(wp, &pos); 1619 1620 /* TODO: Only redraw what changed. */ 1621 for (pos.row = 0; pos.row < wp->w_height; ++pos.row) 1622 { 1623 int off = screen_get_current_line_off(); 1624 int max_col = MIN(wp->w_width, term->tl_cols); 1625 1626 if (pos.row < term->tl_rows) 1627 { 1628 for (pos.col = 0; pos.col < max_col; ) 1629 { 1630 VTermScreenCell cell; 1631 int c; 1632 1633 if (vterm_screen_get_cell(screen, pos, &cell) == 0) 1634 vim_memset(&cell, 0, sizeof(cell)); 1635 1636 /* TODO: composing chars */ 1637 c = cell.chars[0]; 1638 if (c == NUL) 1639 { 1640 ScreenLines[off] = ' '; 1641 #if defined(FEAT_MBYTE) 1642 if (enc_utf8) 1643 ScreenLinesUC[off] = NUL; 1644 #endif 1645 } 1646 else 1647 { 1648 #if defined(FEAT_MBYTE) 1649 if (enc_utf8 && c >= 0x80) 1650 { 1651 ScreenLines[off] = ' '; 1652 ScreenLinesUC[off] = c; 1653 } 1654 else 1655 { 1656 ScreenLines[off] = c; 1657 if (enc_utf8) 1658 ScreenLinesUC[off] = NUL; 1659 } 1660 #else 1661 ScreenLines[off] = c; 1662 #endif 1663 } 1664 ScreenAttrs[off] = cell2attr(&cell); 1665 1666 ++pos.col; 1667 ++off; 1668 if (cell.width == 2) 1669 { 1670 ScreenLines[off] = NUL; 1671 #if defined(FEAT_MBYTE) 1672 if (enc_utf8) 1673 ScreenLinesUC[off] = NUL; 1674 #endif 1675 ++pos.col; 1676 ++off; 1677 } 1678 } 1679 } 1680 else 1681 pos.col = 0; 1682 1683 screen_line(wp->w_winrow + pos.row, wp->w_wincol, 1684 pos.col, wp->w_width, FALSE); 1685 } 1686 1687 return OK; 1688 } 1689 1690 /* 1691 * Return TRUE if "wp" is a terminal window where the job has finished. 1692 */ 1693 int 1694 term_is_finished(buf_T *buf) 1695 { 1696 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL; 1697 } 1698 1699 /* 1700 * Return TRUE if "wp" is a terminal window where the job has finished or we 1701 * are in Terminal-Normal mode, thus we show the buffer contents. 1702 */ 1703 int 1704 term_show_buffer(buf_T *buf) 1705 { 1706 term_T *term = buf->b_term; 1707 1708 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode); 1709 } 1710 1711 /* 1712 * The current buffer is going to be changed. If there is terminal 1713 * highlighting remove it now. 1714 */ 1715 void 1716 term_change_in_curbuf(void) 1717 { 1718 term_T *term = curbuf->b_term; 1719 1720 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0) 1721 { 1722 free_scrollback(term); 1723 redraw_buf_later(term->tl_buffer, NOT_VALID); 1724 1725 /* The buffer is now like a normal buffer, it cannot be easily 1726 * abandoned when changed. */ 1727 set_string_option_direct((char_u *)"buftype", -1, 1728 (char_u *)"", OPT_FREE|OPT_LOCAL, 0); 1729 } 1730 } 1731 1732 /* 1733 * Get the screen attribute for a position in the buffer. 1734 */ 1735 int 1736 term_get_attr(buf_T *buf, linenr_T lnum, int col) 1737 { 1738 term_T *term = buf->b_term; 1739 sb_line_T *line; 1740 1741 if (lnum > term->tl_scrollback.ga_len) 1742 return 0; 1743 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1; 1744 if (col >= line->sb_cols) 1745 return 0; 1746 return cell2attr(line->sb_cells + col); 1747 } 1748 1749 /* 1750 * Create a new vterm and initialize it. 1751 */ 1752 static void 1753 create_vterm(term_T *term, int rows, int cols) 1754 { 1755 VTerm *vterm; 1756 VTermScreen *screen; 1757 1758 vterm = vterm_new(rows, cols); 1759 term->tl_vterm = vterm; 1760 screen = vterm_obtain_screen(vterm); 1761 vterm_screen_set_callbacks(screen, &screen_callbacks, term); 1762 /* TODO: depends on 'encoding'. */ 1763 vterm_set_utf8(vterm, 1); 1764 1765 /* Vterm uses a default black background. Set it to white when 1766 * 'background' is "light". */ 1767 if (*p_bg == 'l') 1768 { 1769 VTermColor fg, bg; 1770 1771 fg.red = fg.green = fg.blue = 0; 1772 bg.red = bg.green = bg.blue = 255; 1773 vterm_state_set_default_colors(vterm_obtain_state(vterm), &fg, &bg); 1774 } 1775 1776 /* Required to initialize most things. */ 1777 vterm_screen_reset(screen, 1 /* hard */); 1778 } 1779 1780 /* 1781 * Return the text to show for the buffer name and status. 1782 */ 1783 char_u * 1784 term_get_status_text(term_T *term) 1785 { 1786 if (term->tl_status_text == NULL) 1787 { 1788 char_u *txt; 1789 size_t len; 1790 1791 if (term->tl_normal_mode) 1792 { 1793 if (term_job_running(term)) 1794 txt = (char_u *)_("Terminal"); 1795 else 1796 txt = (char_u *)_("Terminal-finished"); 1797 } 1798 else if (term->tl_title != NULL) 1799 txt = term->tl_title; 1800 else if (term_job_running(term)) 1801 txt = (char_u *)_("running"); 1802 else 1803 txt = (char_u *)_("finished"); 1804 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt); 1805 term->tl_status_text = alloc((int)len); 1806 if (term->tl_status_text != NULL) 1807 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]", 1808 term->tl_buffer->b_fname, txt); 1809 } 1810 return term->tl_status_text; 1811 } 1812 1813 /* 1814 * Mark references in jobs of terminals. 1815 */ 1816 int 1817 set_ref_in_term(int copyID) 1818 { 1819 int abort = FALSE; 1820 term_T *term; 1821 typval_T tv; 1822 1823 for (term = first_term; term != NULL; term = term->tl_next) 1824 if (term->tl_job != NULL) 1825 { 1826 tv.v_type = VAR_JOB; 1827 tv.vval.v_job = term->tl_job; 1828 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL); 1829 } 1830 return abort; 1831 } 1832 1833 /* 1834 * Get the buffer from the first argument in "argvars". 1835 * Returns NULL when the buffer is not for a terminal window. 1836 */ 1837 static buf_T * 1838 term_get_buf(typval_T *argvars) 1839 { 1840 buf_T *buf; 1841 1842 (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ 1843 ++emsg_off; 1844 buf = get_buf_tv(&argvars[0], FALSE); 1845 --emsg_off; 1846 if (buf == NULL || buf->b_term == NULL) 1847 return NULL; 1848 return buf; 1849 } 1850 1851 /* 1852 * "term_getattr(attr, name)" function 1853 */ 1854 void 1855 f_term_getattr(typval_T *argvars, typval_T *rettv) 1856 { 1857 int attr; 1858 size_t i; 1859 char_u *name; 1860 1861 static struct { 1862 char *name; 1863 int attr; 1864 } attrs[] = { 1865 {"bold", HL_BOLD}, 1866 {"italic", HL_ITALIC}, 1867 {"underline", HL_UNDERLINE}, 1868 {"strike", HL_STANDOUT}, 1869 {"reverse", HL_INVERSE}, 1870 }; 1871 1872 attr = get_tv_number(&argvars[0]); 1873 name = get_tv_string_chk(&argvars[1]); 1874 if (name == NULL) 1875 return; 1876 1877 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i) 1878 if (STRCMP(name, attrs[i].name) == 0) 1879 { 1880 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0; 1881 break; 1882 } 1883 } 1884 1885 /* 1886 * "term_getcursor(buf)" function 1887 */ 1888 void 1889 f_term_getcursor(typval_T *argvars, typval_T *rettv) 1890 { 1891 buf_T *buf = term_get_buf(argvars); 1892 list_T *l; 1893 1894 if (rettv_list_alloc(rettv) == FAIL) 1895 return; 1896 if (buf == NULL) 1897 return; 1898 1899 l = rettv->vval.v_list; 1900 list_append_number(l, buf->b_term->tl_cursor_pos.row + 1); 1901 list_append_number(l, buf->b_term->tl_cursor_pos.col + 1); 1902 list_append_number(l, buf->b_term->tl_cursor_visible); 1903 } 1904 1905 /* 1906 * "term_getjob(buf)" function 1907 */ 1908 void 1909 f_term_getjob(typval_T *argvars, typval_T *rettv) 1910 { 1911 buf_T *buf = term_get_buf(argvars); 1912 1913 rettv->v_type = VAR_JOB; 1914 rettv->vval.v_job = NULL; 1915 if (buf == NULL) 1916 return; 1917 1918 rettv->vval.v_job = buf->b_term->tl_job; 1919 if (rettv->vval.v_job != NULL) 1920 ++rettv->vval.v_job->jv_refcount; 1921 } 1922 1923 static int 1924 get_row_number(typval_T *tv, term_T *term) 1925 { 1926 if (tv->v_type == VAR_STRING 1927 && tv->vval.v_string != NULL 1928 && STRCMP(tv->vval.v_string, ".") == 0) 1929 return term->tl_cursor_pos.row; 1930 return (int)get_tv_number(tv) - 1; 1931 } 1932 1933 /* 1934 * "term_getline(buf, row)" function 1935 */ 1936 void 1937 f_term_getline(typval_T *argvars, typval_T *rettv) 1938 { 1939 buf_T *buf = term_get_buf(argvars); 1940 term_T *term; 1941 int row; 1942 1943 rettv->v_type = VAR_STRING; 1944 if (buf == NULL) 1945 return; 1946 term = buf->b_term; 1947 row = get_row_number(&argvars[1], term); 1948 1949 if (term->tl_vterm == NULL) 1950 { 1951 linenr_T lnum = row + term->tl_scrollback_scrolled + 1; 1952 1953 /* vterm is finished, get the text from the buffer */ 1954 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count) 1955 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE)); 1956 } 1957 else 1958 { 1959 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm); 1960 VTermRect rect; 1961 int len; 1962 char_u *p; 1963 1964 if (row < 0 || row >= term->tl_rows) 1965 return; 1966 len = term->tl_cols * MB_MAXBYTES + 1; 1967 p = alloc(len); 1968 if (p == NULL) 1969 return; 1970 rettv->vval.v_string = p; 1971 1972 rect.start_col = 0; 1973 rect.end_col = term->tl_cols; 1974 rect.start_row = row; 1975 rect.end_row = row + 1; 1976 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL; 1977 } 1978 } 1979 1980 /* 1981 * "term_getsize(buf)" function 1982 */ 1983 void 1984 f_term_getsize(typval_T *argvars, typval_T *rettv) 1985 { 1986 buf_T *buf = term_get_buf(argvars); 1987 list_T *l; 1988 1989 if (rettv_list_alloc(rettv) == FAIL) 1990 return; 1991 if (buf == NULL) 1992 return; 1993 1994 l = rettv->vval.v_list; 1995 list_append_number(l, buf->b_term->tl_rows); 1996 list_append_number(l, buf->b_term->tl_cols); 1997 } 1998 1999 /* 2000 * "term_getstatus(buf)" function 2001 */ 2002 void 2003 f_term_getstatus(typval_T *argvars, typval_T *rettv) 2004 { 2005 buf_T *buf = term_get_buf(argvars); 2006 term_T *term; 2007 char_u val[100]; 2008 2009 rettv->v_type = VAR_STRING; 2010 if (buf == NULL) 2011 return; 2012 term = buf->b_term; 2013 2014 if (term_job_running(term)) 2015 STRCPY(val, "running"); 2016 else 2017 STRCPY(val, "finished"); 2018 if (term->tl_normal_mode) 2019 STRCAT(val, ",normal"); 2020 rettv->vval.v_string = vim_strsave(val); 2021 } 2022 2023 /* 2024 * "term_gettitle(buf)" function 2025 */ 2026 void 2027 f_term_gettitle(typval_T *argvars, typval_T *rettv) 2028 { 2029 buf_T *buf = term_get_buf(argvars); 2030 2031 rettv->v_type = VAR_STRING; 2032 if (buf == NULL) 2033 return; 2034 2035 if (buf->b_term->tl_title != NULL) 2036 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title); 2037 } 2038 2039 /* 2040 * "term_gettty(buf)" function 2041 */ 2042 void 2043 f_term_gettty(typval_T *argvars, typval_T *rettv) 2044 { 2045 buf_T *buf = term_get_buf(argvars); 2046 char_u *p; 2047 2048 rettv->v_type = VAR_STRING; 2049 if (buf == NULL) 2050 return; 2051 if (buf->b_term->tl_job != NULL) 2052 p = buf->b_term->tl_job->jv_tty_name; 2053 else 2054 p = buf->b_term->tl_tty_name; 2055 if (p != NULL) 2056 rettv->vval.v_string = vim_strsave(p); 2057 } 2058 2059 /* 2060 * "term_list()" function 2061 */ 2062 void 2063 f_term_list(typval_T *argvars UNUSED, typval_T *rettv) 2064 { 2065 term_T *tp; 2066 list_T *l; 2067 2068 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL) 2069 return; 2070 2071 l = rettv->vval.v_list; 2072 for (tp = first_term; tp != NULL; tp = tp->tl_next) 2073 if (tp != NULL && tp->tl_buffer != NULL) 2074 if (list_append_number(l, 2075 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL) 2076 return; 2077 } 2078 2079 /* 2080 * "term_scrape(buf, row)" function 2081 */ 2082 void 2083 f_term_scrape(typval_T *argvars, typval_T *rettv) 2084 { 2085 buf_T *buf = term_get_buf(argvars); 2086 VTermScreen *screen = NULL; 2087 VTermPos pos; 2088 list_T *l; 2089 term_T *term; 2090 2091 if (rettv_list_alloc(rettv) == FAIL) 2092 return; 2093 if (buf == NULL) 2094 return; 2095 term = buf->b_term; 2096 if (term->tl_vterm != NULL) 2097 screen = vterm_obtain_screen(term->tl_vterm); 2098 2099 l = rettv->vval.v_list; 2100 pos.row = get_row_number(&argvars[1], term); 2101 for (pos.col = 0; pos.col < term->tl_cols; ) 2102 { 2103 dict_T *dcell; 2104 VTermScreenCell cell; 2105 char_u rgb[8]; 2106 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1]; 2107 int off = 0; 2108 int i; 2109 2110 if (screen == NULL) 2111 { 2112 linenr_T lnum = pos.row + term->tl_scrollback_scrolled; 2113 sb_line_T *line; 2114 2115 /* vterm has finished, get the cell from scrollback */ 2116 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len) 2117 break; 2118 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum; 2119 if (pos.col >= line->sb_cols) 2120 break; 2121 cell = line->sb_cells[pos.col]; 2122 } 2123 else if (vterm_screen_get_cell(screen, pos, &cell) == 0) 2124 break; 2125 dcell = dict_alloc(); 2126 list_append_dict(l, dcell); 2127 2128 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i) 2129 { 2130 if (cell.chars[i] == 0) 2131 break; 2132 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off); 2133 } 2134 mbs[off] = NUL; 2135 dict_add_nr_str(dcell, "chars", 0, mbs); 2136 2137 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x", 2138 cell.fg.red, cell.fg.green, cell.fg.blue); 2139 dict_add_nr_str(dcell, "fg", 0, rgb); 2140 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x", 2141 cell.bg.red, cell.bg.green, cell.bg.blue); 2142 dict_add_nr_str(dcell, "bg", 0, rgb); 2143 2144 dict_add_nr_str(dcell, "attr", cell2attr(&cell), NULL); 2145 dict_add_nr_str(dcell, "width", cell.width, NULL); 2146 2147 ++pos.col; 2148 if (cell.width == 2) 2149 ++pos.col; 2150 } 2151 } 2152 2153 /* 2154 * "term_sendkeys(buf, keys)" function 2155 */ 2156 void 2157 f_term_sendkeys(typval_T *argvars, typval_T *rettv) 2158 { 2159 buf_T *buf = term_get_buf(argvars); 2160 char_u *msg; 2161 term_T *term; 2162 2163 rettv->v_type = VAR_UNKNOWN; 2164 if (buf == NULL) 2165 return; 2166 2167 msg = get_tv_string_chk(&argvars[1]); 2168 if (msg == NULL) 2169 return; 2170 term = buf->b_term; 2171 if (term->tl_vterm == NULL) 2172 return; 2173 2174 while (*msg != NUL) 2175 { 2176 send_keys_to_term(term, PTR2CHAR(msg), FALSE); 2177 msg += MB_PTR2LEN(msg); 2178 } 2179 2180 if (!term->tl_normal_mode) 2181 { 2182 /* TODO: only update once in a while. */ 2183 update_screen(0); 2184 if (buf == curbuf) 2185 update_cursor(term, TRUE); 2186 } 2187 } 2188 2189 /* 2190 * "term_start(command, options)" function 2191 */ 2192 void 2193 f_term_start(typval_T *argvars, typval_T *rettv) 2194 { 2195 char_u *cmd = get_tv_string_chk(&argvars[0]); 2196 jobopt_T opt; 2197 2198 if (cmd == NULL) 2199 return; 2200 init_job_options(&opt); 2201 /* TODO: allow more job options */ 2202 if (argvars[1].v_type != VAR_UNKNOWN 2203 && get_job_options(&argvars[1], &opt, 2204 JO_TIMEOUT_ALL + JO_STOPONEXIT 2205 + JO_EXIT_CB + JO_CLOSE_CALLBACK 2206 + JO2_TERM_NAME) == FAIL) 2207 return; 2208 2209 term_start(cmd, &opt); 2210 2211 if (curbuf->b_term != NULL) 2212 rettv->vval.v_number = curbuf->b_fnum; 2213 } 2214 2215 /* 2216 * "term_wait" function 2217 */ 2218 void 2219 f_term_wait(typval_T *argvars, typval_T *rettv UNUSED) 2220 { 2221 buf_T *buf = term_get_buf(argvars); 2222 2223 if (buf == NULL) 2224 { 2225 ch_log(NULL, "term_wait(): invalid argument"); 2226 return; 2227 } 2228 if (buf->b_term->tl_job == NULL) 2229 { 2230 ch_log(NULL, "term_wait(): no job to wait for"); 2231 return; 2232 } 2233 2234 /* Get the job status, this will detect a job that finished. */ 2235 if (STRCMP(job_status(buf->b_term->tl_job), "dead") == 0) 2236 { 2237 /* The job is dead, keep reading channel I/O until the channel is 2238 * closed. */ 2239 ch_log(NULL, "term_wait(): waiting for channel to close"); 2240 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed) 2241 { 2242 mch_check_messages(); 2243 parse_queued_messages(); 2244 ui_delay(10L, FALSE); 2245 } 2246 mch_check_messages(); 2247 parse_queued_messages(); 2248 } 2249 else 2250 { 2251 mch_check_messages(); 2252 parse_queued_messages(); 2253 2254 /* Wait for 10 msec for any channel I/O. */ 2255 /* TODO: use delay from optional argument */ 2256 ui_delay(10L, TRUE); 2257 mch_check_messages(); 2258 2259 /* Flushing messages on channels is hopefully sufficient. 2260 * TODO: is there a better way? */ 2261 parse_queued_messages(); 2262 } 2263 } 2264 2265 # ifdef WIN3264 2266 2267 /************************************** 2268 * 2. MS-Windows implementation. 2269 */ 2270 2271 #define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul 2272 #define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull 2273 2274 void* (*winpty_config_new)(UINT64, void*); 2275 void* (*winpty_open)(void*, void*); 2276 void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*); 2277 BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*); 2278 void (*winpty_config_set_initial_size)(void*, int, int); 2279 LPCWSTR (*winpty_conin_name)(void*); 2280 LPCWSTR (*winpty_conout_name)(void*); 2281 LPCWSTR (*winpty_conerr_name)(void*); 2282 void (*winpty_free)(void*); 2283 void (*winpty_config_free)(void*); 2284 void (*winpty_spawn_config_free)(void*); 2285 void (*winpty_error_free)(void*); 2286 LPCWSTR (*winpty_error_msg)(void*); 2287 BOOL (*winpty_set_size)(void*, int, int, void*); 2288 HANDLE (*winpty_agent_process)(void*); 2289 2290 #define WINPTY_DLL "winpty.dll" 2291 2292 static HINSTANCE hWinPtyDLL = NULL; 2293 2294 int 2295 dyn_winpty_init(void) 2296 { 2297 int i; 2298 static struct 2299 { 2300 char *name; 2301 FARPROC *ptr; 2302 } winpty_entry[] = 2303 { 2304 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name}, 2305 {"winpty_config_free", (FARPROC*)&winpty_config_free}, 2306 {"winpty_config_new", (FARPROC*)&winpty_config_new}, 2307 {"winpty_config_set_initial_size", (FARPROC*)&winpty_config_set_initial_size}, 2308 {"winpty_conin_name", (FARPROC*)&winpty_conin_name}, 2309 {"winpty_conout_name", (FARPROC*)&winpty_conout_name}, 2310 {"winpty_error_free", (FARPROC*)&winpty_error_free}, 2311 {"winpty_free", (FARPROC*)&winpty_free}, 2312 {"winpty_open", (FARPROC*)&winpty_open}, 2313 {"winpty_spawn", (FARPROC*)&winpty_spawn}, 2314 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free}, 2315 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new}, 2316 {"winpty_error_msg", (FARPROC*)&winpty_error_msg}, 2317 {"winpty_set_size", (FARPROC*)&winpty_set_size}, 2318 {"winpty_agent_process", (FARPROC*)&winpty_agent_process}, 2319 {NULL, NULL} 2320 }; 2321 2322 /* No need to initialize twice. */ 2323 if (hWinPtyDLL) 2324 return 1; 2325 /* Load winpty.dll */ 2326 hWinPtyDLL = vimLoadLib(WINPTY_DLL); 2327 if (!hWinPtyDLL) 2328 { 2329 EMSG2(_(e_loadlib), WINPTY_DLL); 2330 return 0; 2331 } 2332 for (i = 0; winpty_entry[i].name != NULL 2333 && winpty_entry[i].ptr != NULL; ++i) 2334 { 2335 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL, 2336 winpty_entry[i].name)) == NULL) 2337 { 2338 EMSG2(_(e_loadfunc), winpty_entry[i].name); 2339 return 0; 2340 } 2341 } 2342 2343 return 1; 2344 } 2345 2346 /* 2347 * Create a new terminal of "rows" by "cols" cells. 2348 * Store a reference in "term". 2349 * Return OK or FAIL. 2350 */ 2351 static int 2352 term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt) 2353 { 2354 WCHAR *p; 2355 channel_T *channel = NULL; 2356 job_T *job = NULL; 2357 DWORD error; 2358 HANDLE jo = NULL, child_process_handle, child_thread_handle; 2359 void *winpty_err; 2360 void *spawn_config = NULL; 2361 char buf[MAX_PATH]; 2362 2363 if (!dyn_winpty_init()) 2364 return FAIL; 2365 2366 p = enc_to_utf16(cmd, NULL); 2367 if (p == NULL) 2368 return FAIL; 2369 2370 job = job_alloc(); 2371 if (job == NULL) 2372 goto failed; 2373 2374 channel = add_channel(); 2375 if (channel == NULL) 2376 goto failed; 2377 2378 term->tl_winpty_config = winpty_config_new(0, &winpty_err); 2379 if (term->tl_winpty_config == NULL) 2380 goto failed; 2381 2382 winpty_config_set_initial_size(term->tl_winpty_config, cols, rows); 2383 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err); 2384 if (term->tl_winpty == NULL) 2385 goto failed; 2386 2387 /* TODO: if the command is "NONE" only create a pty. */ 2388 spawn_config = winpty_spawn_config_new( 2389 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN | 2390 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN, 2391 NULL, 2392 p, 2393 NULL, 2394 NULL, 2395 &winpty_err); 2396 if (spawn_config == NULL) 2397 goto failed; 2398 2399 channel = add_channel(); 2400 if (channel == NULL) 2401 goto failed; 2402 2403 job = job_alloc(); 2404 if (job == NULL) 2405 goto failed; 2406 2407 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle, 2408 &child_thread_handle, &error, &winpty_err)) 2409 goto failed; 2410 2411 channel_set_pipes(channel, 2412 (sock_T) CreateFileW( 2413 winpty_conin_name(term->tl_winpty), 2414 GENERIC_WRITE, 0, NULL, 2415 OPEN_EXISTING, 0, NULL), 2416 (sock_T) CreateFileW( 2417 winpty_conout_name(term->tl_winpty), 2418 GENERIC_READ, 0, NULL, 2419 OPEN_EXISTING, 0, NULL), 2420 (sock_T) CreateFileW( 2421 winpty_conerr_name(term->tl_winpty), 2422 GENERIC_READ, 0, NULL, 2423 OPEN_EXISTING, 0, NULL)); 2424 2425 jo = CreateJobObject(NULL, NULL); 2426 if (jo == NULL) 2427 goto failed; 2428 2429 if (!AssignProcessToJobObject(jo, child_process_handle)) 2430 { 2431 /* Failed, switch the way to terminate process with TerminateProcess. */ 2432 CloseHandle(jo); 2433 jo = NULL; 2434 } 2435 2436 winpty_spawn_config_free(spawn_config); 2437 vim_free(p); 2438 2439 create_vterm(term, rows, cols); 2440 2441 channel_set_job(channel, job, opt); 2442 job_set_options(job, opt); 2443 2444 job->jv_channel = channel; 2445 job->jv_proc_info.hProcess = child_process_handle; 2446 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle); 2447 job->jv_job_object = jo; 2448 job->jv_status = JOB_STARTED; 2449 sprintf(buf, "winpty://%lu", 2450 GetProcessId(winpty_agent_process(term->tl_winpty))); 2451 job->jv_tty_name = vim_strsave((char_u*)buf); 2452 ++job->jv_refcount; 2453 term->tl_job = job; 2454 2455 return OK; 2456 2457 failed: 2458 if (spawn_config != NULL) 2459 winpty_spawn_config_free(spawn_config); 2460 vim_free(p); 2461 if (channel != NULL) 2462 channel_clear(channel); 2463 if (job != NULL) 2464 { 2465 job->jv_channel = NULL; 2466 job_cleanup(job); 2467 } 2468 term->tl_job = NULL; 2469 if (jo != NULL) 2470 CloseHandle(jo); 2471 if (term->tl_winpty != NULL) 2472 winpty_free(term->tl_winpty); 2473 term->tl_winpty = NULL; 2474 if (term->tl_winpty_config != NULL) 2475 winpty_config_free(term->tl_winpty_config); 2476 term->tl_winpty_config = NULL; 2477 if (winpty_err != NULL) 2478 { 2479 char_u *msg = utf16_to_enc( 2480 (short_u *)winpty_error_msg(winpty_err), NULL); 2481 2482 EMSG(msg); 2483 winpty_error_free(winpty_err); 2484 } 2485 return FAIL; 2486 } 2487 2488 /* 2489 * Free the terminal emulator part of "term". 2490 */ 2491 static void 2492 term_free_vterm(term_T *term) 2493 { 2494 if (term->tl_winpty != NULL) 2495 winpty_free(term->tl_winpty); 2496 term->tl_winpty = NULL; 2497 if (term->tl_winpty_config != NULL) 2498 winpty_config_free(term->tl_winpty_config); 2499 term->tl_winpty_config = NULL; 2500 if (term->tl_vterm != NULL) 2501 vterm_free(term->tl_vterm); 2502 term->tl_vterm = NULL; 2503 } 2504 2505 /* 2506 * Request size to terminal. 2507 */ 2508 static void 2509 term_report_winsize(term_T *term, int rows, int cols) 2510 { 2511 winpty_set_size(term->tl_winpty, cols, rows, NULL); 2512 } 2513 2514 # else 2515 2516 /************************************** 2517 * 3. Unix-like implementation. 2518 */ 2519 2520 /* 2521 * Create a new terminal of "rows" by "cols" cells. 2522 * Start job for "cmd". 2523 * Store the pointers in "term". 2524 * Return OK or FAIL. 2525 */ 2526 static int 2527 term_and_job_init(term_T *term, int rows, int cols, char_u *cmd, jobopt_T *opt) 2528 { 2529 typval_T argvars[2]; 2530 2531 create_vterm(term, rows, cols); 2532 2533 /* TODO: if the command is "NONE" only create a pty. */ 2534 argvars[0].v_type = VAR_STRING; 2535 argvars[0].vval.v_string = cmd; 2536 2537 term->tl_job = job_start(argvars, opt); 2538 if (term->tl_job != NULL) 2539 ++term->tl_job->jv_refcount; 2540 2541 return term->tl_job != NULL 2542 && term->tl_job->jv_channel != NULL 2543 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL; 2544 } 2545 2546 /* 2547 * Free the terminal emulator part of "term". 2548 */ 2549 static void 2550 term_free_vterm(term_T *term) 2551 { 2552 if (term->tl_vterm != NULL) 2553 vterm_free(term->tl_vterm); 2554 term->tl_vterm = NULL; 2555 } 2556 2557 /* 2558 * Request size to terminal. 2559 */ 2560 static void 2561 term_report_winsize(term_T *term, int rows, int cols) 2562 { 2563 /* Use an ioctl() to report the new window size to the job. */ 2564 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL) 2565 { 2566 int fd = -1; 2567 int part; 2568 2569 for (part = PART_OUT; part < PART_COUNT; ++part) 2570 { 2571 fd = term->tl_job->jv_channel->ch_part[part].ch_fd; 2572 if (isatty(fd)) 2573 break; 2574 } 2575 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK) 2576 mch_stop_job(term->tl_job, (char_u *)"winch"); 2577 } 2578 } 2579 2580 # endif 2581 2582 #endif /* FEAT_TERMINAL */ 2583