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 * When the buffer is changed it is turned into a normal buffer, the attributes 38 * in tl_scrollback are no longer used. 39 */ 40 41 #include "vim.h" 42 43 #if defined(FEAT_TERMINAL) || defined(PROTO) 44 45 #ifndef MIN 46 # define MIN(x,y) ((x) < (y) ? (x) : (y)) 47 #endif 48 #ifndef MAX 49 # define MAX(x,y) ((x) > (y) ? (x) : (y)) 50 #endif 51 52 #include "libvterm/include/vterm.h" 53 54 // This is VTermScreenCell without the characters, thus much smaller. 55 typedef struct { 56 VTermScreenCellAttrs attrs; 57 char width; 58 VTermColor fg; 59 VTermColor bg; 60 } cellattr_T; 61 62 typedef struct sb_line_S { 63 int sb_cols; // can differ per line 64 cellattr_T *sb_cells; // allocated 65 cellattr_T sb_fill_attr; // for short line 66 char_u *sb_text; // for tl_scrollback_postponed 67 } sb_line_T; 68 69 #ifdef MSWIN 70 # ifndef HPCON 71 # define HPCON VOID* 72 # endif 73 # ifndef EXTENDED_STARTUPINFO_PRESENT 74 # define EXTENDED_STARTUPINFO_PRESENT 0x00080000 75 # endif 76 # ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 77 # define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 78 # endif 79 typedef struct _DYN_STARTUPINFOEXW 80 { 81 STARTUPINFOW StartupInfo; 82 LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; 83 } DYN_STARTUPINFOEXW, *PDYN_STARTUPINFOEXW; 84 #endif 85 86 // typedef term_T in structs.h 87 struct terminal_S { 88 term_T *tl_next; 89 90 VTerm *tl_vterm; 91 job_T *tl_job; 92 buf_T *tl_buffer; 93 #if defined(FEAT_GUI) 94 int tl_system; // when non-zero used for :!cmd output 95 int tl_toprow; // row with first line of system terminal 96 #endif 97 98 // Set when setting the size of a vterm, reset after redrawing. 99 int tl_vterm_size_changed; 100 101 int tl_normal_mode; // TRUE: Terminal-Normal mode 102 int tl_channel_closed; 103 int tl_channel_recently_closed; // still need to handle tl_finish 104 105 int tl_finish; 106 #define TL_FINISH_UNSET NUL 107 #define TL_FINISH_CLOSE 'c' // ++close or :terminal without argument 108 #define TL_FINISH_NOCLOSE 'n' // ++noclose 109 #define TL_FINISH_OPEN 'o' // ++open 110 char_u *tl_opencmd; 111 char_u *tl_eof_chars; 112 char_u *tl_api; // prefix for terminal API function 113 114 char_u *tl_arg0_cmd; // To format the status bar 115 116 #ifdef MSWIN 117 void *tl_winpty_config; 118 void *tl_winpty; 119 120 HPCON tl_conpty; 121 DYN_STARTUPINFOEXW tl_siex; // Structure that always needs to be hold 122 123 FILE *tl_out_fd; 124 #endif 125 #if defined(FEAT_SESSION) 126 char_u *tl_command; 127 #endif 128 char_u *tl_kill; 129 130 // last known vterm size 131 int tl_rows; 132 int tl_cols; 133 134 char_u *tl_title; // NULL or allocated 135 char_u *tl_status_text; // NULL or allocated 136 137 // Range of screen rows to update. Zero based. 138 int tl_dirty_row_start; // MAX_ROW if nothing dirty 139 int tl_dirty_row_end; // row below last one to update 140 int tl_dirty_snapshot; // text updated after making snapshot 141 #ifdef FEAT_TIMERS 142 int tl_timer_set; 143 proftime_T tl_timer_due; 144 #endif 145 int tl_postponed_scroll; // to be scrolled up 146 147 garray_T tl_scrollback; 148 int tl_scrollback_scrolled; 149 garray_T tl_scrollback_postponed; 150 151 char_u *tl_highlight_name; // replaces "Terminal"; allocated 152 153 cellattr_T tl_default_color; 154 155 linenr_T tl_top_diff_rows; // rows of top diff file or zero 156 linenr_T tl_bot_diff_rows; // rows of bottom diff file 157 158 VTermPos tl_cursor_pos; 159 int tl_cursor_visible; 160 int tl_cursor_blink; 161 int tl_cursor_shape; // 1: block, 2: underline, 3: bar 162 char_u *tl_cursor_color; // NULL or allocated 163 164 int tl_using_altscreen; 165 }; 166 167 #define TMODE_ONCE 1 // CTRL-\ CTRL-N used 168 #define TMODE_LOOP 2 // CTRL-W N used 169 170 /* 171 * List of all active terminals. 172 */ 173 static term_T *first_term = NULL; 174 175 // Terminal active in terminal_loop(). 176 static term_T *in_terminal_loop = NULL; 177 178 #ifdef MSWIN 179 static BOOL has_winpty = FALSE; 180 static BOOL has_conpty = FALSE; 181 #endif 182 183 #define MAX_ROW 999999 // used for tl_dirty_row_end to update all rows 184 #define KEY_BUF_LEN 200 185 186 #define FOR_ALL_TERMS(term) \ 187 for ((term) = first_term; (term) != NULL; (term) = (term)->tl_next) 188 189 /* 190 * Functions with separate implementation for MS-Windows and Unix-like systems. 191 */ 192 static int term_and_job_init(term_T *term, typval_T *argvar, char **argv, jobopt_T *opt, jobopt_T *orig_opt); 193 static int create_pty_only(term_T *term, jobopt_T *opt); 194 static void term_report_winsize(term_T *term, int rows, int cols); 195 static void term_free_vterm(term_T *term); 196 #ifdef FEAT_GUI 197 static void update_system_term(term_T *term); 198 #endif 199 200 static void handle_postponed_scrollback(term_T *term); 201 202 // The character that we know (or assume) that the terminal expects for the 203 // backspace key. 204 static int term_backspace_char = BS; 205 206 // "Terminal" highlight group colors. 207 static int term_default_cterm_fg = -1; 208 static int term_default_cterm_bg = -1; 209 210 // Store the last set and the desired cursor properties, so that we only update 211 // them when needed. Doing it unnecessary may result in flicker. 212 static char_u *last_set_cursor_color = NULL; 213 static char_u *desired_cursor_color = NULL; 214 static int last_set_cursor_shape = -1; 215 static int desired_cursor_shape = -1; 216 static int last_set_cursor_blink = -1; 217 static int desired_cursor_blink = -1; 218 219 220 /////////////////////////////////////// 221 // 1. Generic code for all systems. 222 223 static int 224 cursor_color_equal(char_u *lhs_color, char_u *rhs_color) 225 { 226 if (lhs_color != NULL && rhs_color != NULL) 227 return STRCMP(lhs_color, rhs_color) == 0; 228 return lhs_color == NULL && rhs_color == NULL; 229 } 230 231 static void 232 cursor_color_copy(char_u **to_color, char_u *from_color) 233 { 234 // Avoid a free & alloc if the value is already right. 235 if (cursor_color_equal(*to_color, from_color)) 236 return; 237 vim_free(*to_color); 238 *to_color = (from_color == NULL) ? NULL : vim_strsave(from_color); 239 } 240 241 static char_u * 242 cursor_color_get(char_u *color) 243 { 244 return (color == NULL) ? (char_u *)"" : color; 245 } 246 247 248 /* 249 * Parse 'termwinsize' and set "rows" and "cols" for the terminal size in the 250 * current window. 251 * Sets "rows" and/or "cols" to zero when it should follow the window size. 252 * Return TRUE if the size is the minimum size: "24*80". 253 */ 254 static int 255 parse_termwinsize(win_T *wp, int *rows, int *cols) 256 { 257 int minsize = FALSE; 258 259 *rows = 0; 260 *cols = 0; 261 262 if (*wp->w_p_tws != NUL) 263 { 264 char_u *p = vim_strchr(wp->w_p_tws, 'x'); 265 266 // Syntax of value was already checked when it's set. 267 if (p == NULL) 268 { 269 minsize = TRUE; 270 p = vim_strchr(wp->w_p_tws, '*'); 271 } 272 *rows = atoi((char *)wp->w_p_tws); 273 *cols = atoi((char *)p + 1); 274 } 275 return minsize; 276 } 277 278 /* 279 * Determine the terminal size from 'termwinsize' and the current window. 280 */ 281 static void 282 set_term_and_win_size(term_T *term) 283 { 284 #ifdef FEAT_GUI 285 if (term->tl_system) 286 { 287 // Use the whole screen for the system command. However, it will start 288 // at the command line and scroll up as needed, using tl_toprow. 289 term->tl_rows = Rows; 290 term->tl_cols = Columns; 291 return; 292 } 293 #endif 294 if (parse_termwinsize(curwin, &term->tl_rows, &term->tl_cols)) 295 { 296 if (term->tl_rows != 0) 297 term->tl_rows = MAX(term->tl_rows, curwin->w_height); 298 if (term->tl_cols != 0) 299 term->tl_cols = MAX(term->tl_cols, curwin->w_width); 300 } 301 if (term->tl_rows == 0) 302 term->tl_rows = curwin->w_height; 303 else 304 win_setheight_win(term->tl_rows, curwin); 305 if (term->tl_cols == 0) 306 term->tl_cols = curwin->w_width; 307 else 308 win_setwidth_win(term->tl_cols, curwin); 309 } 310 311 /* 312 * Initialize job options for a terminal job. 313 * Caller may overrule some of them. 314 */ 315 void 316 init_job_options(jobopt_T *opt) 317 { 318 clear_job_options(opt); 319 320 opt->jo_mode = MODE_RAW; 321 opt->jo_out_mode = MODE_RAW; 322 opt->jo_err_mode = MODE_RAW; 323 opt->jo_set = JO_MODE | JO_OUT_MODE | JO_ERR_MODE; 324 } 325 326 /* 327 * Set job options mandatory for a terminal job. 328 */ 329 static void 330 setup_job_options(jobopt_T *opt, int rows, int cols) 331 { 332 #ifndef MSWIN 333 // Win32: Redirecting the job output won't work, thus always connect stdout 334 // here. 335 if (!(opt->jo_set & JO_OUT_IO)) 336 #endif 337 { 338 // Connect stdout to the terminal. 339 opt->jo_io[PART_OUT] = JIO_BUFFER; 340 opt->jo_io_buf[PART_OUT] = curbuf->b_fnum; 341 opt->jo_modifiable[PART_OUT] = 0; 342 opt->jo_set |= JO_OUT_IO + JO_OUT_BUF + JO_OUT_MODIFIABLE; 343 } 344 345 #ifndef MSWIN 346 // Win32: Redirecting the job output won't work, thus always connect stderr 347 // here. 348 if (!(opt->jo_set & JO_ERR_IO)) 349 #endif 350 { 351 // Connect stderr to the terminal. 352 opt->jo_io[PART_ERR] = JIO_BUFFER; 353 opt->jo_io_buf[PART_ERR] = curbuf->b_fnum; 354 opt->jo_modifiable[PART_ERR] = 0; 355 opt->jo_set |= JO_ERR_IO + JO_ERR_BUF + JO_ERR_MODIFIABLE; 356 } 357 358 opt->jo_pty = TRUE; 359 if ((opt->jo_set2 & JO2_TERM_ROWS) == 0) 360 opt->jo_term_rows = rows; 361 if ((opt->jo_set2 & JO2_TERM_COLS) == 0) 362 opt->jo_term_cols = cols; 363 } 364 365 /* 366 * Flush messages on channels. 367 */ 368 static void 369 term_flush_messages() 370 { 371 mch_check_messages(); 372 parse_queued_messages(); 373 } 374 375 /* 376 * Close a terminal buffer (and its window). Used when creating the terminal 377 * fails. 378 */ 379 static void 380 term_close_buffer(buf_T *buf, buf_T *old_curbuf) 381 { 382 free_terminal(buf); 383 if (old_curbuf != NULL) 384 { 385 --curbuf->b_nwindows; 386 curbuf = old_curbuf; 387 curwin->w_buffer = curbuf; 388 ++curbuf->b_nwindows; 389 } 390 CHECK_CURBUF; 391 392 // Wiping out the buffer will also close the window and call 393 // free_terminal(). 394 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf->b_fnum, TRUE); 395 } 396 397 /* 398 * Start a terminal window and return its buffer. 399 * Use either "argvar" or "argv", the other must be NULL. 400 * When "flags" has TERM_START_NOJOB only create the buffer, b_term and open 401 * the window. 402 * Returns NULL when failed. 403 */ 404 buf_T * 405 term_start( 406 typval_T *argvar, 407 char **argv, 408 jobopt_T *opt, 409 int flags) 410 { 411 exarg_T split_ea; 412 win_T *old_curwin = curwin; 413 term_T *term; 414 buf_T *old_curbuf = NULL; 415 int res; 416 buf_T *newbuf; 417 int vertical = opt->jo_vertical || (cmdmod.split & WSP_VERT); 418 jobopt_T orig_opt; // only partly filled 419 420 if (check_restricted() || check_secure()) 421 return NULL; 422 423 if ((opt->jo_set & (JO_IN_IO + JO_OUT_IO + JO_ERR_IO)) 424 == (JO_IN_IO + JO_OUT_IO + JO_ERR_IO) 425 || (!(opt->jo_set & JO_OUT_IO) && (opt->jo_set & JO_OUT_BUF)) 426 || (!(opt->jo_set & JO_ERR_IO) && (opt->jo_set & JO_ERR_BUF)) 427 || (argvar != NULL 428 && argvar->v_type == VAR_LIST 429 && argvar->vval.v_list != NULL 430 && argvar->vval.v_list->lv_first == &range_list_item)) 431 { 432 emsg(_(e_invarg)); 433 return NULL; 434 } 435 436 term = ALLOC_CLEAR_ONE(term_T); 437 if (term == NULL) 438 return NULL; 439 term->tl_dirty_row_end = MAX_ROW; 440 term->tl_cursor_visible = TRUE; 441 term->tl_cursor_shape = VTERM_PROP_CURSORSHAPE_BLOCK; 442 term->tl_finish = opt->jo_term_finish; 443 #ifdef FEAT_GUI 444 term->tl_system = (flags & TERM_START_SYSTEM); 445 #endif 446 ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300); 447 ga_init2(&term->tl_scrollback_postponed, sizeof(sb_line_T), 300); 448 449 CLEAR_FIELD(split_ea); 450 if (opt->jo_curwin) 451 { 452 // Create a new buffer in the current window. 453 if (!can_abandon(curbuf, flags & TERM_START_FORCEIT)) 454 { 455 no_write_message(); 456 vim_free(term); 457 return NULL; 458 } 459 if (do_ecmd(0, NULL, NULL, &split_ea, ECMD_ONE, 460 ECMD_HIDE 461 + ((flags & TERM_START_FORCEIT) ? ECMD_FORCEIT : 0), 462 curwin) == FAIL) 463 { 464 vim_free(term); 465 return NULL; 466 } 467 } 468 else if (opt->jo_hidden || (flags & TERM_START_SYSTEM)) 469 { 470 buf_T *buf; 471 472 // Create a new buffer without a window. Make it the current buffer for 473 // a moment to be able to do the initializations. 474 buf = buflist_new((char_u *)"", NULL, (linenr_T)0, 475 BLN_NEW | BLN_LISTED); 476 if (buf == NULL || ml_open(buf) == FAIL) 477 { 478 vim_free(term); 479 return NULL; 480 } 481 old_curbuf = curbuf; 482 --curbuf->b_nwindows; 483 curbuf = buf; 484 curwin->w_buffer = buf; 485 ++curbuf->b_nwindows; 486 } 487 else 488 { 489 // Open a new window or tab. 490 split_ea.cmdidx = CMD_new; 491 split_ea.cmd = (char_u *)"new"; 492 split_ea.arg = (char_u *)""; 493 if (opt->jo_term_rows > 0 && !vertical) 494 { 495 split_ea.line2 = opt->jo_term_rows; 496 split_ea.addr_count = 1; 497 } 498 if (opt->jo_term_cols > 0 && vertical) 499 { 500 split_ea.line2 = opt->jo_term_cols; 501 split_ea.addr_count = 1; 502 } 503 504 if (vertical) 505 cmdmod.split |= WSP_VERT; 506 ex_splitview(&split_ea); 507 if (curwin == old_curwin) 508 { 509 // split failed 510 vim_free(term); 511 return NULL; 512 } 513 } 514 term->tl_buffer = curbuf; 515 curbuf->b_term = term; 516 517 if (!opt->jo_hidden) 518 { 519 // Only one size was taken care of with :new, do the other one. With 520 // "curwin" both need to be done. 521 if (opt->jo_term_rows > 0 && (opt->jo_curwin || vertical)) 522 win_setheight(opt->jo_term_rows); 523 if (opt->jo_term_cols > 0 && (opt->jo_curwin || !vertical)) 524 win_setwidth(opt->jo_term_cols); 525 } 526 527 // Link the new terminal in the list of active terminals. 528 term->tl_next = first_term; 529 first_term = term; 530 531 apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf); 532 533 if (opt->jo_term_name != NULL) 534 { 535 vim_free(curbuf->b_ffname); 536 curbuf->b_ffname = vim_strsave(opt->jo_term_name); 537 } 538 else if (argv != NULL) 539 { 540 vim_free(curbuf->b_ffname); 541 curbuf->b_ffname = vim_strsave((char_u *)"!system"); 542 } 543 else 544 { 545 int i; 546 size_t len; 547 char_u *cmd, *p; 548 549 if (argvar->v_type == VAR_STRING) 550 { 551 cmd = argvar->vval.v_string; 552 if (cmd == NULL) 553 cmd = (char_u *)""; 554 else if (STRCMP(cmd, "NONE") == 0) 555 cmd = (char_u *)"pty"; 556 } 557 else if (argvar->v_type != VAR_LIST 558 || argvar->vval.v_list == NULL 559 || argvar->vval.v_list->lv_len == 0 560 || (cmd = tv_get_string_chk( 561 &argvar->vval.v_list->lv_first->li_tv)) == NULL) 562 cmd = (char_u*)""; 563 564 len = STRLEN(cmd) + 10; 565 p = alloc(len); 566 567 for (i = 0; p != NULL; ++i) 568 { 569 // Prepend a ! to the command name to avoid the buffer name equals 570 // the executable, otherwise ":w!" would overwrite it. 571 if (i == 0) 572 vim_snprintf((char *)p, len, "!%s", cmd); 573 else 574 vim_snprintf((char *)p, len, "!%s (%d)", cmd, i); 575 if (buflist_findname(p) == NULL) 576 { 577 vim_free(curbuf->b_ffname); 578 curbuf->b_ffname = p; 579 break; 580 } 581 } 582 } 583 vim_free(curbuf->b_sfname); 584 curbuf->b_sfname = vim_strsave(curbuf->b_ffname); 585 curbuf->b_fname = curbuf->b_ffname; 586 587 apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf); 588 589 if (opt->jo_term_opencmd != NULL) 590 term->tl_opencmd = vim_strsave(opt->jo_term_opencmd); 591 592 if (opt->jo_eof_chars != NULL) 593 term->tl_eof_chars = vim_strsave(opt->jo_eof_chars); 594 595 set_string_option_direct((char_u *)"buftype", -1, 596 (char_u *)"terminal", OPT_FREE|OPT_LOCAL, 0); 597 // Avoid that 'buftype' is reset when this buffer is entered. 598 curbuf->b_p_initialized = TRUE; 599 600 // Mark the buffer as not modifiable. It can only be made modifiable after 601 // the job finished. 602 curbuf->b_p_ma = FALSE; 603 604 set_term_and_win_size(term); 605 #ifdef MSWIN 606 mch_memmove(orig_opt.jo_io, opt->jo_io, sizeof(orig_opt.jo_io)); 607 #endif 608 setup_job_options(opt, term->tl_rows, term->tl_cols); 609 610 if (flags & TERM_START_NOJOB) 611 return curbuf; 612 613 #if defined(FEAT_SESSION) 614 // Remember the command for the session file. 615 if (opt->jo_term_norestore || argv != NULL) 616 term->tl_command = vim_strsave((char_u *)"NONE"); 617 else if (argvar->v_type == VAR_STRING) 618 { 619 char_u *cmd = argvar->vval.v_string; 620 621 if (cmd != NULL && STRCMP(cmd, p_sh) != 0) 622 term->tl_command = vim_strsave(cmd); 623 } 624 else if (argvar->v_type == VAR_LIST 625 && argvar->vval.v_list != NULL 626 && argvar->vval.v_list->lv_len > 0) 627 { 628 garray_T ga; 629 listitem_T *item; 630 631 ga_init2(&ga, 1, 100); 632 FOR_ALL_LIST_ITEMS(argvar->vval.v_list, item) 633 { 634 char_u *s = tv_get_string_chk(&item->li_tv); 635 char_u *p; 636 637 if (s == NULL) 638 break; 639 p = vim_strsave_fnameescape(s, FALSE); 640 if (p == NULL) 641 break; 642 ga_concat(&ga, p); 643 vim_free(p); 644 ga_append(&ga, ' '); 645 } 646 if (item == NULL) 647 { 648 ga_append(&ga, NUL); 649 term->tl_command = ga.ga_data; 650 } 651 else 652 ga_clear(&ga); 653 } 654 #endif 655 656 if (opt->jo_term_kill != NULL) 657 { 658 char_u *p = skiptowhite(opt->jo_term_kill); 659 660 term->tl_kill = vim_strnsave(opt->jo_term_kill, p - opt->jo_term_kill); 661 } 662 663 if (opt->jo_term_api != NULL) 664 { 665 char_u *p = skiptowhite(opt->jo_term_api); 666 667 term->tl_api = vim_strnsave(opt->jo_term_api, p - opt->jo_term_api); 668 } 669 else 670 term->tl_api = vim_strsave((char_u *)"Tapi_"); 671 672 if (opt->jo_set2 & JO2_TERM_HIGHLIGHT) 673 term->tl_highlight_name = vim_strsave(opt->jo_term_highlight); 674 675 // System dependent: setup the vterm and maybe start the job in it. 676 if (argv == NULL 677 && argvar->v_type == VAR_STRING 678 && argvar->vval.v_string != NULL 679 && STRCMP(argvar->vval.v_string, "NONE") == 0) 680 res = create_pty_only(term, opt); 681 else 682 res = term_and_job_init(term, argvar, argv, opt, &orig_opt); 683 684 newbuf = curbuf; 685 if (res == OK) 686 { 687 // Get and remember the size we ended up with. Update the pty. 688 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols); 689 term_report_winsize(term, term->tl_rows, term->tl_cols); 690 #ifdef FEAT_GUI 691 if (term->tl_system) 692 { 693 // display first line below typed command 694 term->tl_toprow = msg_row + 1; 695 term->tl_dirty_row_end = 0; 696 } 697 #endif 698 699 // Make sure we don't get stuck on sending keys to the job, it leads to 700 // a deadlock if the job is waiting for Vim to read. 701 channel_set_nonblock(term->tl_job->jv_channel, PART_IN); 702 703 if (old_curbuf != NULL) 704 { 705 --curbuf->b_nwindows; 706 curbuf = old_curbuf; 707 curwin->w_buffer = curbuf; 708 ++curbuf->b_nwindows; 709 } 710 } 711 else 712 { 713 term_close_buffer(curbuf, old_curbuf); 714 return NULL; 715 } 716 717 apply_autocmds(EVENT_TERMINALOPEN, NULL, NULL, FALSE, newbuf); 718 if (!opt->jo_hidden && !(flags & TERM_START_SYSTEM)) 719 apply_autocmds(EVENT_TERMINALWINOPEN, NULL, NULL, FALSE, newbuf); 720 return newbuf; 721 } 722 723 /* 724 * ":terminal": open a terminal window and execute a job in it. 725 */ 726 void 727 ex_terminal(exarg_T *eap) 728 { 729 typval_T argvar[2]; 730 jobopt_T opt; 731 int opt_shell = FALSE; 732 char_u *cmd; 733 char_u *tofree = NULL; 734 735 init_job_options(&opt); 736 737 cmd = eap->arg; 738 while (*cmd == '+' && *(cmd + 1) == '+') 739 { 740 char_u *p, *ep; 741 742 cmd += 2; 743 p = skiptowhite(cmd); 744 ep = vim_strchr(cmd, '='); 745 if (ep != NULL) 746 { 747 if (ep < p) 748 p = ep; 749 else 750 ep = NULL; 751 } 752 753 # define OPTARG_HAS(name) ((int)(p - cmd) == sizeof(name) - 1 \ 754 && STRNICMP(cmd, name, sizeof(name) - 1) == 0) 755 if (OPTARG_HAS("close")) 756 opt.jo_term_finish = 'c'; 757 else if (OPTARG_HAS("noclose")) 758 opt.jo_term_finish = 'n'; 759 else if (OPTARG_HAS("open")) 760 opt.jo_term_finish = 'o'; 761 else if (OPTARG_HAS("curwin")) 762 opt.jo_curwin = 1; 763 else if (OPTARG_HAS("hidden")) 764 opt.jo_hidden = 1; 765 else if (OPTARG_HAS("norestore")) 766 opt.jo_term_norestore = 1; 767 else if (OPTARG_HAS("shell")) 768 opt_shell = TRUE; 769 else if (OPTARG_HAS("kill") && ep != NULL) 770 { 771 opt.jo_set2 |= JO2_TERM_KILL; 772 opt.jo_term_kill = ep + 1; 773 p = skiptowhite(cmd); 774 } 775 else if (OPTARG_HAS("api")) 776 { 777 opt.jo_set2 |= JO2_TERM_API; 778 if (ep != NULL) 779 { 780 opt.jo_term_api = ep + 1; 781 p = skiptowhite(cmd); 782 } 783 else 784 opt.jo_term_api = NULL; 785 } 786 else if (OPTARG_HAS("rows") && ep != NULL && isdigit(ep[1])) 787 { 788 opt.jo_set2 |= JO2_TERM_ROWS; 789 opt.jo_term_rows = atoi((char *)ep + 1); 790 p = skiptowhite(cmd); 791 } 792 else if (OPTARG_HAS("cols") && ep != NULL && isdigit(ep[1])) 793 { 794 opt.jo_set2 |= JO2_TERM_COLS; 795 opt.jo_term_cols = atoi((char *)ep + 1); 796 p = skiptowhite(cmd); 797 } 798 else if (OPTARG_HAS("eof") && ep != NULL) 799 { 800 char_u *buf = NULL; 801 char_u *keys; 802 803 vim_free(opt.jo_eof_chars); 804 p = skiptowhite(cmd); 805 *p = NUL; 806 keys = replace_termcodes(ep + 1, &buf, 807 REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL); 808 opt.jo_set2 |= JO2_EOF_CHARS; 809 opt.jo_eof_chars = vim_strsave(keys); 810 vim_free(buf); 811 *p = ' '; 812 } 813 #ifdef MSWIN 814 else if ((int)(p - cmd) == 4 && STRNICMP(cmd, "type", 4) == 0 815 && ep != NULL) 816 { 817 int tty_type = NUL; 818 819 p = skiptowhite(cmd); 820 if (STRNICMP(ep + 1, "winpty", p - (ep + 1)) == 0) 821 tty_type = 'w'; 822 else if (STRNICMP(ep + 1, "conpty", p - (ep + 1)) == 0) 823 tty_type = 'c'; 824 else 825 { 826 semsg(e_invargval, "type"); 827 goto theend; 828 } 829 opt.jo_set2 |= JO2_TTY_TYPE; 830 opt.jo_tty_type = tty_type; 831 } 832 #endif 833 else 834 { 835 if (*p) 836 *p = NUL; 837 semsg(_("E181: Invalid attribute: %s"), cmd); 838 goto theend; 839 } 840 # undef OPTARG_HAS 841 cmd = skipwhite(p); 842 } 843 if (*cmd == NUL) 844 { 845 // Make a copy of 'shell', an autocommand may change the option. 846 tofree = cmd = vim_strsave(p_sh); 847 848 // default to close when the shell exits 849 if (opt.jo_term_finish == NUL) 850 opt.jo_term_finish = TL_FINISH_CLOSE; 851 } 852 853 if (eap->addr_count > 0) 854 { 855 // Write lines from current buffer to the job. 856 opt.jo_set |= JO_IN_IO | JO_IN_BUF | JO_IN_TOP | JO_IN_BOT; 857 opt.jo_io[PART_IN] = JIO_BUFFER; 858 opt.jo_io_buf[PART_IN] = curbuf->b_fnum; 859 opt.jo_in_top = eap->line1; 860 opt.jo_in_bot = eap->line2; 861 } 862 863 if (opt_shell && tofree == NULL) 864 { 865 #ifdef UNIX 866 char **argv = NULL; 867 char_u *tofree1 = NULL; 868 char_u *tofree2 = NULL; 869 870 // :term ++shell command 871 if (unix_build_argv(cmd, &argv, &tofree1, &tofree2) == OK) 872 term_start(NULL, argv, &opt, eap->forceit ? TERM_START_FORCEIT : 0); 873 vim_free(argv); 874 vim_free(tofree1); 875 vim_free(tofree2); 876 goto theend; 877 #else 878 # ifdef MSWIN 879 long_u cmdlen = STRLEN(p_sh) + STRLEN(p_shcf) + STRLEN(cmd) + 10; 880 char_u *newcmd; 881 882 newcmd = alloc(cmdlen); 883 if (newcmd == NULL) 884 goto theend; 885 tofree = newcmd; 886 vim_snprintf((char *)newcmd, cmdlen, "%s %s %s", p_sh, p_shcf, cmd); 887 cmd = newcmd; 888 # else 889 emsg(_("E279: Sorry, ++shell is not supported on this system")); 890 goto theend; 891 # endif 892 #endif 893 } 894 argvar[0].v_type = VAR_STRING; 895 argvar[0].vval.v_string = cmd; 896 argvar[1].v_type = VAR_UNKNOWN; 897 term_start(argvar, NULL, &opt, eap->forceit ? TERM_START_FORCEIT : 0); 898 899 theend: 900 vim_free(tofree); 901 vim_free(opt.jo_eof_chars); 902 } 903 904 #if defined(FEAT_SESSION) || defined(PROTO) 905 /* 906 * Write a :terminal command to the session file to restore the terminal in 907 * window "wp". 908 * Return FAIL if writing fails. 909 */ 910 int 911 term_write_session(FILE *fd, win_T *wp) 912 { 913 term_T *term = wp->w_buffer->b_term; 914 915 // Create the terminal and run the command. This is not without 916 // risk, but let's assume the user only creates a session when this 917 // will be OK. 918 if (fprintf(fd, "terminal ++curwin ++cols=%d ++rows=%d ", 919 term->tl_cols, term->tl_rows) < 0) 920 return FAIL; 921 #ifdef MSWIN 922 if (fprintf(fd, "++type=%s ", term->tl_job->jv_tty_type) < 0) 923 return FAIL; 924 #endif 925 if (term->tl_command != NULL && fputs((char *)term->tl_command, fd) < 0) 926 return FAIL; 927 928 return put_eol(fd); 929 } 930 931 /* 932 * Return TRUE if "buf" has a terminal that should be restored. 933 */ 934 int 935 term_should_restore(buf_T *buf) 936 { 937 term_T *term = buf->b_term; 938 939 return term != NULL && (term->tl_command == NULL 940 || STRCMP(term->tl_command, "NONE") != 0); 941 } 942 #endif 943 944 /* 945 * Free the scrollback buffer for "term". 946 */ 947 static void 948 free_scrollback(term_T *term) 949 { 950 int i; 951 952 for (i = 0; i < term->tl_scrollback.ga_len; ++i) 953 vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i)->sb_cells); 954 ga_clear(&term->tl_scrollback); 955 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i) 956 vim_free(((sb_line_T *)term->tl_scrollback_postponed.ga_data + i)->sb_cells); 957 ga_clear(&term->tl_scrollback_postponed); 958 } 959 960 961 // Terminals that need to be freed soon. 962 static term_T *terminals_to_free = NULL; 963 964 /* 965 * Free a terminal and everything it refers to. 966 * Kills the job if there is one. 967 * Called when wiping out a buffer. 968 * The actual terminal structure is freed later in free_unused_terminals(), 969 * because callbacks may wipe out a buffer while the terminal is still 970 * referenced. 971 */ 972 void 973 free_terminal(buf_T *buf) 974 { 975 term_T *term = buf->b_term; 976 term_T *tp; 977 978 if (term == NULL) 979 return; 980 981 // Unlink the terminal form the list of terminals. 982 if (first_term == term) 983 first_term = term->tl_next; 984 else 985 for (tp = first_term; tp->tl_next != NULL; tp = tp->tl_next) 986 if (tp->tl_next == term) 987 { 988 tp->tl_next = term->tl_next; 989 break; 990 } 991 992 if (term->tl_job != NULL) 993 { 994 if (term->tl_job->jv_status != JOB_ENDED 995 && term->tl_job->jv_status != JOB_FINISHED 996 && term->tl_job->jv_status != JOB_FAILED) 997 job_stop(term->tl_job, NULL, "kill"); 998 job_unref(term->tl_job); 999 } 1000 term->tl_next = terminals_to_free; 1001 terminals_to_free = term; 1002 1003 buf->b_term = NULL; 1004 if (in_terminal_loop == term) 1005 in_terminal_loop = NULL; 1006 } 1007 1008 void 1009 free_unused_terminals() 1010 { 1011 while (terminals_to_free != NULL) 1012 { 1013 term_T *term = terminals_to_free; 1014 1015 terminals_to_free = term->tl_next; 1016 1017 free_scrollback(term); 1018 1019 term_free_vterm(term); 1020 vim_free(term->tl_api); 1021 vim_free(term->tl_title); 1022 #ifdef FEAT_SESSION 1023 vim_free(term->tl_command); 1024 #endif 1025 vim_free(term->tl_kill); 1026 vim_free(term->tl_status_text); 1027 vim_free(term->tl_opencmd); 1028 vim_free(term->tl_eof_chars); 1029 vim_free(term->tl_arg0_cmd); 1030 #ifdef MSWIN 1031 if (term->tl_out_fd != NULL) 1032 fclose(term->tl_out_fd); 1033 #endif 1034 vim_free(term->tl_highlight_name); 1035 vim_free(term->tl_cursor_color); 1036 vim_free(term); 1037 } 1038 } 1039 1040 /* 1041 * Get the part that is connected to the tty. Normally this is PART_IN, but 1042 * when writing buffer lines to the job it can be another. This makes it 1043 * possible to do "1,5term vim -". 1044 */ 1045 static ch_part_T 1046 get_tty_part(term_T *term UNUSED) 1047 { 1048 #ifdef UNIX 1049 ch_part_T parts[3] = {PART_IN, PART_OUT, PART_ERR}; 1050 int i; 1051 1052 for (i = 0; i < 3; ++i) 1053 { 1054 int fd = term->tl_job->jv_channel->ch_part[parts[i]].ch_fd; 1055 1056 if (mch_isatty(fd)) 1057 return parts[i]; 1058 } 1059 #endif 1060 return PART_IN; 1061 } 1062 1063 /* 1064 * Write job output "msg[len]" to the vterm. 1065 */ 1066 static void 1067 term_write_job_output(term_T *term, char_u *msg, size_t len) 1068 { 1069 VTerm *vterm = term->tl_vterm; 1070 size_t prevlen = vterm_output_get_buffer_current(vterm); 1071 1072 vterm_input_write(vterm, (char *)msg, len); 1073 1074 // flush vterm buffer when vterm responded to control sequence 1075 if (prevlen != vterm_output_get_buffer_current(vterm)) 1076 { 1077 char buf[KEY_BUF_LEN]; 1078 size_t curlen = vterm_output_read(vterm, buf, KEY_BUF_LEN); 1079 1080 if (curlen > 0) 1081 channel_send(term->tl_job->jv_channel, get_tty_part(term), 1082 (char_u *)buf, (int)curlen, NULL); 1083 } 1084 1085 // this invokes the damage callbacks 1086 vterm_screen_flush_damage(vterm_obtain_screen(vterm)); 1087 } 1088 1089 static void 1090 update_cursor(term_T *term, int redraw) 1091 { 1092 if (term->tl_normal_mode) 1093 return; 1094 #ifdef FEAT_GUI 1095 if (term->tl_system) 1096 windgoto(term->tl_cursor_pos.row + term->tl_toprow, 1097 term->tl_cursor_pos.col); 1098 else 1099 #endif 1100 setcursor(); 1101 if (redraw) 1102 { 1103 if (term->tl_buffer == curbuf && term->tl_cursor_visible) 1104 cursor_on(); 1105 out_flush(); 1106 #ifdef FEAT_GUI 1107 if (gui.in_use) 1108 { 1109 gui_update_cursor(FALSE, FALSE); 1110 gui_mch_flush(); 1111 } 1112 #endif 1113 } 1114 } 1115 1116 /* 1117 * Invoked when "msg" output from a job was received. Write it to the terminal 1118 * of "buffer". 1119 */ 1120 void 1121 write_to_term(buf_T *buffer, char_u *msg, channel_T *channel) 1122 { 1123 size_t len = STRLEN(msg); 1124 term_T *term = buffer->b_term; 1125 1126 #ifdef MSWIN 1127 // Win32: Cannot redirect output of the job, intercept it here and write to 1128 // the file. 1129 if (term->tl_out_fd != NULL) 1130 { 1131 ch_log(channel, "Writing %d bytes to output file", (int)len); 1132 fwrite(msg, len, 1, term->tl_out_fd); 1133 return; 1134 } 1135 #endif 1136 1137 if (term->tl_vterm == NULL) 1138 { 1139 ch_log(channel, "NOT writing %d bytes to terminal", (int)len); 1140 return; 1141 } 1142 ch_log(channel, "writing %d bytes to terminal", (int)len); 1143 term_write_job_output(term, msg, len); 1144 1145 #ifdef FEAT_GUI 1146 if (term->tl_system) 1147 { 1148 // show system output, scrolling up the screen as needed 1149 update_system_term(term); 1150 update_cursor(term, TRUE); 1151 } 1152 else 1153 #endif 1154 // In Terminal-Normal mode we are displaying the buffer, not the terminal 1155 // contents, thus no screen update is needed. 1156 if (!term->tl_normal_mode) 1157 { 1158 // Don't use update_screen() when editing the command line, it gets 1159 // cleared. 1160 // TODO: only update once in a while. 1161 ch_log(term->tl_job->jv_channel, "updating screen"); 1162 if (buffer == curbuf && (State & CMDLINE) == 0) 1163 { 1164 update_screen(VALID_NO_UPDATE); 1165 // update_screen() can be slow, check the terminal wasn't closed 1166 // already 1167 if (buffer == curbuf && curbuf->b_term != NULL) 1168 update_cursor(curbuf->b_term, TRUE); 1169 } 1170 else 1171 redraw_after_callback(TRUE); 1172 } 1173 } 1174 1175 /* 1176 * Send a mouse position and click to the vterm 1177 */ 1178 static int 1179 term_send_mouse(VTerm *vterm, int button, int pressed) 1180 { 1181 VTermModifier mod = VTERM_MOD_NONE; 1182 int row = mouse_row - W_WINROW(curwin); 1183 int col = mouse_col - curwin->w_wincol; 1184 1185 #ifdef FEAT_PROP_POPUP 1186 if (popup_is_popup(curwin)) 1187 { 1188 row -= popup_top_extra(curwin); 1189 col -= popup_left_extra(curwin); 1190 } 1191 #endif 1192 vterm_mouse_move(vterm, row, col, mod); 1193 if (button != 0) 1194 vterm_mouse_button(vterm, button, pressed, mod); 1195 return TRUE; 1196 } 1197 1198 static int enter_mouse_col = -1; 1199 static int enter_mouse_row = -1; 1200 1201 /* 1202 * Handle a mouse click, drag or release. 1203 * Return TRUE when a mouse event is sent to the terminal. 1204 */ 1205 static int 1206 term_mouse_click(VTerm *vterm, int key) 1207 { 1208 #if defined(FEAT_CLIPBOARD) 1209 // For modeless selection mouse drag and release events are ignored, unless 1210 // they are preceded with a mouse down event 1211 static int ignore_drag_release = TRUE; 1212 VTermMouseState mouse_state; 1213 1214 vterm_state_get_mousestate(vterm_obtain_state(vterm), &mouse_state); 1215 if (mouse_state.flags == 0) 1216 { 1217 // Terminal is not using the mouse, use modeless selection. 1218 switch (key) 1219 { 1220 case K_LEFTDRAG: 1221 case K_LEFTRELEASE: 1222 case K_RIGHTDRAG: 1223 case K_RIGHTRELEASE: 1224 // Ignore drag and release events when the button-down wasn't 1225 // seen before. 1226 if (ignore_drag_release) 1227 { 1228 int save_mouse_col, save_mouse_row; 1229 1230 if (enter_mouse_col < 0) 1231 break; 1232 1233 // mouse click in the window gave us focus, handle that 1234 // click now 1235 save_mouse_col = mouse_col; 1236 save_mouse_row = mouse_row; 1237 mouse_col = enter_mouse_col; 1238 mouse_row = enter_mouse_row; 1239 clip_modeless(MOUSE_LEFT, TRUE, FALSE); 1240 mouse_col = save_mouse_col; 1241 mouse_row = save_mouse_row; 1242 } 1243 // FALLTHROUGH 1244 case K_LEFTMOUSE: 1245 case K_RIGHTMOUSE: 1246 if (key == K_LEFTRELEASE || key == K_RIGHTRELEASE) 1247 ignore_drag_release = TRUE; 1248 else 1249 ignore_drag_release = FALSE; 1250 // Should we call mouse_has() here? 1251 if (clip_star.available) 1252 { 1253 int button, is_click, is_drag; 1254 1255 button = get_mouse_button(KEY2TERMCAP1(key), 1256 &is_click, &is_drag); 1257 if (mouse_model_popup() && button == MOUSE_LEFT 1258 && (mod_mask & MOD_MASK_SHIFT)) 1259 { 1260 // Translate shift-left to right button. 1261 button = MOUSE_RIGHT; 1262 mod_mask &= ~MOD_MASK_SHIFT; 1263 } 1264 clip_modeless(button, is_click, is_drag); 1265 } 1266 break; 1267 1268 case K_MIDDLEMOUSE: 1269 if (clip_star.available) 1270 insert_reg('*', TRUE); 1271 break; 1272 } 1273 enter_mouse_col = -1; 1274 return FALSE; 1275 } 1276 #endif 1277 enter_mouse_col = -1; 1278 1279 switch (key) 1280 { 1281 case K_LEFTMOUSE: 1282 case K_LEFTMOUSE_NM: term_send_mouse(vterm, 1, 1); break; 1283 case K_LEFTDRAG: term_send_mouse(vterm, 1, 1); break; 1284 case K_LEFTRELEASE: 1285 case K_LEFTRELEASE_NM: term_send_mouse(vterm, 1, 0); break; 1286 case K_MOUSEMOVE: term_send_mouse(vterm, 0, 0); break; 1287 case K_MIDDLEMOUSE: term_send_mouse(vterm, 2, 1); break; 1288 case K_MIDDLEDRAG: term_send_mouse(vterm, 2, 1); break; 1289 case K_MIDDLERELEASE: term_send_mouse(vterm, 2, 0); break; 1290 case K_RIGHTMOUSE: term_send_mouse(vterm, 3, 1); break; 1291 case K_RIGHTDRAG: term_send_mouse(vterm, 3, 1); break; 1292 case K_RIGHTRELEASE: term_send_mouse(vterm, 3, 0); break; 1293 } 1294 return TRUE; 1295 } 1296 1297 /* 1298 * Convert typed key "c" with modifiers "modmask" into bytes to send to the 1299 * job. 1300 * Return the number of bytes in "buf". 1301 */ 1302 static int 1303 term_convert_key(term_T *term, int c, int modmask, char *buf) 1304 { 1305 VTerm *vterm = term->tl_vterm; 1306 VTermKey key = VTERM_KEY_NONE; 1307 VTermModifier mod = VTERM_MOD_NONE; 1308 int other = FALSE; 1309 1310 switch (c) 1311 { 1312 // don't use VTERM_KEY_ENTER, it may do an unwanted conversion 1313 1314 // don't use VTERM_KEY_BACKSPACE, it always 1315 // becomes 0x7f DEL 1316 case K_BS: c = term_backspace_char; break; 1317 1318 case ESC: key = VTERM_KEY_ESCAPE; break; 1319 case K_DEL: key = VTERM_KEY_DEL; break; 1320 case K_DOWN: key = VTERM_KEY_DOWN; break; 1321 case K_S_DOWN: mod = VTERM_MOD_SHIFT; 1322 key = VTERM_KEY_DOWN; break; 1323 case K_END: key = VTERM_KEY_END; break; 1324 case K_S_END: mod = VTERM_MOD_SHIFT; 1325 key = VTERM_KEY_END; break; 1326 case K_C_END: mod = VTERM_MOD_CTRL; 1327 key = VTERM_KEY_END; break; 1328 case K_F10: key = VTERM_KEY_FUNCTION(10); break; 1329 case K_F11: key = VTERM_KEY_FUNCTION(11); break; 1330 case K_F12: key = VTERM_KEY_FUNCTION(12); break; 1331 case K_F1: key = VTERM_KEY_FUNCTION(1); break; 1332 case K_F2: key = VTERM_KEY_FUNCTION(2); break; 1333 case K_F3: key = VTERM_KEY_FUNCTION(3); break; 1334 case K_F4: key = VTERM_KEY_FUNCTION(4); break; 1335 case K_F5: key = VTERM_KEY_FUNCTION(5); break; 1336 case K_F6: key = VTERM_KEY_FUNCTION(6); break; 1337 case K_F7: key = VTERM_KEY_FUNCTION(7); break; 1338 case K_F8: key = VTERM_KEY_FUNCTION(8); break; 1339 case K_F9: key = VTERM_KEY_FUNCTION(9); break; 1340 case K_HOME: key = VTERM_KEY_HOME; break; 1341 case K_S_HOME: mod = VTERM_MOD_SHIFT; 1342 key = VTERM_KEY_HOME; break; 1343 case K_C_HOME: mod = VTERM_MOD_CTRL; 1344 key = VTERM_KEY_HOME; break; 1345 case K_INS: key = VTERM_KEY_INS; break; 1346 case K_K0: key = VTERM_KEY_KP_0; break; 1347 case K_K1: key = VTERM_KEY_KP_1; break; 1348 case K_K2: key = VTERM_KEY_KP_2; break; 1349 case K_K3: key = VTERM_KEY_KP_3; break; 1350 case K_K4: key = VTERM_KEY_KP_4; break; 1351 case K_K5: key = VTERM_KEY_KP_5; break; 1352 case K_K6: key = VTERM_KEY_KP_6; break; 1353 case K_K7: key = VTERM_KEY_KP_7; break; 1354 case K_K8: key = VTERM_KEY_KP_8; break; 1355 case K_K9: key = VTERM_KEY_KP_9; break; 1356 case K_KDEL: key = VTERM_KEY_DEL; break; // TODO 1357 case K_KDIVIDE: key = VTERM_KEY_KP_DIVIDE; break; 1358 case K_KEND: key = VTERM_KEY_KP_1; break; // TODO 1359 case K_KENTER: key = VTERM_KEY_KP_ENTER; break; 1360 case K_KHOME: key = VTERM_KEY_KP_7; break; // TODO 1361 case K_KINS: key = VTERM_KEY_KP_0; break; // TODO 1362 case K_KMINUS: key = VTERM_KEY_KP_MINUS; break; 1363 case K_KMULTIPLY: key = VTERM_KEY_KP_MULT; break; 1364 case K_KPAGEDOWN: key = VTERM_KEY_KP_3; break; // TODO 1365 case K_KPAGEUP: key = VTERM_KEY_KP_9; break; // TODO 1366 case K_KPLUS: key = VTERM_KEY_KP_PLUS; break; 1367 case K_KPOINT: key = VTERM_KEY_KP_PERIOD; break; 1368 case K_LEFT: key = VTERM_KEY_LEFT; break; 1369 case K_S_LEFT: mod = VTERM_MOD_SHIFT; 1370 key = VTERM_KEY_LEFT; break; 1371 case K_C_LEFT: mod = VTERM_MOD_CTRL; 1372 key = VTERM_KEY_LEFT; break; 1373 case K_PAGEDOWN: key = VTERM_KEY_PAGEDOWN; break; 1374 case K_PAGEUP: key = VTERM_KEY_PAGEUP; break; 1375 case K_RIGHT: key = VTERM_KEY_RIGHT; break; 1376 case K_S_RIGHT: mod = VTERM_MOD_SHIFT; 1377 key = VTERM_KEY_RIGHT; break; 1378 case K_C_RIGHT: mod = VTERM_MOD_CTRL; 1379 key = VTERM_KEY_RIGHT; break; 1380 case K_UP: key = VTERM_KEY_UP; break; 1381 case K_S_UP: mod = VTERM_MOD_SHIFT; 1382 key = VTERM_KEY_UP; break; 1383 case TAB: key = VTERM_KEY_TAB; break; 1384 case K_S_TAB: mod = VTERM_MOD_SHIFT; 1385 key = VTERM_KEY_TAB; break; 1386 1387 case K_MOUSEUP: other = term_send_mouse(vterm, 5, 1); break; 1388 case K_MOUSEDOWN: other = term_send_mouse(vterm, 4, 1); break; 1389 case K_MOUSELEFT: /* TODO */ return 0; 1390 case K_MOUSERIGHT: /* TODO */ return 0; 1391 1392 case K_LEFTMOUSE: 1393 case K_LEFTMOUSE_NM: 1394 case K_LEFTDRAG: 1395 case K_LEFTRELEASE: 1396 case K_LEFTRELEASE_NM: 1397 case K_MOUSEMOVE: 1398 case K_MIDDLEMOUSE: 1399 case K_MIDDLEDRAG: 1400 case K_MIDDLERELEASE: 1401 case K_RIGHTMOUSE: 1402 case K_RIGHTDRAG: 1403 case K_RIGHTRELEASE: if (!term_mouse_click(vterm, c)) 1404 return 0; 1405 other = TRUE; 1406 break; 1407 1408 case K_X1MOUSE: /* TODO */ return 0; 1409 case K_X1DRAG: /* TODO */ return 0; 1410 case K_X1RELEASE: /* TODO */ return 0; 1411 case K_X2MOUSE: /* TODO */ return 0; 1412 case K_X2DRAG: /* TODO */ return 0; 1413 case K_X2RELEASE: /* TODO */ return 0; 1414 1415 case K_IGNORE: return 0; 1416 case K_NOP: return 0; 1417 case K_UNDO: return 0; 1418 case K_HELP: return 0; 1419 case K_XF1: key = VTERM_KEY_FUNCTION(1); break; 1420 case K_XF2: key = VTERM_KEY_FUNCTION(2); break; 1421 case K_XF3: key = VTERM_KEY_FUNCTION(3); break; 1422 case K_XF4: key = VTERM_KEY_FUNCTION(4); break; 1423 case K_SELECT: return 0; 1424 #ifdef FEAT_GUI 1425 case K_VER_SCROLLBAR: return 0; 1426 case K_HOR_SCROLLBAR: return 0; 1427 #endif 1428 #ifdef FEAT_GUI_TABLINE 1429 case K_TABLINE: return 0; 1430 case K_TABMENU: return 0; 1431 #endif 1432 #ifdef FEAT_NETBEANS_INTG 1433 case K_F21: key = VTERM_KEY_FUNCTION(21); break; 1434 #endif 1435 #ifdef FEAT_DND 1436 case K_DROP: return 0; 1437 #endif 1438 case K_CURSORHOLD: return 0; 1439 case K_PS: vterm_keyboard_start_paste(vterm); 1440 other = TRUE; 1441 break; 1442 case K_PE: vterm_keyboard_end_paste(vterm); 1443 other = TRUE; 1444 break; 1445 } 1446 1447 // add modifiers for the typed key 1448 if (modmask & MOD_MASK_SHIFT) 1449 mod |= VTERM_MOD_SHIFT; 1450 if (modmask & MOD_MASK_CTRL) 1451 mod |= VTERM_MOD_CTRL; 1452 if (modmask & (MOD_MASK_ALT | MOD_MASK_META)) 1453 mod |= VTERM_MOD_ALT; 1454 1455 /* 1456 * Convert special keys to vterm keys: 1457 * - Write keys to vterm: vterm_keyboard_key() 1458 * - Write output to channel. 1459 */ 1460 if (key != VTERM_KEY_NONE) 1461 // Special key, let vterm convert it. 1462 vterm_keyboard_key(vterm, key, mod); 1463 else if (!other) 1464 // Normal character, let vterm convert it. 1465 vterm_keyboard_unichar(vterm, c, mod); 1466 1467 // Read back the converted escape sequence. 1468 return (int)vterm_output_read(vterm, buf, KEY_BUF_LEN); 1469 } 1470 1471 /* 1472 * Return TRUE if the job for "term" is still running. 1473 * If "check_job_status" is TRUE update the job status. 1474 * NOTE: "term" may be freed by callbacks. 1475 */ 1476 static int 1477 term_job_running_check(term_T *term, int check_job_status) 1478 { 1479 // Also consider the job finished when the channel is closed, to avoid a 1480 // race condition when updating the title. 1481 if (term != NULL 1482 && term->tl_job != NULL 1483 && channel_is_open(term->tl_job->jv_channel)) 1484 { 1485 job_T *job = term->tl_job; 1486 1487 // Careful: Checking the job status may invoked callbacks, which close 1488 // the buffer and terminate "term". However, "job" will not be freed 1489 // yet. 1490 if (check_job_status) 1491 job_status(job); 1492 return (job->jv_status == JOB_STARTED 1493 || (job->jv_channel != NULL && job->jv_channel->ch_keep_open)); 1494 } 1495 return FALSE; 1496 } 1497 1498 /* 1499 * Return TRUE if the job for "term" is still running. 1500 */ 1501 int 1502 term_job_running(term_T *term) 1503 { 1504 return term_job_running_check(term, FALSE); 1505 } 1506 1507 /* 1508 * Return TRUE if "term" has an active channel and used ":term NONE". 1509 */ 1510 int 1511 term_none_open(term_T *term) 1512 { 1513 // Also consider the job finished when the channel is closed, to avoid a 1514 // race condition when updating the title. 1515 return term != NULL 1516 && term->tl_job != NULL 1517 && channel_is_open(term->tl_job->jv_channel) 1518 && term->tl_job->jv_channel->ch_keep_open; 1519 } 1520 1521 /* 1522 * Used when exiting: kill the job in "buf" if so desired. 1523 * Return OK when the job finished. 1524 * Return FAIL when the job is still running. 1525 */ 1526 int 1527 term_try_stop_job(buf_T *buf) 1528 { 1529 int count; 1530 char *how = (char *)buf->b_term->tl_kill; 1531 1532 #if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) 1533 if ((how == NULL || *how == NUL) && (p_confirm || cmdmod.confirm)) 1534 { 1535 char_u buff[DIALOG_MSG_SIZE]; 1536 int ret; 1537 1538 dialog_msg(buff, _("Kill job in \"%s\"?"), buf->b_fname); 1539 ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1); 1540 if (ret == VIM_YES) 1541 how = "kill"; 1542 else if (ret == VIM_CANCEL) 1543 return FAIL; 1544 } 1545 #endif 1546 if (how == NULL || *how == NUL) 1547 return FAIL; 1548 1549 job_stop(buf->b_term->tl_job, NULL, how); 1550 1551 // wait for up to a second for the job to die 1552 for (count = 0; count < 100; ++count) 1553 { 1554 job_T *job; 1555 1556 // buffer, terminal and job may be cleaned up while waiting 1557 if (!buf_valid(buf) 1558 || buf->b_term == NULL 1559 || buf->b_term->tl_job == NULL) 1560 return OK; 1561 job = buf->b_term->tl_job; 1562 1563 // Call job_status() to update jv_status. It may cause the job to be 1564 // cleaned up but it won't be freed. 1565 job_status(job); 1566 if (job->jv_status >= JOB_ENDED) 1567 return OK; 1568 1569 ui_delay(10L, TRUE); 1570 term_flush_messages(); 1571 } 1572 return FAIL; 1573 } 1574 1575 /* 1576 * Add the last line of the scrollback buffer to the buffer in the window. 1577 */ 1578 static void 1579 add_scrollback_line_to_buffer(term_T *term, char_u *text, int len) 1580 { 1581 buf_T *buf = term->tl_buffer; 1582 int empty = (buf->b_ml.ml_flags & ML_EMPTY); 1583 linenr_T lnum = buf->b_ml.ml_line_count; 1584 1585 #ifdef MSWIN 1586 if (!enc_utf8 && enc_codepage > 0) 1587 { 1588 WCHAR *ret = NULL; 1589 int length = 0; 1590 1591 MultiByteToWideChar_alloc(CP_UTF8, 0, (char*)text, len + 1, 1592 &ret, &length); 1593 if (ret != NULL) 1594 { 1595 WideCharToMultiByte_alloc(enc_codepage, 0, 1596 ret, length, (char **)&text, &len, 0, 0); 1597 vim_free(ret); 1598 ml_append_buf(term->tl_buffer, lnum, text, len, FALSE); 1599 vim_free(text); 1600 } 1601 } 1602 else 1603 #endif 1604 ml_append_buf(term->tl_buffer, lnum, text, len + 1, FALSE); 1605 if (empty) 1606 { 1607 // Delete the empty line that was in the empty buffer. 1608 curbuf = buf; 1609 ml_delete(1, FALSE); 1610 curbuf = curwin->w_buffer; 1611 } 1612 } 1613 1614 static void 1615 cell2cellattr(const VTermScreenCell *cell, cellattr_T *attr) 1616 { 1617 attr->width = cell->width; 1618 attr->attrs = cell->attrs; 1619 attr->fg = cell->fg; 1620 attr->bg = cell->bg; 1621 } 1622 1623 static int 1624 equal_celattr(cellattr_T *a, cellattr_T *b) 1625 { 1626 // Comparing the colors should be sufficient. 1627 return a->fg.red == b->fg.red 1628 && a->fg.green == b->fg.green 1629 && a->fg.blue == b->fg.blue 1630 && a->bg.red == b->bg.red 1631 && a->bg.green == b->bg.green 1632 && a->bg.blue == b->bg.blue; 1633 } 1634 1635 /* 1636 * Add an empty scrollback line to "term". When "lnum" is not zero, add the 1637 * line at this position. Otherwise at the end. 1638 */ 1639 static int 1640 add_empty_scrollback(term_T *term, cellattr_T *fill_attr, int lnum) 1641 { 1642 if (ga_grow(&term->tl_scrollback, 1) == OK) 1643 { 1644 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data 1645 + term->tl_scrollback.ga_len; 1646 1647 if (lnum > 0) 1648 { 1649 int i; 1650 1651 for (i = 0; i < term->tl_scrollback.ga_len - lnum; ++i) 1652 { 1653 *line = *(line - 1); 1654 --line; 1655 } 1656 } 1657 line->sb_cols = 0; 1658 line->sb_cells = NULL; 1659 line->sb_fill_attr = *fill_attr; 1660 ++term->tl_scrollback.ga_len; 1661 return OK; 1662 } 1663 return FALSE; 1664 } 1665 1666 /* 1667 * Remove the terminal contents from the scrollback and the buffer. 1668 * Used before adding a new scrollback line or updating the buffer for lines 1669 * displayed in the terminal. 1670 */ 1671 static void 1672 cleanup_scrollback(term_T *term) 1673 { 1674 sb_line_T *line; 1675 garray_T *gap; 1676 1677 curbuf = term->tl_buffer; 1678 gap = &term->tl_scrollback; 1679 while (curbuf->b_ml.ml_line_count > term->tl_scrollback_scrolled 1680 && gap->ga_len > 0) 1681 { 1682 ml_delete(curbuf->b_ml.ml_line_count, FALSE); 1683 line = (sb_line_T *)gap->ga_data + gap->ga_len - 1; 1684 vim_free(line->sb_cells); 1685 --gap->ga_len; 1686 } 1687 curbuf = curwin->w_buffer; 1688 if (curbuf == term->tl_buffer) 1689 check_cursor(); 1690 } 1691 1692 /* 1693 * Add the current lines of the terminal to scrollback and to the buffer. 1694 */ 1695 static void 1696 update_snapshot(term_T *term) 1697 { 1698 VTermScreen *screen; 1699 int len; 1700 int lines_skipped = 0; 1701 VTermPos pos; 1702 VTermScreenCell cell; 1703 cellattr_T fill_attr, new_fill_attr; 1704 cellattr_T *p; 1705 1706 ch_log(term->tl_job == NULL ? NULL : term->tl_job->jv_channel, 1707 "Adding terminal window snapshot to buffer"); 1708 1709 // First remove the lines that were appended before, they might be 1710 // outdated. 1711 cleanup_scrollback(term); 1712 1713 screen = vterm_obtain_screen(term->tl_vterm); 1714 fill_attr = new_fill_attr = term->tl_default_color; 1715 for (pos.row = 0; pos.row < term->tl_rows; ++pos.row) 1716 { 1717 len = 0; 1718 for (pos.col = 0; pos.col < term->tl_cols; ++pos.col) 1719 if (vterm_screen_get_cell(screen, pos, &cell) != 0 1720 && cell.chars[0] != NUL) 1721 { 1722 len = pos.col + 1; 1723 new_fill_attr = term->tl_default_color; 1724 } 1725 else 1726 // Assume the last attr is the filler attr. 1727 cell2cellattr(&cell, &new_fill_attr); 1728 1729 if (len == 0 && equal_celattr(&new_fill_attr, &fill_attr)) 1730 ++lines_skipped; 1731 else 1732 { 1733 while (lines_skipped > 0) 1734 { 1735 // Line was skipped, add an empty line. 1736 --lines_skipped; 1737 if (add_empty_scrollback(term, &fill_attr, 0) == OK) 1738 add_scrollback_line_to_buffer(term, (char_u *)"", 0); 1739 } 1740 1741 if (len == 0) 1742 p = NULL; 1743 else 1744 p = ALLOC_MULT(cellattr_T, len); 1745 if ((p != NULL || len == 0) 1746 && ga_grow(&term->tl_scrollback, 1) == OK) 1747 { 1748 garray_T ga; 1749 int width; 1750 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data 1751 + term->tl_scrollback.ga_len; 1752 1753 ga_init2(&ga, 1, 100); 1754 for (pos.col = 0; pos.col < len; pos.col += width) 1755 { 1756 if (vterm_screen_get_cell(screen, pos, &cell) == 0) 1757 { 1758 width = 1; 1759 CLEAR_POINTER(p + pos.col); 1760 if (ga_grow(&ga, 1) == OK) 1761 ga.ga_len += utf_char2bytes(' ', 1762 (char_u *)ga.ga_data + ga.ga_len); 1763 } 1764 else 1765 { 1766 width = cell.width; 1767 1768 cell2cellattr(&cell, &p[pos.col]); 1769 1770 // Each character can be up to 6 bytes. 1771 if (ga_grow(&ga, VTERM_MAX_CHARS_PER_CELL * 6) == OK) 1772 { 1773 int i; 1774 int c; 1775 1776 for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i) 1777 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c, 1778 (char_u *)ga.ga_data + ga.ga_len); 1779 } 1780 } 1781 } 1782 line->sb_cols = len; 1783 line->sb_cells = p; 1784 line->sb_fill_attr = new_fill_attr; 1785 fill_attr = new_fill_attr; 1786 ++term->tl_scrollback.ga_len; 1787 1788 if (ga_grow(&ga, 1) == FAIL) 1789 add_scrollback_line_to_buffer(term, (char_u *)"", 0); 1790 else 1791 { 1792 *((char_u *)ga.ga_data + ga.ga_len) = NUL; 1793 add_scrollback_line_to_buffer(term, ga.ga_data, ga.ga_len); 1794 } 1795 ga_clear(&ga); 1796 } 1797 else 1798 vim_free(p); 1799 } 1800 } 1801 1802 // Add trailing empty lines. 1803 for (pos.row = term->tl_scrollback.ga_len; 1804 pos.row < term->tl_scrollback_scrolled + term->tl_cursor_pos.row; 1805 ++pos.row) 1806 { 1807 if (add_empty_scrollback(term, &fill_attr, 0) == OK) 1808 add_scrollback_line_to_buffer(term, (char_u *)"", 0); 1809 } 1810 1811 term->tl_dirty_snapshot = FALSE; 1812 #ifdef FEAT_TIMERS 1813 term->tl_timer_set = FALSE; 1814 #endif 1815 } 1816 1817 /* 1818 * Loop over all windows in the current tab, and also curwin, which is not 1819 * encountered when using a terminal in a popup window. 1820 * Return TRUE if "*wp" was set to the next window. 1821 */ 1822 static int 1823 for_all_windows_and_curwin(win_T **wp, int *did_curwin) 1824 { 1825 if (*wp == NULL) 1826 *wp = firstwin; 1827 else if ((*wp)->w_next != NULL) 1828 *wp = (*wp)->w_next; 1829 else if (!*did_curwin) 1830 *wp = curwin; 1831 else 1832 return FALSE; 1833 if (*wp == curwin) 1834 *did_curwin = TRUE; 1835 return TRUE; 1836 } 1837 1838 /* 1839 * If needed, add the current lines of the terminal to scrollback and to the 1840 * buffer. Called after the job has ended and when switching to 1841 * Terminal-Normal mode. 1842 * When "redraw" is TRUE redraw the windows that show the terminal. 1843 */ 1844 static void 1845 may_move_terminal_to_buffer(term_T *term, int redraw) 1846 { 1847 if (term->tl_vterm == NULL) 1848 return; 1849 1850 // Update the snapshot only if something changes or the buffer does not 1851 // have all the lines. 1852 if (term->tl_dirty_snapshot || term->tl_buffer->b_ml.ml_line_count 1853 <= term->tl_scrollback_scrolled) 1854 update_snapshot(term); 1855 1856 // Obtain the current background color. 1857 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm), 1858 &term->tl_default_color.fg, &term->tl_default_color.bg); 1859 1860 if (redraw) 1861 { 1862 win_T *wp = NULL; 1863 int did_curwin = FALSE; 1864 1865 while (for_all_windows_and_curwin(&wp, &did_curwin)) 1866 { 1867 if (wp->w_buffer == term->tl_buffer) 1868 { 1869 wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count; 1870 wp->w_cursor.col = 0; 1871 wp->w_valid = 0; 1872 if (wp->w_cursor.lnum >= wp->w_height) 1873 { 1874 linenr_T min_topline = wp->w_cursor.lnum - wp->w_height + 1; 1875 1876 if (wp->w_topline < min_topline) 1877 wp->w_topline = min_topline; 1878 } 1879 redraw_win_later(wp, NOT_VALID); 1880 } 1881 } 1882 } 1883 } 1884 1885 #if defined(FEAT_TIMERS) || defined(PROTO) 1886 /* 1887 * Check if any terminal timer expired. If so, copy text from the terminal to 1888 * the buffer. 1889 * Return the time until the next timer will expire. 1890 */ 1891 int 1892 term_check_timers(int next_due_arg, proftime_T *now) 1893 { 1894 term_T *term; 1895 int next_due = next_due_arg; 1896 1897 FOR_ALL_TERMS(term) 1898 { 1899 if (term->tl_timer_set && !term->tl_normal_mode) 1900 { 1901 long this_due = proftime_time_left(&term->tl_timer_due, now); 1902 1903 if (this_due <= 1) 1904 { 1905 term->tl_timer_set = FALSE; 1906 may_move_terminal_to_buffer(term, FALSE); 1907 } 1908 else if (next_due == -1 || next_due > this_due) 1909 next_due = this_due; 1910 } 1911 } 1912 1913 return next_due; 1914 } 1915 #endif 1916 1917 /* 1918 * When "normal_mode" is TRUE set the terminal to Terminal-Normal mode, 1919 * otherwise end it. 1920 */ 1921 static void 1922 set_terminal_mode(term_T *term, int normal_mode) 1923 { 1924 term->tl_normal_mode = normal_mode; 1925 if (!normal_mode) 1926 handle_postponed_scrollback(term); 1927 VIM_CLEAR(term->tl_status_text); 1928 if (term->tl_buffer == curbuf) 1929 maketitle(); 1930 } 1931 1932 /* 1933 * Called after the job is finished and Terminal mode is not active: 1934 * Move the vterm contents into the scrollback buffer and free the vterm. 1935 */ 1936 static void 1937 cleanup_vterm(term_T *term) 1938 { 1939 set_terminal_mode(term, FALSE); 1940 if (term->tl_finish != TL_FINISH_CLOSE) 1941 may_move_terminal_to_buffer(term, TRUE); 1942 term_free_vterm(term); 1943 } 1944 1945 /* 1946 * Switch from Terminal-Job mode to Terminal-Normal mode. 1947 * Suspends updating the terminal window. 1948 */ 1949 static void 1950 term_enter_normal_mode(void) 1951 { 1952 term_T *term = curbuf->b_term; 1953 1954 set_terminal_mode(term, TRUE); 1955 1956 // Append the current terminal contents to the buffer. 1957 may_move_terminal_to_buffer(term, TRUE); 1958 1959 // Move the window cursor to the position of the cursor in the 1960 // terminal. 1961 curwin->w_cursor.lnum = term->tl_scrollback_scrolled 1962 + term->tl_cursor_pos.row + 1; 1963 check_cursor(); 1964 if (coladvance(term->tl_cursor_pos.col) == FAIL) 1965 coladvance(MAXCOL); 1966 curwin->w_set_curswant = TRUE; 1967 1968 // Display the same lines as in the terminal. 1969 curwin->w_topline = term->tl_scrollback_scrolled + 1; 1970 } 1971 1972 /* 1973 * Returns TRUE if the current window contains a terminal and we are in 1974 * Terminal-Normal mode. 1975 */ 1976 int 1977 term_in_normal_mode(void) 1978 { 1979 term_T *term = curbuf->b_term; 1980 1981 return term != NULL && term->tl_normal_mode; 1982 } 1983 1984 /* 1985 * Switch from Terminal-Normal mode to Terminal-Job mode. 1986 * Restores updating the terminal window. 1987 */ 1988 void 1989 term_enter_job_mode() 1990 { 1991 term_T *term = curbuf->b_term; 1992 1993 set_terminal_mode(term, FALSE); 1994 1995 if (term->tl_channel_closed) 1996 cleanup_vterm(term); 1997 redraw_buf_and_status_later(curbuf, NOT_VALID); 1998 #ifdef FEAT_PROP_POPUP 1999 if (WIN_IS_POPUP(curwin)) 2000 redraw_later(NOT_VALID); 2001 #endif 2002 } 2003 2004 /* 2005 * Get a key from the user with terminal mode mappings. 2006 * Note: while waiting a terminal may be closed and freed if the channel is 2007 * closed and ++close was used. 2008 */ 2009 static int 2010 term_vgetc() 2011 { 2012 int c; 2013 int save_State = State; 2014 int modify_other_keys = 2015 vterm_is_modify_other_keys(curbuf->b_term->tl_vterm); 2016 2017 State = TERMINAL; 2018 got_int = FALSE; 2019 #ifdef MSWIN 2020 ctrl_break_was_pressed = FALSE; 2021 #endif 2022 if (modify_other_keys) 2023 ++no_reduce_keys; 2024 c = vgetc(); 2025 got_int = FALSE; 2026 State = save_State; 2027 if (modify_other_keys) 2028 --no_reduce_keys; 2029 return c; 2030 } 2031 2032 static int mouse_was_outside = FALSE; 2033 2034 /* 2035 * Send key "c" with modifiers "modmask" to terminal. 2036 * Return FAIL when the key needs to be handled in Normal mode. 2037 * Return OK when the key was dropped or sent to the terminal. 2038 */ 2039 int 2040 send_keys_to_term(term_T *term, int c, int modmask, int typed) 2041 { 2042 char msg[KEY_BUF_LEN]; 2043 size_t len; 2044 int dragging_outside = FALSE; 2045 2046 // Catch keys that need to be handled as in Normal mode. 2047 switch (c) 2048 { 2049 case NUL: 2050 case K_ZERO: 2051 if (typed) 2052 stuffcharReadbuff(c); 2053 return FAIL; 2054 2055 case K_TABLINE: 2056 stuffcharReadbuff(c); 2057 return FAIL; 2058 2059 case K_IGNORE: 2060 case K_CANCEL: // used for :normal when running out of chars 2061 return FAIL; 2062 2063 case K_LEFTDRAG: 2064 case K_MIDDLEDRAG: 2065 case K_RIGHTDRAG: 2066 case K_X1DRAG: 2067 case K_X2DRAG: 2068 dragging_outside = mouse_was_outside; 2069 // FALLTHROUGH 2070 case K_LEFTMOUSE: 2071 case K_LEFTMOUSE_NM: 2072 case K_LEFTRELEASE: 2073 case K_LEFTRELEASE_NM: 2074 case K_MOUSEMOVE: 2075 case K_MIDDLEMOUSE: 2076 case K_MIDDLERELEASE: 2077 case K_RIGHTMOUSE: 2078 case K_RIGHTRELEASE: 2079 case K_X1MOUSE: 2080 case K_X1RELEASE: 2081 case K_X2MOUSE: 2082 case K_X2RELEASE: 2083 2084 case K_MOUSEUP: 2085 case K_MOUSEDOWN: 2086 case K_MOUSELEFT: 2087 case K_MOUSERIGHT: 2088 { 2089 int row = mouse_row; 2090 int col = mouse_col; 2091 2092 #ifdef FEAT_PROP_POPUP 2093 if (popup_is_popup(curwin)) 2094 { 2095 row -= popup_top_extra(curwin); 2096 col -= popup_left_extra(curwin); 2097 } 2098 #endif 2099 if (row < W_WINROW(curwin) 2100 || row >= (W_WINROW(curwin) + curwin->w_height) 2101 || col < curwin->w_wincol 2102 || col >= W_ENDCOL(curwin) 2103 || dragging_outside) 2104 { 2105 // click or scroll outside the current window or on status 2106 // line or vertical separator 2107 if (typed) 2108 { 2109 stuffcharReadbuff(c); 2110 mouse_was_outside = TRUE; 2111 } 2112 return FAIL; 2113 } 2114 } 2115 } 2116 if (typed) 2117 mouse_was_outside = FALSE; 2118 2119 // Convert the typed key to a sequence of bytes for the job. 2120 len = term_convert_key(term, c, modmask, msg); 2121 if (len > 0) 2122 // TODO: if FAIL is returned, stop? 2123 channel_send(term->tl_job->jv_channel, get_tty_part(term), 2124 (char_u *)msg, (int)len, NULL); 2125 2126 return OK; 2127 } 2128 2129 static void 2130 position_cursor(win_T *wp, VTermPos *pos, int add_off UNUSED) 2131 { 2132 wp->w_wrow = MIN(pos->row, MAX(0, wp->w_height - 1)); 2133 wp->w_wcol = MIN(pos->col, MAX(0, wp->w_width - 1)); 2134 #ifdef FEAT_PROP_POPUP 2135 if (add_off && popup_is_popup(curwin)) 2136 { 2137 wp->w_wrow += popup_top_extra(curwin); 2138 wp->w_wcol += popup_left_extra(curwin); 2139 } 2140 #endif 2141 wp->w_valid |= (VALID_WCOL|VALID_WROW); 2142 } 2143 2144 /* 2145 * Handle CTRL-W "": send register contents to the job. 2146 */ 2147 static void 2148 term_paste_register(int prev_c UNUSED) 2149 { 2150 int c; 2151 list_T *l; 2152 listitem_T *item; 2153 long reglen = 0; 2154 int type; 2155 2156 #ifdef FEAT_CMDL_INFO 2157 if (add_to_showcmd(prev_c)) 2158 if (add_to_showcmd('"')) 2159 out_flush(); 2160 #endif 2161 c = term_vgetc(); 2162 #ifdef FEAT_CMDL_INFO 2163 clear_showcmd(); 2164 #endif 2165 if (!term_use_loop()) 2166 // job finished while waiting for a character 2167 return; 2168 2169 // CTRL-W "= prompt for expression to evaluate. 2170 if (c == '=' && get_expr_register() != '=') 2171 return; 2172 if (!term_use_loop()) 2173 // job finished while waiting for a character 2174 return; 2175 2176 l = (list_T *)get_reg_contents(c, GREG_LIST); 2177 if (l != NULL) 2178 { 2179 type = get_reg_type(c, ®len); 2180 FOR_ALL_LIST_ITEMS(l, item) 2181 { 2182 char_u *s = tv_get_string(&item->li_tv); 2183 #ifdef MSWIN 2184 char_u *tmp = s; 2185 2186 if (!enc_utf8 && enc_codepage > 0) 2187 { 2188 WCHAR *ret = NULL; 2189 int length = 0; 2190 2191 MultiByteToWideChar_alloc(enc_codepage, 0, (char *)s, 2192 (int)STRLEN(s), &ret, &length); 2193 if (ret != NULL) 2194 { 2195 WideCharToMultiByte_alloc(CP_UTF8, 0, 2196 ret, length, (char **)&s, &length, 0, 0); 2197 vim_free(ret); 2198 } 2199 } 2200 #endif 2201 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN, 2202 s, (int)STRLEN(s), NULL); 2203 #ifdef MSWIN 2204 if (tmp != s) 2205 vim_free(s); 2206 #endif 2207 2208 if (item->li_next != NULL || type == MLINE) 2209 channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN, 2210 (char_u *)"\r", 1, NULL); 2211 } 2212 list_free(l); 2213 } 2214 } 2215 2216 /* 2217 * Return TRUE when waiting for a character in the terminal, the cursor of the 2218 * terminal should be displayed. 2219 */ 2220 int 2221 terminal_is_active() 2222 { 2223 return in_terminal_loop != NULL; 2224 } 2225 2226 /* 2227 * Return the highight group name for the terminal; "Terminal" if not set. 2228 */ 2229 static char_u * 2230 term_get_highlight_name(term_T *term) 2231 { 2232 if (term->tl_highlight_name == NULL) 2233 return (char_u *)"Terminal"; 2234 return term->tl_highlight_name; 2235 } 2236 2237 #if defined(FEAT_GUI) || defined(PROTO) 2238 cursorentry_T * 2239 term_get_cursor_shape(guicolor_T *fg, guicolor_T *bg) 2240 { 2241 term_T *term = in_terminal_loop; 2242 static cursorentry_T entry; 2243 int id; 2244 guicolor_T term_fg, term_bg; 2245 2246 CLEAR_FIELD(entry); 2247 entry.shape = entry.mshape = 2248 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE ? SHAPE_HOR : 2249 term->tl_cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT ? SHAPE_VER : 2250 SHAPE_BLOCK; 2251 entry.percentage = 20; 2252 if (term->tl_cursor_blink) 2253 { 2254 entry.blinkwait = 700; 2255 entry.blinkon = 400; 2256 entry.blinkoff = 250; 2257 } 2258 2259 // The highlight group overrules the defaults. 2260 id = syn_name2id(term_get_highlight_name(term)); 2261 if (id != 0) 2262 { 2263 syn_id2colors(id, &term_fg, &term_bg); 2264 *fg = term_bg; 2265 } 2266 else 2267 *fg = gui.back_pixel; 2268 2269 if (term->tl_cursor_color == NULL) 2270 { 2271 if (id != 0) 2272 *bg = term_fg; 2273 else 2274 *bg = gui.norm_pixel; 2275 } 2276 else 2277 *bg = color_name2handle(term->tl_cursor_color); 2278 entry.name = "n"; 2279 entry.used_for = SHAPE_CURSOR; 2280 2281 return &entry; 2282 } 2283 #endif 2284 2285 static void 2286 may_output_cursor_props(void) 2287 { 2288 if (!cursor_color_equal(last_set_cursor_color, desired_cursor_color) 2289 || last_set_cursor_shape != desired_cursor_shape 2290 || last_set_cursor_blink != desired_cursor_blink) 2291 { 2292 cursor_color_copy(&last_set_cursor_color, desired_cursor_color); 2293 last_set_cursor_shape = desired_cursor_shape; 2294 last_set_cursor_blink = desired_cursor_blink; 2295 term_cursor_color(cursor_color_get(desired_cursor_color)); 2296 if (desired_cursor_shape == -1 || desired_cursor_blink == -1) 2297 // this will restore the initial cursor style, if possible 2298 ui_cursor_shape_forced(TRUE); 2299 else 2300 term_cursor_shape(desired_cursor_shape, desired_cursor_blink); 2301 } 2302 } 2303 2304 /* 2305 * Set the cursor color and shape, if not last set to these. 2306 */ 2307 static void 2308 may_set_cursor_props(term_T *term) 2309 { 2310 #ifdef FEAT_GUI 2311 // For the GUI the cursor properties are obtained with 2312 // term_get_cursor_shape(). 2313 if (gui.in_use) 2314 return; 2315 #endif 2316 if (in_terminal_loop == term) 2317 { 2318 cursor_color_copy(&desired_cursor_color, term->tl_cursor_color); 2319 desired_cursor_shape = term->tl_cursor_shape; 2320 desired_cursor_blink = term->tl_cursor_blink; 2321 may_output_cursor_props(); 2322 } 2323 } 2324 2325 /* 2326 * Reset the desired cursor properties and restore them when needed. 2327 */ 2328 static void 2329 prepare_restore_cursor_props(void) 2330 { 2331 #ifdef FEAT_GUI 2332 if (gui.in_use) 2333 return; 2334 #endif 2335 cursor_color_copy(&desired_cursor_color, NULL); 2336 desired_cursor_shape = -1; 2337 desired_cursor_blink = -1; 2338 may_output_cursor_props(); 2339 } 2340 2341 /* 2342 * Returns TRUE if the current window contains a terminal and we are sending 2343 * keys to the job. 2344 * If "check_job_status" is TRUE update the job status. 2345 */ 2346 static int 2347 term_use_loop_check(int check_job_status) 2348 { 2349 term_T *term = curbuf->b_term; 2350 2351 return term != NULL 2352 && !term->tl_normal_mode 2353 && term->tl_vterm != NULL 2354 && term_job_running_check(term, check_job_status); 2355 } 2356 2357 /* 2358 * Returns TRUE if the current window contains a terminal and we are sending 2359 * keys to the job. 2360 */ 2361 int 2362 term_use_loop(void) 2363 { 2364 return term_use_loop_check(FALSE); 2365 } 2366 2367 /* 2368 * Called when entering a window with the mouse. If this is a terminal window 2369 * we may want to change state. 2370 */ 2371 void 2372 term_win_entered() 2373 { 2374 term_T *term = curbuf->b_term; 2375 2376 if (term != NULL) 2377 { 2378 if (term_use_loop_check(TRUE)) 2379 { 2380 reset_VIsual_and_resel(); 2381 if (State & INSERT) 2382 stop_insert_mode = TRUE; 2383 } 2384 mouse_was_outside = FALSE; 2385 enter_mouse_col = mouse_col; 2386 enter_mouse_row = mouse_row; 2387 } 2388 } 2389 2390 /* 2391 * vgetc() may not include CTRL in the key when modify_other_keys is set. 2392 * Return the Ctrl-key value in that case. 2393 */ 2394 static int 2395 raw_c_to_ctrl(int c) 2396 { 2397 if ((mod_mask & MOD_MASK_CTRL) 2398 && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_'))) 2399 return c & 0x1f; 2400 return c; 2401 } 2402 2403 /* 2404 * When modify_other_keys is set then do the reverse of raw_c_to_ctrl(). 2405 * May set "mod_mask". 2406 */ 2407 static int 2408 ctrl_to_raw_c(int c) 2409 { 2410 if (c < 0x20 && vterm_is_modify_other_keys(curbuf->b_term->tl_vterm)) 2411 { 2412 mod_mask |= MOD_MASK_CTRL; 2413 return c + '@'; 2414 } 2415 return c; 2416 } 2417 2418 /* 2419 * Wait for input and send it to the job. 2420 * When "blocking" is TRUE wait for a character to be typed. Otherwise return 2421 * when there is no more typahead. 2422 * Return when the start of a CTRL-W command is typed or anything else that 2423 * should be handled as a Normal mode command. 2424 * Returns OK if a typed character is to be handled in Normal mode, FAIL if 2425 * the terminal was closed. 2426 */ 2427 int 2428 terminal_loop(int blocking) 2429 { 2430 int c; 2431 int raw_c; 2432 int termwinkey = 0; 2433 int ret; 2434 #ifdef UNIX 2435 int tty_fd = curbuf->b_term->tl_job->jv_channel 2436 ->ch_part[get_tty_part(curbuf->b_term)].ch_fd; 2437 #endif 2438 int restore_cursor = FALSE; 2439 2440 // Remember the terminal we are sending keys to. However, the terminal 2441 // might be closed while waiting for a character, e.g. typing "exit" in a 2442 // shell and ++close was used. Therefore use curbuf->b_term instead of a 2443 // stored reference. 2444 in_terminal_loop = curbuf->b_term; 2445 2446 if (*curwin->w_p_twk != NUL) 2447 { 2448 termwinkey = string_to_key(curwin->w_p_twk, TRUE); 2449 if (termwinkey == Ctrl_W) 2450 termwinkey = 0; 2451 } 2452 position_cursor(curwin, &curbuf->b_term->tl_cursor_pos, TRUE); 2453 may_set_cursor_props(curbuf->b_term); 2454 2455 while (blocking || vpeekc_nomap() != NUL) 2456 { 2457 #ifdef FEAT_GUI 2458 if (!curbuf->b_term->tl_system) 2459 #endif 2460 // TODO: skip screen update when handling a sequence of keys. 2461 // Repeat redrawing in case a message is received while redrawing. 2462 while (must_redraw != 0) 2463 if (update_screen(0) == FAIL) 2464 break; 2465 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term) 2466 // job finished while redrawing 2467 break; 2468 2469 update_cursor(curbuf->b_term, FALSE); 2470 restore_cursor = TRUE; 2471 2472 raw_c = term_vgetc(); 2473 if (!term_use_loop_check(TRUE) || in_terminal_loop != curbuf->b_term) 2474 { 2475 // Job finished while waiting for a character. Push back the 2476 // received character. 2477 if (raw_c != K_IGNORE) 2478 vungetc(raw_c); 2479 break; 2480 } 2481 if (raw_c == K_IGNORE) 2482 continue; 2483 c = raw_c_to_ctrl(raw_c); 2484 2485 #ifdef UNIX 2486 /* 2487 * The shell or another program may change the tty settings. Getting 2488 * them for every typed character is a bit of overhead, but it's needed 2489 * for the first character typed, e.g. when Vim starts in a shell. 2490 */ 2491 if (mch_isatty(tty_fd)) 2492 { 2493 ttyinfo_T info; 2494 2495 // Get the current backspace character of the pty. 2496 if (get_tty_info(tty_fd, &info) == OK) 2497 term_backspace_char = info.backspace; 2498 } 2499 #endif 2500 2501 #ifdef MSWIN 2502 // On Windows winpty handles CTRL-C, don't send a CTRL_C_EVENT. 2503 // Use CTRL-BREAK to kill the job. 2504 if (ctrl_break_was_pressed) 2505 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill"); 2506 #endif 2507 // Was either CTRL-W (termwinkey) or CTRL-\ pressed? 2508 // Not in a system terminal. 2509 if ((c == (termwinkey == 0 ? Ctrl_W : termwinkey) || c == Ctrl_BSL) 2510 #ifdef FEAT_GUI 2511 && !curbuf->b_term->tl_system 2512 #endif 2513 ) 2514 { 2515 int prev_c = c; 2516 int prev_raw_c = raw_c; 2517 int prev_mod_mask = mod_mask; 2518 2519 #ifdef FEAT_CMDL_INFO 2520 if (add_to_showcmd(c)) 2521 out_flush(); 2522 #endif 2523 raw_c = term_vgetc(); 2524 c = raw_c_to_ctrl(raw_c); 2525 2526 #ifdef FEAT_CMDL_INFO 2527 clear_showcmd(); 2528 #endif 2529 if (!term_use_loop_check(TRUE) 2530 || in_terminal_loop != curbuf->b_term) 2531 // job finished while waiting for a character 2532 break; 2533 2534 if (prev_c == Ctrl_BSL) 2535 { 2536 if (c == Ctrl_N) 2537 { 2538 // CTRL-\ CTRL-N : go to Terminal-Normal mode. 2539 term_enter_normal_mode(); 2540 ret = FAIL; 2541 goto theend; 2542 } 2543 // Send both keys to the terminal, first one here, second one 2544 // below. 2545 send_keys_to_term(curbuf->b_term, prev_raw_c, prev_mod_mask, 2546 TRUE); 2547 } 2548 else if (c == Ctrl_C) 2549 { 2550 // "CTRL-W CTRL-C" or 'termwinkey' CTRL-C: end the job 2551 mch_signal_job(curbuf->b_term->tl_job, (char_u *)"kill"); 2552 } 2553 else if (c == '.') 2554 { 2555 // "CTRL-W .": send CTRL-W to the job 2556 // "'termwinkey' .": send 'termwinkey' to the job 2557 raw_c = ctrl_to_raw_c(termwinkey == 0 ? Ctrl_W : termwinkey); 2558 } 2559 else if (c == Ctrl_BSL) 2560 { 2561 // "CTRL-W CTRL-\": send CTRL-\ to the job 2562 raw_c = ctrl_to_raw_c(Ctrl_BSL); 2563 } 2564 else if (c == 'N') 2565 { 2566 // CTRL-W N : go to Terminal-Normal mode. 2567 term_enter_normal_mode(); 2568 ret = FAIL; 2569 goto theend; 2570 } 2571 else if (c == '"') 2572 { 2573 term_paste_register(prev_c); 2574 continue; 2575 } 2576 else if (termwinkey == 0 || c != termwinkey) 2577 { 2578 char_u buf[MB_MAXBYTES + 2]; 2579 2580 // Put the command into the typeahead buffer, when using the 2581 // stuff buffer KeyStuffed is set and 'langmap' won't be used. 2582 buf[0] = Ctrl_W; 2583 buf[(*mb_char2bytes)(c, buf + 1) + 1] = NUL; 2584 ins_typebuf(buf, REMAP_NONE, 0, TRUE, FALSE); 2585 ret = OK; 2586 goto theend; 2587 } 2588 } 2589 # ifdef MSWIN 2590 if (!enc_utf8 && has_mbyte && raw_c >= 0x80) 2591 { 2592 WCHAR wc; 2593 char_u mb[3]; 2594 2595 mb[0] = (unsigned)raw_c >> 8; 2596 mb[1] = raw_c; 2597 if (MultiByteToWideChar(GetACP(), 0, (char*)mb, 2, &wc, 1) > 0) 2598 raw_c = wc; 2599 } 2600 # endif 2601 if (send_keys_to_term(curbuf->b_term, raw_c, mod_mask, TRUE) != OK) 2602 { 2603 if (raw_c == K_MOUSEMOVE) 2604 // We are sure to come back here, don't reset the cursor color 2605 // and shape to avoid flickering. 2606 restore_cursor = FALSE; 2607 2608 ret = OK; 2609 goto theend; 2610 } 2611 } 2612 ret = FAIL; 2613 2614 theend: 2615 in_terminal_loop = NULL; 2616 if (restore_cursor) 2617 prepare_restore_cursor_props(); 2618 2619 // Move a snapshot of the screen contents to the buffer, so that completion 2620 // works in other buffers. 2621 if (curbuf->b_term != NULL && !curbuf->b_term->tl_normal_mode) 2622 may_move_terminal_to_buffer(curbuf->b_term, FALSE); 2623 2624 return ret; 2625 } 2626 2627 static void 2628 may_toggle_cursor(term_T *term) 2629 { 2630 if (in_terminal_loop == term) 2631 { 2632 if (term->tl_cursor_visible) 2633 cursor_on(); 2634 else 2635 cursor_off(); 2636 } 2637 } 2638 2639 /* 2640 * Cache "Terminal" highlight group colors. 2641 */ 2642 void 2643 set_terminal_default_colors(int cterm_fg, int cterm_bg) 2644 { 2645 term_default_cterm_fg = cterm_fg - 1; 2646 term_default_cterm_bg = cterm_bg - 1; 2647 } 2648 2649 static int 2650 get_default_cterm_fg(term_T *term) 2651 { 2652 if (term->tl_highlight_name != NULL) 2653 { 2654 int id = syn_name2id(term->tl_highlight_name); 2655 int fg = -1; 2656 int bg = -1; 2657 2658 if (id > 0) 2659 syn_id2cterm_bg(id, &fg, &bg); 2660 return fg; 2661 } 2662 return term_default_cterm_fg; 2663 } 2664 2665 static int 2666 get_default_cterm_bg(term_T *term) 2667 { 2668 if (term->tl_highlight_name != NULL) 2669 { 2670 int id = syn_name2id(term->tl_highlight_name); 2671 int fg = -1; 2672 int bg = -1; 2673 2674 if (id > 0) 2675 syn_id2cterm_bg(id, &fg, &bg); 2676 return bg; 2677 } 2678 return term_default_cterm_bg; 2679 } 2680 2681 /* 2682 * Reverse engineer the RGB value into a cterm color index. 2683 * First color is 1. Return 0 if no match found (default color). 2684 */ 2685 static int 2686 color2index(VTermColor *color, int fg, int *boldp) 2687 { 2688 int red = color->red; 2689 int blue = color->blue; 2690 int green = color->green; 2691 2692 if (color->ansi_index != VTERM_ANSI_INDEX_NONE) 2693 { 2694 // The first 16 colors and default: use the ANSI index. 2695 switch (color->ansi_index) 2696 { 2697 case 0: return 0; 2698 case 1: return lookup_color( 0, fg, boldp) + 1; // black 2699 case 2: return lookup_color( 4, fg, boldp) + 1; // dark red 2700 case 3: return lookup_color( 2, fg, boldp) + 1; // dark green 2701 case 4: return lookup_color( 7, fg, boldp) + 1; // dark yellow 2702 case 5: return lookup_color( 1, fg, boldp) + 1; // dark blue 2703 case 6: return lookup_color( 5, fg, boldp) + 1; // dark magenta 2704 case 7: return lookup_color( 3, fg, boldp) + 1; // dark cyan 2705 case 8: return lookup_color( 8, fg, boldp) + 1; // light grey 2706 case 9: return lookup_color(12, fg, boldp) + 1; // dark grey 2707 case 10: return lookup_color(20, fg, boldp) + 1; // red 2708 case 11: return lookup_color(16, fg, boldp) + 1; // green 2709 case 12: return lookup_color(24, fg, boldp) + 1; // yellow 2710 case 13: return lookup_color(14, fg, boldp) + 1; // blue 2711 case 14: return lookup_color(22, fg, boldp) + 1; // magenta 2712 case 15: return lookup_color(18, fg, boldp) + 1; // cyan 2713 case 16: return lookup_color(26, fg, boldp) + 1; // white 2714 } 2715 } 2716 2717 if (t_colors >= 256) 2718 { 2719 if (red == blue && red == green) 2720 { 2721 // 24-color greyscale plus white and black 2722 static int cutoff[23] = { 2723 0x0D, 0x17, 0x21, 0x2B, 0x35, 0x3F, 0x49, 0x53, 0x5D, 0x67, 2724 0x71, 0x7B, 0x85, 0x8F, 0x99, 0xA3, 0xAD, 0xB7, 0xC1, 0xCB, 2725 0xD5, 0xDF, 0xE9}; 2726 int i; 2727 2728 if (red < 5) 2729 return 17; // 00/00/00 2730 if (red > 245) // ff/ff/ff 2731 return 232; 2732 for (i = 0; i < 23; ++i) 2733 if (red < cutoff[i]) 2734 return i + 233; 2735 return 256; 2736 } 2737 { 2738 static int cutoff[5] = {0x2F, 0x73, 0x9B, 0xC3, 0xEB}; 2739 int ri, gi, bi; 2740 2741 // 216-color cube 2742 for (ri = 0; ri < 5; ++ri) 2743 if (red < cutoff[ri]) 2744 break; 2745 for (gi = 0; gi < 5; ++gi) 2746 if (green < cutoff[gi]) 2747 break; 2748 for (bi = 0; bi < 5; ++bi) 2749 if (blue < cutoff[bi]) 2750 break; 2751 return 17 + ri * 36 + gi * 6 + bi; 2752 } 2753 } 2754 return 0; 2755 } 2756 2757 /* 2758 * Convert Vterm attributes to highlight flags. 2759 */ 2760 static int 2761 vtermAttr2hl(VTermScreenCellAttrs cellattrs) 2762 { 2763 int attr = 0; 2764 2765 if (cellattrs.bold) 2766 attr |= HL_BOLD; 2767 if (cellattrs.underline) 2768 attr |= HL_UNDERLINE; 2769 if (cellattrs.italic) 2770 attr |= HL_ITALIC; 2771 if (cellattrs.strike) 2772 attr |= HL_STRIKETHROUGH; 2773 if (cellattrs.reverse) 2774 attr |= HL_INVERSE; 2775 return attr; 2776 } 2777 2778 /* 2779 * Store Vterm attributes in "cell" from highlight flags. 2780 */ 2781 static void 2782 hl2vtermAttr(int attr, cellattr_T *cell) 2783 { 2784 CLEAR_FIELD(cell->attrs); 2785 if (attr & HL_BOLD) 2786 cell->attrs.bold = 1; 2787 if (attr & HL_UNDERLINE) 2788 cell->attrs.underline = 1; 2789 if (attr & HL_ITALIC) 2790 cell->attrs.italic = 1; 2791 if (attr & HL_STRIKETHROUGH) 2792 cell->attrs.strike = 1; 2793 if (attr & HL_INVERSE) 2794 cell->attrs.reverse = 1; 2795 } 2796 2797 /* 2798 * Convert the attributes of a vterm cell into an attribute index. 2799 */ 2800 static int 2801 cell2attr( 2802 term_T *term, 2803 win_T *wp, 2804 VTermScreenCellAttrs cellattrs, 2805 VTermColor cellfg, 2806 VTermColor cellbg) 2807 { 2808 int attr = vtermAttr2hl(cellattrs); 2809 2810 #ifdef FEAT_GUI 2811 if (gui.in_use) 2812 { 2813 guicolor_T fg, bg; 2814 2815 fg = gui_mch_get_rgb_color(cellfg.red, cellfg.green, cellfg.blue); 2816 bg = gui_mch_get_rgb_color(cellbg.red, cellbg.green, cellbg.blue); 2817 return get_gui_attr_idx(attr, fg, bg); 2818 } 2819 else 2820 #endif 2821 #ifdef FEAT_TERMGUICOLORS 2822 if (p_tgc) 2823 { 2824 guicolor_T fg, bg; 2825 2826 fg = gui_get_rgb_color_cmn(cellfg.red, cellfg.green, cellfg.blue); 2827 bg = gui_get_rgb_color_cmn(cellbg.red, cellbg.green, cellbg.blue); 2828 2829 return get_tgc_attr_idx(attr, fg, bg); 2830 } 2831 else 2832 #endif 2833 { 2834 int bold = MAYBE; 2835 int fg = color2index(&cellfg, TRUE, &bold); 2836 int bg = color2index(&cellbg, FALSE, &bold); 2837 2838 // Use the 'wincolor' or "Terminal" highlighting for the default 2839 // colors. 2840 if ((fg == 0 || bg == 0) && t_colors >= 16) 2841 { 2842 int wincolor_fg = -1; 2843 int wincolor_bg = -1; 2844 2845 if (wp != NULL && *wp->w_p_wcr != NUL) 2846 { 2847 int id = syn_name2id(curwin->w_p_wcr); 2848 2849 // Get the 'wincolor' group colors. 2850 if (id > 0) 2851 syn_id2cterm_bg(id, &wincolor_fg, &wincolor_bg); 2852 } 2853 if (fg == 0) 2854 { 2855 if (wincolor_fg >= 0) 2856 fg = wincolor_fg + 1; 2857 else 2858 { 2859 int cterm_fg = get_default_cterm_fg(term); 2860 2861 if (cterm_fg >= 0) 2862 fg = cterm_fg + 1; 2863 } 2864 } 2865 if (bg == 0) 2866 { 2867 if (wincolor_bg >= 0) 2868 bg = wincolor_bg + 1; 2869 else 2870 { 2871 int cterm_bg = get_default_cterm_bg(term); 2872 2873 if (cterm_bg >= 0) 2874 bg = cterm_bg + 1; 2875 } 2876 } 2877 } 2878 2879 // with 8 colors set the bold attribute to get a bright foreground 2880 if (bold == TRUE) 2881 attr |= HL_BOLD; 2882 return get_cterm_attr_idx(attr, fg, bg); 2883 } 2884 return 0; 2885 } 2886 2887 static void 2888 set_dirty_snapshot(term_T *term) 2889 { 2890 term->tl_dirty_snapshot = TRUE; 2891 #ifdef FEAT_TIMERS 2892 if (!term->tl_normal_mode) 2893 { 2894 // Update the snapshot after 100 msec of not getting updates. 2895 profile_setlimit(100L, &term->tl_timer_due); 2896 term->tl_timer_set = TRUE; 2897 } 2898 #endif 2899 } 2900 2901 static int 2902 handle_damage(VTermRect rect, void *user) 2903 { 2904 term_T *term = (term_T *)user; 2905 2906 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, rect.start_row); 2907 term->tl_dirty_row_end = MAX(term->tl_dirty_row_end, rect.end_row); 2908 set_dirty_snapshot(term); 2909 redraw_buf_later(term->tl_buffer, SOME_VALID); 2910 return 1; 2911 } 2912 2913 static void 2914 term_scroll_up(term_T *term, int start_row, int count) 2915 { 2916 win_T *wp = NULL; 2917 int did_curwin = FALSE; 2918 VTermColor fg, bg; 2919 VTermScreenCellAttrs attr; 2920 int clear_attr; 2921 2922 CLEAR_FIELD(attr); 2923 2924 while (for_all_windows_and_curwin(&wp, &did_curwin)) 2925 { 2926 if (wp->w_buffer == term->tl_buffer) 2927 { 2928 // Set the color to clear lines with. 2929 vterm_state_get_default_colors(vterm_obtain_state(term->tl_vterm), 2930 &fg, &bg); 2931 clear_attr = cell2attr(term, wp, attr, fg, bg); 2932 win_del_lines(wp, start_row, count, FALSE, FALSE, clear_attr); 2933 } 2934 } 2935 } 2936 2937 static int 2938 handle_moverect(VTermRect dest, VTermRect src, void *user) 2939 { 2940 term_T *term = (term_T *)user; 2941 int count = src.start_row - dest.start_row; 2942 2943 // Scrolling up is done much more efficiently by deleting lines instead of 2944 // redrawing the text. But avoid doing this multiple times, postpone until 2945 // the redraw happens. 2946 if (dest.start_col == src.start_col 2947 && dest.end_col == src.end_col 2948 && dest.start_row < src.start_row) 2949 { 2950 if (dest.start_row == 0) 2951 term->tl_postponed_scroll += count; 2952 else 2953 term_scroll_up(term, dest.start_row, count); 2954 } 2955 2956 term->tl_dirty_row_start = MIN(term->tl_dirty_row_start, dest.start_row); 2957 term->tl_dirty_row_end = MIN(term->tl_dirty_row_end, dest.end_row); 2958 set_dirty_snapshot(term); 2959 2960 // Note sure if the scrolling will work correctly, let's do a complete 2961 // redraw later. 2962 redraw_buf_later(term->tl_buffer, NOT_VALID); 2963 return 1; 2964 } 2965 2966 static int 2967 handle_movecursor( 2968 VTermPos pos, 2969 VTermPos oldpos UNUSED, 2970 int visible, 2971 void *user) 2972 { 2973 term_T *term = (term_T *)user; 2974 win_T *wp = NULL; 2975 int did_curwin = FALSE; 2976 2977 term->tl_cursor_pos = pos; 2978 term->tl_cursor_visible = visible; 2979 2980 while (for_all_windows_and_curwin(&wp, &did_curwin)) 2981 { 2982 if (wp->w_buffer == term->tl_buffer) 2983 position_cursor(wp, &pos, FALSE); 2984 } 2985 if (term->tl_buffer == curbuf && !term->tl_normal_mode) 2986 { 2987 may_toggle_cursor(term); 2988 update_cursor(term, term->tl_cursor_visible); 2989 } 2990 2991 return 1; 2992 } 2993 2994 static int 2995 handle_settermprop( 2996 VTermProp prop, 2997 VTermValue *value, 2998 void *user) 2999 { 3000 term_T *term = (term_T *)user; 3001 3002 switch (prop) 3003 { 3004 case VTERM_PROP_TITLE: 3005 vim_free(term->tl_title); 3006 // a blank title isn't useful, make it empty, so that "running" is 3007 // displayed 3008 if (*skipwhite((char_u *)value->string) == NUL) 3009 term->tl_title = NULL; 3010 // Same as blank 3011 else if (term->tl_arg0_cmd != NULL 3012 && STRNCMP(term->tl_arg0_cmd, (char_u *)value->string, 3013 (int)STRLEN(term->tl_arg0_cmd)) == 0) 3014 term->tl_title = NULL; 3015 // Empty corrupted data of winpty 3016 else if (STRNCMP(" - ", (char_u *)value->string, 4) == 0) 3017 term->tl_title = NULL; 3018 #ifdef MSWIN 3019 else if (!enc_utf8 && enc_codepage > 0) 3020 { 3021 WCHAR *ret = NULL; 3022 int length = 0; 3023 3024 MultiByteToWideChar_alloc(CP_UTF8, 0, 3025 (char*)value->string, (int)STRLEN(value->string), 3026 &ret, &length); 3027 if (ret != NULL) 3028 { 3029 WideCharToMultiByte_alloc(enc_codepage, 0, 3030 ret, length, (char**)&term->tl_title, 3031 &length, 0, 0); 3032 vim_free(ret); 3033 } 3034 } 3035 #endif 3036 else 3037 term->tl_title = vim_strsave((char_u *)value->string); 3038 VIM_CLEAR(term->tl_status_text); 3039 if (term == curbuf->b_term) 3040 maketitle(); 3041 break; 3042 3043 case VTERM_PROP_CURSORVISIBLE: 3044 term->tl_cursor_visible = value->boolean; 3045 may_toggle_cursor(term); 3046 out_flush(); 3047 break; 3048 3049 case VTERM_PROP_CURSORBLINK: 3050 term->tl_cursor_blink = value->boolean; 3051 may_set_cursor_props(term); 3052 break; 3053 3054 case VTERM_PROP_CURSORSHAPE: 3055 term->tl_cursor_shape = value->number; 3056 may_set_cursor_props(term); 3057 break; 3058 3059 case VTERM_PROP_CURSORCOLOR: 3060 cursor_color_copy(&term->tl_cursor_color, (char_u*)value->string); 3061 may_set_cursor_props(term); 3062 break; 3063 3064 case VTERM_PROP_ALTSCREEN: 3065 // TODO: do anything else? 3066 term->tl_using_altscreen = value->boolean; 3067 break; 3068 3069 default: 3070 break; 3071 } 3072 // Always return 1, otherwise vterm doesn't store the value internally. 3073 return 1; 3074 } 3075 3076 /* 3077 * The job running in the terminal resized the terminal. 3078 */ 3079 static int 3080 handle_resize(int rows, int cols, void *user) 3081 { 3082 term_T *term = (term_T *)user; 3083 win_T *wp; 3084 3085 term->tl_rows = rows; 3086 term->tl_cols = cols; 3087 if (term->tl_vterm_size_changed) 3088 // Size was set by vterm_set_size(), don't set the window size. 3089 term->tl_vterm_size_changed = FALSE; 3090 else 3091 { 3092 FOR_ALL_WINDOWS(wp) 3093 { 3094 if (wp->w_buffer == term->tl_buffer) 3095 { 3096 win_setheight_win(rows, wp); 3097 win_setwidth_win(cols, wp); 3098 } 3099 } 3100 redraw_buf_later(term->tl_buffer, NOT_VALID); 3101 } 3102 return 1; 3103 } 3104 3105 /* 3106 * If the number of lines that are stored goes over 'termscrollback' then 3107 * delete the first 10%. 3108 * "gap" points to tl_scrollback or tl_scrollback_postponed. 3109 * "update_buffer" is TRUE when the buffer should be updated. 3110 */ 3111 static void 3112 limit_scrollback(term_T *term, garray_T *gap, int update_buffer) 3113 { 3114 if (gap->ga_len >= term->tl_buffer->b_p_twsl) 3115 { 3116 int todo = term->tl_buffer->b_p_twsl / 10; 3117 int i; 3118 3119 curbuf = term->tl_buffer; 3120 for (i = 0; i < todo; ++i) 3121 { 3122 vim_free(((sb_line_T *)gap->ga_data + i)->sb_cells); 3123 if (update_buffer) 3124 ml_delete(1, FALSE); 3125 } 3126 curbuf = curwin->w_buffer; 3127 3128 gap->ga_len -= todo; 3129 mch_memmove(gap->ga_data, 3130 (sb_line_T *)gap->ga_data + todo, 3131 sizeof(sb_line_T) * gap->ga_len); 3132 if (update_buffer) 3133 term->tl_scrollback_scrolled -= todo; 3134 } 3135 } 3136 3137 /* 3138 * Handle a line that is pushed off the top of the screen. 3139 */ 3140 static int 3141 handle_pushline(int cols, const VTermScreenCell *cells, void *user) 3142 { 3143 term_T *term = (term_T *)user; 3144 garray_T *gap; 3145 int update_buffer; 3146 3147 if (term->tl_normal_mode) 3148 { 3149 // In Terminal-Normal mode the user interacts with the buffer, thus we 3150 // must not change it. Postpone adding the scrollback lines. 3151 gap = &term->tl_scrollback_postponed; 3152 update_buffer = FALSE; 3153 } 3154 else 3155 { 3156 // First remove the lines that were appended before, the pushed line 3157 // goes above it. 3158 cleanup_scrollback(term); 3159 gap = &term->tl_scrollback; 3160 update_buffer = TRUE; 3161 } 3162 3163 limit_scrollback(term, gap, update_buffer); 3164 3165 if (ga_grow(gap, 1) == OK) 3166 { 3167 cellattr_T *p = NULL; 3168 int len = 0; 3169 int i; 3170 int c; 3171 int col; 3172 int text_len; 3173 char_u *text; 3174 sb_line_T *line; 3175 garray_T ga; 3176 cellattr_T fill_attr = term->tl_default_color; 3177 3178 // do not store empty cells at the end 3179 for (i = 0; i < cols; ++i) 3180 if (cells[i].chars[0] != 0) 3181 len = i + 1; 3182 else 3183 cell2cellattr(&cells[i], &fill_attr); 3184 3185 ga_init2(&ga, 1, 100); 3186 if (len > 0) 3187 p = ALLOC_MULT(cellattr_T, len); 3188 if (p != NULL) 3189 { 3190 for (col = 0; col < len; col += cells[col].width) 3191 { 3192 if (ga_grow(&ga, MB_MAXBYTES) == FAIL) 3193 { 3194 ga.ga_len = 0; 3195 break; 3196 } 3197 for (i = 0; (c = cells[col].chars[i]) > 0 || i == 0; ++i) 3198 ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c, 3199 (char_u *)ga.ga_data + ga.ga_len); 3200 cell2cellattr(&cells[col], &p[col]); 3201 } 3202 } 3203 if (ga_grow(&ga, 1) == FAIL) 3204 { 3205 if (update_buffer) 3206 text = (char_u *)""; 3207 else 3208 text = vim_strsave((char_u *)""); 3209 text_len = 0; 3210 } 3211 else 3212 { 3213 text = ga.ga_data; 3214 text_len = ga.ga_len; 3215 *(text + text_len) = NUL; 3216 } 3217 if (update_buffer) 3218 add_scrollback_line_to_buffer(term, text, text_len); 3219 3220 line = (sb_line_T *)gap->ga_data + gap->ga_len; 3221 line->sb_cols = len; 3222 line->sb_cells = p; 3223 line->sb_fill_attr = fill_attr; 3224 if (update_buffer) 3225 { 3226 line->sb_text = NULL; 3227 ++term->tl_scrollback_scrolled; 3228 ga_clear(&ga); // free the text 3229 } 3230 else 3231 { 3232 line->sb_text = text; 3233 ga_init(&ga); // text is kept in tl_scrollback_postponed 3234 } 3235 ++gap->ga_len; 3236 } 3237 return 0; // ignored 3238 } 3239 3240 /* 3241 * Called when leaving Terminal-Normal mode: deal with any scrollback that was 3242 * received and stored in tl_scrollback_postponed. 3243 */ 3244 static void 3245 handle_postponed_scrollback(term_T *term) 3246 { 3247 int i; 3248 3249 if (term->tl_scrollback_postponed.ga_len == 0) 3250 return; 3251 ch_log(NULL, "Moving postponed scrollback to scrollback"); 3252 3253 // First remove the lines that were appended before, the pushed lines go 3254 // above it. 3255 cleanup_scrollback(term); 3256 3257 for (i = 0; i < term->tl_scrollback_postponed.ga_len; ++i) 3258 { 3259 char_u *text; 3260 sb_line_T *pp_line; 3261 sb_line_T *line; 3262 3263 if (ga_grow(&term->tl_scrollback, 1) == FAIL) 3264 break; 3265 pp_line = (sb_line_T *)term->tl_scrollback_postponed.ga_data + i; 3266 3267 text = pp_line->sb_text; 3268 if (text == NULL) 3269 text = (char_u *)""; 3270 add_scrollback_line_to_buffer(term, text, (int)STRLEN(text)); 3271 vim_free(pp_line->sb_text); 3272 3273 line = (sb_line_T *)term->tl_scrollback.ga_data 3274 + term->tl_scrollback.ga_len; 3275 line->sb_cols = pp_line->sb_cols; 3276 line->sb_cells = pp_line->sb_cells; 3277 line->sb_fill_attr = pp_line->sb_fill_attr; 3278 line->sb_text = NULL; 3279 ++term->tl_scrollback_scrolled; 3280 ++term->tl_scrollback.ga_len; 3281 } 3282 3283 ga_clear(&term->tl_scrollback_postponed); 3284 limit_scrollback(term, &term->tl_scrollback, TRUE); 3285 } 3286 3287 static VTermScreenCallbacks screen_callbacks = { 3288 handle_damage, // damage 3289 handle_moverect, // moverect 3290 handle_movecursor, // movecursor 3291 handle_settermprop, // settermprop 3292 NULL, // bell 3293 handle_resize, // resize 3294 handle_pushline, // sb_pushline 3295 NULL // sb_popline 3296 }; 3297 3298 /* 3299 * Do the work after the channel of a terminal was closed. 3300 * Must be called only when updating_screen is FALSE. 3301 * Returns TRUE when a buffer was closed (list of terminals may have changed). 3302 */ 3303 static int 3304 term_after_channel_closed(term_T *term) 3305 { 3306 // Unless in Terminal-Normal mode: clear the vterm. 3307 if (!term->tl_normal_mode) 3308 { 3309 int fnum = term->tl_buffer->b_fnum; 3310 3311 cleanup_vterm(term); 3312 3313 if (term->tl_finish == TL_FINISH_CLOSE) 3314 { 3315 aco_save_T aco; 3316 int do_set_w_closing = term->tl_buffer->b_nwindows == 0; 3317 #ifdef FEAT_PROP_POPUP 3318 win_T *pwin = NULL; 3319 3320 // If this was a terminal in a popup window, go back to the 3321 // previous window. 3322 if (popup_is_popup(curwin) && curbuf == term->tl_buffer) 3323 { 3324 pwin = curwin; 3325 if (win_valid(prevwin)) 3326 win_enter(prevwin, FALSE); 3327 } 3328 else 3329 #endif 3330 // If this is the last normal window: exit Vim. 3331 if (term->tl_buffer->b_nwindows > 0 && only_one_window()) 3332 { 3333 exarg_T ea; 3334 3335 CLEAR_FIELD(ea); 3336 ex_quit(&ea); 3337 return TRUE; 3338 } 3339 3340 // ++close or term_finish == "close" 3341 ch_log(NULL, "terminal job finished, closing window"); 3342 aucmd_prepbuf(&aco, term->tl_buffer); 3343 // Avoid closing the window if we temporarily use it. 3344 if (curwin == aucmd_win) 3345 do_set_w_closing = TRUE; 3346 if (do_set_w_closing) 3347 curwin->w_closing = TRUE; 3348 do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE); 3349 if (do_set_w_closing) 3350 curwin->w_closing = FALSE; 3351 aucmd_restbuf(&aco); 3352 #ifdef FEAT_PROP_POPUP 3353 if (pwin != NULL) 3354 popup_close_with_retval(pwin, 0); 3355 #endif 3356 return TRUE; 3357 } 3358 if (term->tl_finish == TL_FINISH_OPEN 3359 && term->tl_buffer->b_nwindows == 0) 3360 { 3361 char buf[50]; 3362 3363 // TODO: use term_opencmd 3364 ch_log(NULL, "terminal job finished, opening window"); 3365 vim_snprintf(buf, sizeof(buf), 3366 term->tl_opencmd == NULL 3367 ? "botright sbuf %d" 3368 : (char *)term->tl_opencmd, fnum); 3369 do_cmdline_cmd((char_u *)buf); 3370 } 3371 else 3372 ch_log(NULL, "terminal job finished"); 3373 } 3374 3375 redraw_buf_and_status_later(term->tl_buffer, NOT_VALID); 3376 return FALSE; 3377 } 3378 3379 #if defined(FEAT_PROP_POPUP) || defined(PROTO) 3380 /* 3381 * If the current window is a terminal in a popup window and the job has 3382 * finished, close the popup window and to back to the previous window. 3383 * Otherwise return FAIL. 3384 */ 3385 int 3386 may_close_term_popup(void) 3387 { 3388 if (popup_is_popup(curwin) && curbuf->b_term != NULL 3389 && !term_job_running(curbuf->b_term)) 3390 { 3391 win_T *pwin = curwin; 3392 3393 if (win_valid(prevwin)) 3394 win_enter(prevwin, FALSE); 3395 popup_close_with_retval(pwin, 0); 3396 return OK; 3397 } 3398 return FAIL; 3399 } 3400 #endif 3401 3402 /* 3403 * Called when a channel has been closed. 3404 * If this was a channel for a terminal window then finish it up. 3405 */ 3406 void 3407 term_channel_closed(channel_T *ch) 3408 { 3409 term_T *term; 3410 term_T *next_term; 3411 int did_one = FALSE; 3412 3413 for (term = first_term; term != NULL; term = next_term) 3414 { 3415 next_term = term->tl_next; 3416 if (term->tl_job == ch->ch_job && !term->tl_channel_closed) 3417 { 3418 term->tl_channel_closed = TRUE; 3419 did_one = TRUE; 3420 3421 VIM_CLEAR(term->tl_title); 3422 VIM_CLEAR(term->tl_status_text); 3423 #ifdef MSWIN 3424 if (term->tl_out_fd != NULL) 3425 { 3426 fclose(term->tl_out_fd); 3427 term->tl_out_fd = NULL; 3428 } 3429 #endif 3430 3431 if (updating_screen) 3432 { 3433 // Cannot open or close windows now. Can happen when 3434 // 'lazyredraw' is set. 3435 term->tl_channel_recently_closed = TRUE; 3436 continue; 3437 } 3438 3439 if (term_after_channel_closed(term)) 3440 next_term = first_term; 3441 } 3442 } 3443 3444 if (did_one) 3445 { 3446 redraw_statuslines(); 3447 3448 // Need to break out of vgetc(). 3449 ins_char_typebuf(K_IGNORE); 3450 typebuf_was_filled = TRUE; 3451 3452 term = curbuf->b_term; 3453 if (term != NULL) 3454 { 3455 if (term->tl_job == ch->ch_job) 3456 maketitle(); 3457 update_cursor(term, term->tl_cursor_visible); 3458 } 3459 } 3460 } 3461 3462 /* 3463 * To be called after resetting updating_screen: handle any terminal where the 3464 * channel was closed. 3465 */ 3466 void 3467 term_check_channel_closed_recently() 3468 { 3469 term_T *term; 3470 term_T *next_term; 3471 3472 for (term = first_term; term != NULL; term = next_term) 3473 { 3474 next_term = term->tl_next; 3475 if (term->tl_channel_recently_closed) 3476 { 3477 term->tl_channel_recently_closed = FALSE; 3478 if (term_after_channel_closed(term)) 3479 // start over, the list may have changed 3480 next_term = first_term; 3481 } 3482 } 3483 } 3484 3485 /* 3486 * Fill one screen line from a line of the terminal. 3487 * Advances "pos" to past the last column. 3488 */ 3489 static void 3490 term_line2screenline( 3491 term_T *term, 3492 win_T *wp, 3493 VTermScreen *screen, 3494 VTermPos *pos, 3495 int max_col) 3496 { 3497 int off = screen_get_current_line_off(); 3498 3499 for (pos->col = 0; pos->col < max_col; ) 3500 { 3501 VTermScreenCell cell; 3502 int c; 3503 3504 if (vterm_screen_get_cell(screen, *pos, &cell) == 0) 3505 CLEAR_FIELD(cell); 3506 3507 c = cell.chars[0]; 3508 if (c == NUL) 3509 { 3510 ScreenLines[off] = ' '; 3511 if (enc_utf8) 3512 ScreenLinesUC[off] = NUL; 3513 } 3514 else 3515 { 3516 if (enc_utf8) 3517 { 3518 int i; 3519 3520 // composing chars 3521 for (i = 0; i < Screen_mco 3522 && i + 1 < VTERM_MAX_CHARS_PER_CELL; ++i) 3523 { 3524 ScreenLinesC[i][off] = cell.chars[i + 1]; 3525 if (cell.chars[i + 1] == 0) 3526 break; 3527 } 3528 if (c >= 0x80 || (Screen_mco > 0 3529 && ScreenLinesC[0][off] != 0)) 3530 { 3531 ScreenLines[off] = ' '; 3532 ScreenLinesUC[off] = c; 3533 } 3534 else 3535 { 3536 ScreenLines[off] = c; 3537 ScreenLinesUC[off] = NUL; 3538 } 3539 } 3540 #ifdef MSWIN 3541 else if (has_mbyte && c >= 0x80) 3542 { 3543 char_u mb[MB_MAXBYTES+1]; 3544 WCHAR wc = c; 3545 3546 if (WideCharToMultiByte(GetACP(), 0, &wc, 1, 3547 (char*)mb, 2, 0, 0) > 1) 3548 { 3549 ScreenLines[off] = mb[0]; 3550 ScreenLines[off + 1] = mb[1]; 3551 cell.width = mb_ptr2cells(mb); 3552 } 3553 else 3554 ScreenLines[off] = c; 3555 } 3556 #endif 3557 else 3558 ScreenLines[off] = c; 3559 } 3560 ScreenAttrs[off] = cell2attr(term, wp, cell.attrs, cell.fg, cell.bg); 3561 3562 ++pos->col; 3563 ++off; 3564 if (cell.width == 2) 3565 { 3566 if (enc_utf8) 3567 ScreenLinesUC[off] = NUL; 3568 3569 // don't set the second byte to NUL for a DBCS encoding, it 3570 // has been set above 3571 if (enc_utf8 || !has_mbyte) 3572 ScreenLines[off] = NUL; 3573 3574 ++pos->col; 3575 ++off; 3576 } 3577 } 3578 } 3579 3580 #if defined(FEAT_GUI) 3581 static void 3582 update_system_term(term_T *term) 3583 { 3584 VTermPos pos; 3585 VTermScreen *screen; 3586 3587 if (term->tl_vterm == NULL) 3588 return; 3589 screen = vterm_obtain_screen(term->tl_vterm); 3590 3591 // Scroll up to make more room for terminal lines if needed. 3592 while (term->tl_toprow > 0 3593 && (Rows - term->tl_toprow) < term->tl_dirty_row_end) 3594 { 3595 int save_p_more = p_more; 3596 3597 p_more = FALSE; 3598 msg_row = Rows - 1; 3599 msg_puts("\n"); 3600 p_more = save_p_more; 3601 --term->tl_toprow; 3602 } 3603 3604 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end 3605 && pos.row < Rows; ++pos.row) 3606 { 3607 if (pos.row < term->tl_rows) 3608 { 3609 int max_col = MIN(Columns, term->tl_cols); 3610 3611 term_line2screenline(term, NULL, screen, &pos, max_col); 3612 } 3613 else 3614 pos.col = 0; 3615 3616 screen_line(term->tl_toprow + pos.row, 0, pos.col, Columns, 0); 3617 } 3618 3619 term->tl_dirty_row_start = MAX_ROW; 3620 term->tl_dirty_row_end = 0; 3621 } 3622 #endif 3623 3624 /* 3625 * Return TRUE if window "wp" is to be redrawn with term_update_window(). 3626 * Returns FALSE when there is no terminal running in this window or it is in 3627 * Terminal-Normal mode. 3628 */ 3629 int 3630 term_do_update_window(win_T *wp) 3631 { 3632 term_T *term = wp->w_buffer->b_term; 3633 3634 return term != NULL && term->tl_vterm != NULL && !term->tl_normal_mode; 3635 } 3636 3637 /* 3638 * Called to update a window that contains an active terminal. 3639 */ 3640 void 3641 term_update_window(win_T *wp) 3642 { 3643 term_T *term = wp->w_buffer->b_term; 3644 VTerm *vterm; 3645 VTermScreen *screen; 3646 VTermState *state; 3647 VTermPos pos; 3648 int rows, cols; 3649 int newrows, newcols; 3650 int minsize; 3651 win_T *twp; 3652 3653 vterm = term->tl_vterm; 3654 screen = vterm_obtain_screen(vterm); 3655 state = vterm_obtain_state(vterm); 3656 3657 // We use NOT_VALID on a resize or scroll, redraw everything then. With 3658 // SOME_VALID only redraw what was marked dirty. 3659 if (wp->w_redr_type > SOME_VALID) 3660 { 3661 term->tl_dirty_row_start = 0; 3662 term->tl_dirty_row_end = MAX_ROW; 3663 3664 if (term->tl_postponed_scroll > 0 3665 && term->tl_postponed_scroll < term->tl_rows / 3) 3666 // Scrolling is usually faster than redrawing, when there are only 3667 // a few lines to scroll. 3668 term_scroll_up(term, 0, term->tl_postponed_scroll); 3669 term->tl_postponed_scroll = 0; 3670 } 3671 3672 /* 3673 * If the window was resized a redraw will be triggered and we get here. 3674 * Adjust the size of the vterm unless 'termwinsize' specifies a fixed size. 3675 */ 3676 minsize = parse_termwinsize(wp, &rows, &cols); 3677 3678 newrows = 99999; 3679 newcols = 99999; 3680 for (twp = firstwin; ; twp = twp->w_next) 3681 { 3682 // Always use curwin, it may be a popup window. 3683 win_T *wwp = twp == NULL ? curwin : twp; 3684 3685 // When more than one window shows the same terminal, use the 3686 // smallest size. 3687 if (wwp->w_buffer == term->tl_buffer) 3688 { 3689 newrows = MIN(newrows, wwp->w_height); 3690 newcols = MIN(newcols, wwp->w_width); 3691 } 3692 if (twp == NULL) 3693 break; 3694 } 3695 if (newrows == 99999 || newcols == 99999) 3696 return; // safety exit 3697 newrows = rows == 0 ? newrows : minsize ? MAX(rows, newrows) : rows; 3698 newcols = cols == 0 ? newcols : minsize ? MAX(cols, newcols) : cols; 3699 3700 if (term->tl_rows != newrows || term->tl_cols != newcols) 3701 { 3702 term->tl_vterm_size_changed = TRUE; 3703 vterm_set_size(vterm, newrows, newcols); 3704 ch_log(term->tl_job->jv_channel, "Resizing terminal to %d lines", 3705 newrows); 3706 term_report_winsize(term, newrows, newcols); 3707 3708 // Updating the terminal size will cause the snapshot to be cleared. 3709 // When not in terminal_loop() we need to restore it. 3710 if (term != in_terminal_loop) 3711 may_move_terminal_to_buffer(term, FALSE); 3712 } 3713 3714 // The cursor may have been moved when resizing. 3715 vterm_state_get_cursorpos(state, &pos); 3716 position_cursor(wp, &pos, FALSE); 3717 3718 for (pos.row = term->tl_dirty_row_start; pos.row < term->tl_dirty_row_end 3719 && pos.row < wp->w_height; ++pos.row) 3720 { 3721 if (pos.row < term->tl_rows) 3722 { 3723 int max_col = MIN(wp->w_width, term->tl_cols); 3724 3725 term_line2screenline(term, wp, screen, &pos, max_col); 3726 } 3727 else 3728 pos.col = 0; 3729 3730 screen_line(wp->w_winrow + pos.row 3731 #ifdef FEAT_MENU 3732 + winbar_height(wp) 3733 #endif 3734 , wp->w_wincol, pos.col, wp->w_width, 3735 #ifdef FEAT_PROP_POPUP 3736 popup_is_popup(wp) ? SLF_POPUP : 3737 #endif 3738 0); 3739 } 3740 term->tl_dirty_row_start = MAX_ROW; 3741 term->tl_dirty_row_end = 0; 3742 } 3743 3744 /* 3745 * Return TRUE if "wp" is a terminal window where the job has finished. 3746 */ 3747 int 3748 term_is_finished(buf_T *buf) 3749 { 3750 return buf->b_term != NULL && buf->b_term->tl_vterm == NULL; 3751 } 3752 3753 /* 3754 * Return TRUE if "wp" is a terminal window where the job has finished or we 3755 * are in Terminal-Normal mode, thus we show the buffer contents. 3756 */ 3757 int 3758 term_show_buffer(buf_T *buf) 3759 { 3760 term_T *term = buf->b_term; 3761 3762 return term != NULL && (term->tl_vterm == NULL || term->tl_normal_mode); 3763 } 3764 3765 /* 3766 * The current buffer is going to be changed. If there is terminal 3767 * highlighting remove it now. 3768 */ 3769 void 3770 term_change_in_curbuf(void) 3771 { 3772 term_T *term = curbuf->b_term; 3773 3774 if (term_is_finished(curbuf) && term->tl_scrollback.ga_len > 0) 3775 { 3776 free_scrollback(term); 3777 redraw_buf_later(term->tl_buffer, NOT_VALID); 3778 3779 // The buffer is now like a normal buffer, it cannot be easily 3780 // abandoned when changed. 3781 set_string_option_direct((char_u *)"buftype", -1, 3782 (char_u *)"", OPT_FREE|OPT_LOCAL, 0); 3783 } 3784 } 3785 3786 /* 3787 * Get the screen attribute for a position in the buffer. 3788 * Use a negative "col" to get the filler background color. 3789 */ 3790 int 3791 term_get_attr(win_T *wp, linenr_T lnum, int col) 3792 { 3793 buf_T *buf = wp->w_buffer; 3794 term_T *term = buf->b_term; 3795 sb_line_T *line; 3796 cellattr_T *cellattr; 3797 3798 if (lnum > term->tl_scrollback.ga_len) 3799 cellattr = &term->tl_default_color; 3800 else 3801 { 3802 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum - 1; 3803 if (col < 0 || col >= line->sb_cols) 3804 cellattr = &line->sb_fill_attr; 3805 else 3806 cellattr = line->sb_cells + col; 3807 } 3808 return cell2attr(term, wp, cellattr->attrs, cellattr->fg, cellattr->bg); 3809 } 3810 3811 /* 3812 * Convert a cterm color number 0 - 255 to RGB. 3813 * This is compatible with xterm. 3814 */ 3815 static void 3816 cterm_color2vterm(int nr, VTermColor *rgb) 3817 { 3818 cterm_color2rgb(nr, &rgb->red, &rgb->green, &rgb->blue, &rgb->ansi_index); 3819 } 3820 3821 /* 3822 * Initialize term->tl_default_color from the environment. 3823 */ 3824 static void 3825 init_default_colors(term_T *term, win_T *wp) 3826 { 3827 VTermColor *fg, *bg; 3828 int fgval, bgval; 3829 int id; 3830 3831 CLEAR_FIELD(term->tl_default_color.attrs); 3832 term->tl_default_color.width = 1; 3833 fg = &term->tl_default_color.fg; 3834 bg = &term->tl_default_color.bg; 3835 3836 // Vterm uses a default black background. Set it to white when 3837 // 'background' is "light". 3838 if (*p_bg == 'l') 3839 { 3840 fgval = 0; 3841 bgval = 255; 3842 } 3843 else 3844 { 3845 fgval = 255; 3846 bgval = 0; 3847 } 3848 fg->red = fg->green = fg->blue = fgval; 3849 bg->red = bg->green = bg->blue = bgval; 3850 fg->ansi_index = bg->ansi_index = VTERM_ANSI_INDEX_DEFAULT; 3851 3852 // The 'wincolor' or the highlight group overrules the defaults. 3853 if (wp != NULL && *wp->w_p_wcr != NUL) 3854 id = syn_name2id(wp->w_p_wcr); 3855 else 3856 id = syn_name2id(term_get_highlight_name(term)); 3857 3858 // Use the actual color for the GUI and when 'termguicolors' is set. 3859 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) 3860 if (0 3861 # ifdef FEAT_GUI 3862 || gui.in_use 3863 # endif 3864 # ifdef FEAT_TERMGUICOLORS 3865 || p_tgc 3866 # ifdef FEAT_VTP 3867 // Finally get INVALCOLOR on this execution path 3868 || (!p_tgc && t_colors >= 256) 3869 # endif 3870 # endif 3871 ) 3872 { 3873 guicolor_T fg_rgb = INVALCOLOR; 3874 guicolor_T bg_rgb = INVALCOLOR; 3875 3876 if (id != 0) 3877 syn_id2colors(id, &fg_rgb, &bg_rgb); 3878 3879 # ifdef FEAT_GUI 3880 if (gui.in_use) 3881 { 3882 if (fg_rgb == INVALCOLOR) 3883 fg_rgb = gui.norm_pixel; 3884 if (bg_rgb == INVALCOLOR) 3885 bg_rgb = gui.back_pixel; 3886 } 3887 # ifdef FEAT_TERMGUICOLORS 3888 else 3889 # endif 3890 # endif 3891 # ifdef FEAT_TERMGUICOLORS 3892 { 3893 if (fg_rgb == INVALCOLOR) 3894 fg_rgb = cterm_normal_fg_gui_color; 3895 if (bg_rgb == INVALCOLOR) 3896 bg_rgb = cterm_normal_bg_gui_color; 3897 } 3898 # endif 3899 if (fg_rgb != INVALCOLOR) 3900 { 3901 long_u rgb = GUI_MCH_GET_RGB(fg_rgb); 3902 3903 fg->red = (unsigned)(rgb >> 16); 3904 fg->green = (unsigned)(rgb >> 8) & 255; 3905 fg->blue = (unsigned)rgb & 255; 3906 } 3907 if (bg_rgb != INVALCOLOR) 3908 { 3909 long_u rgb = GUI_MCH_GET_RGB(bg_rgb); 3910 3911 bg->red = (unsigned)(rgb >> 16); 3912 bg->green = (unsigned)(rgb >> 8) & 255; 3913 bg->blue = (unsigned)rgb & 255; 3914 } 3915 } 3916 else 3917 #endif 3918 if (id != 0 && t_colors >= 16) 3919 { 3920 int cterm_fg = get_default_cterm_fg(term); 3921 int cterm_bg = get_default_cterm_bg(term); 3922 3923 if (cterm_fg >= 0) 3924 cterm_color2vterm(cterm_fg, fg); 3925 if (cterm_bg >= 0) 3926 cterm_color2vterm(cterm_bg, bg); 3927 } 3928 else 3929 { 3930 #if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) 3931 int tmp; 3932 #endif 3933 3934 // In an MS-Windows console we know the normal colors. 3935 if (cterm_normal_fg_color > 0) 3936 { 3937 cterm_color2vterm(cterm_normal_fg_color - 1, fg); 3938 # if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) 3939 # ifdef VIMDLL 3940 if (!gui.in_use) 3941 # endif 3942 { 3943 tmp = fg->red; 3944 fg->red = fg->blue; 3945 fg->blue = tmp; 3946 } 3947 # endif 3948 } 3949 # ifdef FEAT_TERMRESPONSE 3950 else 3951 term_get_fg_color(&fg->red, &fg->green, &fg->blue); 3952 # endif 3953 3954 if (cterm_normal_bg_color > 0) 3955 { 3956 cterm_color2vterm(cterm_normal_bg_color - 1, bg); 3957 # if defined(MSWIN) && (!defined(FEAT_GUI_MSWIN) || defined(VIMDLL)) 3958 # ifdef VIMDLL 3959 if (!gui.in_use) 3960 # endif 3961 { 3962 tmp = fg->red; 3963 fg->red = fg->blue; 3964 fg->blue = tmp; 3965 } 3966 # endif 3967 } 3968 # ifdef FEAT_TERMRESPONSE 3969 else 3970 term_get_bg_color(&bg->red, &bg->green, &bg->blue); 3971 # endif 3972 } 3973 } 3974 3975 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) 3976 /* 3977 * Set the 16 ANSI colors from array of RGB values 3978 */ 3979 static void 3980 set_vterm_palette(VTerm *vterm, long_u *rgb) 3981 { 3982 int index = 0; 3983 VTermState *state = vterm_obtain_state(vterm); 3984 3985 for (; index < 16; index++) 3986 { 3987 VTermColor color; 3988 3989 color.red = (unsigned)(rgb[index] >> 16); 3990 color.green = (unsigned)(rgb[index] >> 8) & 255; 3991 color.blue = (unsigned)rgb[index] & 255; 3992 vterm_state_set_palette_color(state, index, &color); 3993 } 3994 } 3995 3996 /* 3997 * Set the ANSI color palette from a list of colors 3998 */ 3999 static int 4000 set_ansi_colors_list(VTerm *vterm, list_T *list) 4001 { 4002 int n = 0; 4003 long_u rgb[16]; 4004 listitem_T *li; 4005 4006 for (li = list->lv_first; li != NULL && n < 16; li = li->li_next, n++) 4007 { 4008 char_u *color_name; 4009 guicolor_T guicolor; 4010 4011 color_name = tv_get_string_chk(&li->li_tv); 4012 if (color_name == NULL) 4013 return FAIL; 4014 4015 guicolor = GUI_GET_COLOR(color_name); 4016 if (guicolor == INVALCOLOR) 4017 return FAIL; 4018 4019 rgb[n] = GUI_MCH_GET_RGB(guicolor); 4020 } 4021 4022 if (n != 16 || li != NULL) 4023 return FAIL; 4024 4025 set_vterm_palette(vterm, rgb); 4026 4027 return OK; 4028 } 4029 4030 /* 4031 * Initialize the ANSI color palette from g:terminal_ansi_colors[0:15] 4032 */ 4033 static void 4034 init_vterm_ansi_colors(VTerm *vterm) 4035 { 4036 dictitem_T *var = find_var((char_u *)"g:terminal_ansi_colors", NULL, TRUE); 4037 4038 if (var != NULL 4039 && (var->di_tv.v_type != VAR_LIST 4040 || var->di_tv.vval.v_list == NULL 4041 || var->di_tv.vval.v_list->lv_first == &range_list_item 4042 || set_ansi_colors_list(vterm, var->di_tv.vval.v_list) == FAIL)) 4043 semsg(_(e_invarg2), "g:terminal_ansi_colors"); 4044 } 4045 #endif 4046 4047 /* 4048 * Handles a "drop" command from the job in the terminal. 4049 * "item" is the file name, "item->li_next" may have options. 4050 */ 4051 static void 4052 handle_drop_command(listitem_T *item) 4053 { 4054 char_u *fname = tv_get_string(&item->li_tv); 4055 listitem_T *opt_item = item->li_next; 4056 int bufnr; 4057 win_T *wp; 4058 tabpage_T *tp; 4059 exarg_T ea; 4060 char_u *tofree = NULL; 4061 4062 bufnr = buflist_add(fname, BLN_LISTED | BLN_NOOPT); 4063 FOR_ALL_TAB_WINDOWS(tp, wp) 4064 { 4065 if (wp->w_buffer->b_fnum == bufnr) 4066 { 4067 // buffer is in a window already, go there 4068 goto_tabpage_win(tp, wp); 4069 return; 4070 } 4071 } 4072 4073 CLEAR_FIELD(ea); 4074 4075 if (opt_item != NULL && opt_item->li_tv.v_type == VAR_DICT 4076 && opt_item->li_tv.vval.v_dict != NULL) 4077 { 4078 dict_T *dict = opt_item->li_tv.vval.v_dict; 4079 char_u *p; 4080 4081 p = dict_get_string(dict, (char_u *)"ff", FALSE); 4082 if (p == NULL) 4083 p = dict_get_string(dict, (char_u *)"fileformat", FALSE); 4084 if (p != NULL) 4085 { 4086 if (check_ff_value(p) == FAIL) 4087 ch_log(NULL, "Invalid ff argument to drop: %s", p); 4088 else 4089 ea.force_ff = *p; 4090 } 4091 p = dict_get_string(dict, (char_u *)"enc", FALSE); 4092 if (p == NULL) 4093 p = dict_get_string(dict, (char_u *)"encoding", FALSE); 4094 if (p != NULL) 4095 { 4096 ea.cmd = alloc(STRLEN(p) + 12); 4097 if (ea.cmd != NULL) 4098 { 4099 sprintf((char *)ea.cmd, "sbuf ++enc=%s", p); 4100 ea.force_enc = 11; 4101 tofree = ea.cmd; 4102 } 4103 } 4104 4105 p = dict_get_string(dict, (char_u *)"bad", FALSE); 4106 if (p != NULL) 4107 get_bad_opt(p, &ea); 4108 4109 if (dict_find(dict, (char_u *)"bin", -1) != NULL) 4110 ea.force_bin = FORCE_BIN; 4111 if (dict_find(dict, (char_u *)"binary", -1) != NULL) 4112 ea.force_bin = FORCE_BIN; 4113 if (dict_find(dict, (char_u *)"nobin", -1) != NULL) 4114 ea.force_bin = FORCE_NOBIN; 4115 if (dict_find(dict, (char_u *)"nobinary", -1) != NULL) 4116 ea.force_bin = FORCE_NOBIN; 4117 } 4118 4119 // open in new window, like ":split fname" 4120 if (ea.cmd == NULL) 4121 ea.cmd = (char_u *)"split"; 4122 ea.arg = fname; 4123 ea.cmdidx = CMD_split; 4124 ex_splitview(&ea); 4125 4126 vim_free(tofree); 4127 } 4128 4129 /* 4130 * Return TRUE if "func" starts with "pat" and "pat" isn't empty. 4131 */ 4132 static int 4133 is_permitted_term_api(char_u *func, char_u *pat) 4134 { 4135 return pat != NULL && *pat != NUL && STRNICMP(func, pat, STRLEN(pat)) == 0; 4136 } 4137 4138 /* 4139 * Handles a function call from the job running in a terminal. 4140 * "item" is the function name, "item->li_next" has the arguments. 4141 */ 4142 static void 4143 handle_call_command(term_T *term, channel_T *channel, listitem_T *item) 4144 { 4145 char_u *func; 4146 typval_T argvars[2]; 4147 typval_T rettv; 4148 funcexe_T funcexe; 4149 4150 if (item->li_next == NULL) 4151 { 4152 ch_log(channel, "Missing function arguments for call"); 4153 return; 4154 } 4155 func = tv_get_string(&item->li_tv); 4156 4157 if (!is_permitted_term_api(func, term->tl_api)) 4158 { 4159 ch_log(channel, "Unpermitted function: %s", func); 4160 return; 4161 } 4162 4163 argvars[0].v_type = VAR_NUMBER; 4164 argvars[0].vval.v_number = term->tl_buffer->b_fnum; 4165 argvars[1] = item->li_next->li_tv; 4166 CLEAR_FIELD(funcexe); 4167 funcexe.firstline = 1L; 4168 funcexe.lastline = 1L; 4169 funcexe.evaluate = TRUE; 4170 if (call_func(func, -1, &rettv, 2, argvars, &funcexe) == OK) 4171 { 4172 clear_tv(&rettv); 4173 ch_log(channel, "Function %s called", func); 4174 } 4175 else 4176 ch_log(channel, "Calling function %s failed", func); 4177 } 4178 4179 /* 4180 * Called by libvterm when it cannot recognize an OSC sequence. 4181 * We recognize a terminal API command. 4182 */ 4183 static int 4184 parse_osc(const char *command, size_t cmdlen, void *user) 4185 { 4186 term_T *term = (term_T *)user; 4187 js_read_T reader; 4188 typval_T tv; 4189 channel_T *channel = term->tl_job == NULL ? NULL 4190 : term->tl_job->jv_channel; 4191 4192 // We recognize only OSC 5 1 ; {command} 4193 if (cmdlen < 3 || STRNCMP(command, "51;", 3) != 0) 4194 return 0; // not handled 4195 4196 reader.js_buf = vim_strnsave((char_u *)command + 3, (int)(cmdlen - 3)); 4197 if (reader.js_buf == NULL) 4198 return 1; 4199 reader.js_fill = NULL; 4200 reader.js_used = 0; 4201 if (json_decode(&reader, &tv, 0) == OK 4202 && tv.v_type == VAR_LIST 4203 && tv.vval.v_list != NULL) 4204 { 4205 listitem_T *item = tv.vval.v_list->lv_first; 4206 4207 if (item == NULL) 4208 ch_log(channel, "Missing command"); 4209 else 4210 { 4211 char_u *cmd = tv_get_string(&item->li_tv); 4212 4213 // Make sure an invoked command doesn't delete the buffer (and the 4214 // terminal) under our fingers. 4215 ++term->tl_buffer->b_locked; 4216 4217 item = item->li_next; 4218 if (item == NULL) 4219 ch_log(channel, "Missing argument for %s", cmd); 4220 else if (STRCMP(cmd, "drop") == 0) 4221 handle_drop_command(item); 4222 else if (STRCMP(cmd, "call") == 0) 4223 handle_call_command(term, channel, item); 4224 else 4225 ch_log(channel, "Invalid command received: %s", cmd); 4226 --term->tl_buffer->b_locked; 4227 } 4228 } 4229 else 4230 ch_log(channel, "Invalid JSON received"); 4231 4232 vim_free(reader.js_buf); 4233 clear_tv(&tv); 4234 return 1; 4235 } 4236 4237 /* 4238 * Called by libvterm when it cannot recognize a CSI sequence. 4239 * We recognize the window position report. 4240 */ 4241 static int 4242 parse_csi( 4243 const char *leader UNUSED, 4244 const long args[], 4245 int argcount, 4246 const char *intermed UNUSED, 4247 char command, 4248 void *user) 4249 { 4250 term_T *term = (term_T *)user; 4251 char buf[100]; 4252 int len; 4253 int x = 0; 4254 int y = 0; 4255 win_T *wp; 4256 4257 // We recognize only CSI 13 t 4258 if (command != 't' || argcount != 1 || args[0] != 13) 4259 return 0; // not handled 4260 4261 // When getting the window position is not possible or it fails it results 4262 // in zero/zero. 4263 #if defined(FEAT_GUI) \ 4264 || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)) \ 4265 || defined(MSWIN) 4266 (void)ui_get_winpos(&x, &y, (varnumber_T)100); 4267 #endif 4268 4269 FOR_ALL_WINDOWS(wp) 4270 if (wp->w_buffer == term->tl_buffer) 4271 break; 4272 if (wp != NULL) 4273 { 4274 #ifdef FEAT_GUI 4275 if (gui.in_use) 4276 { 4277 x += wp->w_wincol * gui.char_width; 4278 y += W_WINROW(wp) * gui.char_height; 4279 } 4280 else 4281 #endif 4282 { 4283 // We roughly estimate the position of the terminal window inside 4284 // the Vim window by assuming a 10 x 7 character cell. 4285 x += wp->w_wincol * 7; 4286 y += W_WINROW(wp) * 10; 4287 } 4288 } 4289 4290 len = vim_snprintf(buf, 100, "\x1b[3;%d;%dt", x, y); 4291 channel_send(term->tl_job->jv_channel, get_tty_part(term), 4292 (char_u *)buf, len, NULL); 4293 return 1; 4294 } 4295 4296 static VTermParserCallbacks parser_fallbacks = { 4297 NULL, // text 4298 NULL, // control 4299 NULL, // escape 4300 parse_csi, // csi 4301 parse_osc, // osc 4302 NULL, // dcs 4303 NULL // resize 4304 }; 4305 4306 /* 4307 * Use Vim's allocation functions for vterm so profiling works. 4308 */ 4309 static void * 4310 vterm_malloc(size_t size, void *data UNUSED) 4311 { 4312 return alloc_clear(size); 4313 } 4314 4315 static void 4316 vterm_memfree(void *ptr, void *data UNUSED) 4317 { 4318 vim_free(ptr); 4319 } 4320 4321 static VTermAllocatorFunctions vterm_allocator = { 4322 &vterm_malloc, 4323 &vterm_memfree 4324 }; 4325 4326 /* 4327 * Create a new vterm and initialize it. 4328 * Return FAIL when out of memory. 4329 */ 4330 static int 4331 create_vterm(term_T *term, int rows, int cols) 4332 { 4333 VTerm *vterm; 4334 VTermScreen *screen; 4335 VTermState *state; 4336 VTermValue value; 4337 4338 vterm = vterm_new_with_allocator(rows, cols, &vterm_allocator, NULL); 4339 term->tl_vterm = vterm; 4340 if (vterm == NULL) 4341 return FAIL; 4342 4343 // Allocate screen and state here, so we can bail out if that fails. 4344 state = vterm_obtain_state(vterm); 4345 screen = vterm_obtain_screen(vterm); 4346 if (state == NULL || screen == NULL) 4347 { 4348 vterm_free(vterm); 4349 return FAIL; 4350 } 4351 4352 vterm_screen_set_callbacks(screen, &screen_callbacks, term); 4353 // TODO: depends on 'encoding'. 4354 vterm_set_utf8(vterm, 1); 4355 4356 init_default_colors(term, NULL); 4357 4358 vterm_state_set_default_colors( 4359 state, 4360 &term->tl_default_color.fg, 4361 &term->tl_default_color.bg); 4362 4363 if (t_colors < 16) 4364 // Less than 16 colors: assume that bold means using a bright color for 4365 // the foreground color. 4366 vterm_state_set_bold_highbright(vterm_obtain_state(vterm), 1); 4367 4368 // Required to initialize most things. 4369 vterm_screen_reset(screen, 1 /* hard */); 4370 4371 // Allow using alternate screen. 4372 vterm_screen_enable_altscreen(screen, 1); 4373 4374 // For unix do not use a blinking cursor. In an xterm this causes the 4375 // cursor to blink if it's blinking in the xterm. 4376 // For Windows we respect the system wide setting. 4377 #ifdef MSWIN 4378 if (GetCaretBlinkTime() == INFINITE) 4379 value.boolean = 0; 4380 else 4381 value.boolean = 1; 4382 #else 4383 value.boolean = 0; 4384 #endif 4385 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &value); 4386 vterm_state_set_unrecognised_fallbacks(state, &parser_fallbacks, term); 4387 4388 return OK; 4389 } 4390 4391 /* 4392 * Called when 'wincolor' was set. 4393 */ 4394 void 4395 term_update_colors(void) 4396 { 4397 term_T *term = curwin->w_buffer->b_term; 4398 4399 if (term->tl_vterm == NULL) 4400 return; 4401 init_default_colors(term, curwin); 4402 vterm_state_set_default_colors( 4403 vterm_obtain_state(term->tl_vterm), 4404 &term->tl_default_color.fg, 4405 &term->tl_default_color.bg); 4406 4407 redraw_later(NOT_VALID); 4408 } 4409 4410 /* 4411 * Return the text to show for the buffer name and status. 4412 */ 4413 char_u * 4414 term_get_status_text(term_T *term) 4415 { 4416 if (term->tl_status_text == NULL) 4417 { 4418 char_u *txt; 4419 size_t len; 4420 4421 if (term->tl_normal_mode) 4422 { 4423 if (term_job_running(term)) 4424 txt = (char_u *)_("Terminal"); 4425 else 4426 txt = (char_u *)_("Terminal-finished"); 4427 } 4428 else if (term->tl_title != NULL) 4429 txt = term->tl_title; 4430 else if (term_none_open(term)) 4431 txt = (char_u *)_("active"); 4432 else if (term_job_running(term)) 4433 txt = (char_u *)_("running"); 4434 else 4435 txt = (char_u *)_("finished"); 4436 len = 9 + STRLEN(term->tl_buffer->b_fname) + STRLEN(txt); 4437 term->tl_status_text = alloc(len); 4438 if (term->tl_status_text != NULL) 4439 vim_snprintf((char *)term->tl_status_text, len, "%s [%s]", 4440 term->tl_buffer->b_fname, txt); 4441 } 4442 return term->tl_status_text; 4443 } 4444 4445 /* 4446 * Mark references in jobs of terminals. 4447 */ 4448 int 4449 set_ref_in_term(int copyID) 4450 { 4451 int abort = FALSE; 4452 term_T *term; 4453 typval_T tv; 4454 4455 for (term = first_term; !abort && term != NULL; term = term->tl_next) 4456 if (term->tl_job != NULL) 4457 { 4458 tv.v_type = VAR_JOB; 4459 tv.vval.v_job = term->tl_job; 4460 abort = abort || set_ref_in_item(&tv, copyID, NULL, NULL); 4461 } 4462 return abort; 4463 } 4464 4465 /* 4466 * Get the buffer from the first argument in "argvars". 4467 * Returns NULL when the buffer is not for a terminal window and logs a message 4468 * with "where". 4469 */ 4470 static buf_T * 4471 term_get_buf(typval_T *argvars, char *where) 4472 { 4473 buf_T *buf; 4474 4475 (void)tv_get_number(&argvars[0]); // issue errmsg if type error 4476 ++emsg_off; 4477 buf = tv_get_buf(&argvars[0], FALSE); 4478 --emsg_off; 4479 if (buf == NULL || buf->b_term == NULL) 4480 { 4481 ch_log(NULL, "%s: invalid buffer argument", where); 4482 return NULL; 4483 } 4484 return buf; 4485 } 4486 4487 static int 4488 same_color(VTermColor *a, VTermColor *b) 4489 { 4490 return a->red == b->red 4491 && a->green == b->green 4492 && a->blue == b->blue 4493 && a->ansi_index == b->ansi_index; 4494 } 4495 4496 static void 4497 dump_term_color(FILE *fd, VTermColor *color) 4498 { 4499 fprintf(fd, "%02x%02x%02x%d", 4500 (int)color->red, (int)color->green, (int)color->blue, 4501 (int)color->ansi_index); 4502 } 4503 4504 /* 4505 * "term_dumpwrite(buf, filename, options)" function 4506 * 4507 * Each screen cell in full is: 4508 * |{characters}+{attributes}#{fg-color}{color-idx}#{bg-color}{color-idx} 4509 * {characters} is a space for an empty cell 4510 * For a double-width character "+" is changed to "*" and the next cell is 4511 * skipped. 4512 * {attributes} is the decimal value of HL_BOLD + HL_UNDERLINE, etc. 4513 * when "&" use the same as the previous cell. 4514 * {fg-color} is hex RGB, when "&" use the same as the previous cell. 4515 * {bg-color} is hex RGB, when "&" use the same as the previous cell. 4516 * {color-idx} is a number from 0 to 255 4517 * 4518 * Screen cell with same width, attributes and color as the previous one: 4519 * |{characters} 4520 * 4521 * To use the color of the previous cell, use "&" instead of {color}-{idx}. 4522 * 4523 * Repeating the previous screen cell: 4524 * @{count} 4525 */ 4526 void 4527 f_term_dumpwrite(typval_T *argvars, typval_T *rettv UNUSED) 4528 { 4529 buf_T *buf = term_get_buf(argvars, "term_dumpwrite()"); 4530 term_T *term; 4531 char_u *fname; 4532 int max_height = 0; 4533 int max_width = 0; 4534 stat_T st; 4535 FILE *fd; 4536 VTermPos pos; 4537 VTermScreen *screen; 4538 VTermScreenCell prev_cell; 4539 VTermState *state; 4540 VTermPos cursor_pos; 4541 4542 if (check_restricted() || check_secure()) 4543 return; 4544 if (buf == NULL) 4545 return; 4546 term = buf->b_term; 4547 if (term->tl_vterm == NULL) 4548 { 4549 emsg(_("E958: Job already finished")); 4550 return; 4551 } 4552 4553 if (argvars[2].v_type != VAR_UNKNOWN) 4554 { 4555 dict_T *d; 4556 4557 if (argvars[2].v_type != VAR_DICT) 4558 { 4559 emsg(_(e_dictreq)); 4560 return; 4561 } 4562 d = argvars[2].vval.v_dict; 4563 if (d != NULL) 4564 { 4565 max_height = dict_get_number(d, (char_u *)"rows"); 4566 max_width = dict_get_number(d, (char_u *)"columns"); 4567 } 4568 } 4569 4570 fname = tv_get_string_chk(&argvars[1]); 4571 if (fname == NULL) 4572 return; 4573 if (mch_stat((char *)fname, &st) >= 0) 4574 { 4575 semsg(_("E953: File exists: %s"), fname); 4576 return; 4577 } 4578 4579 if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL) 4580 { 4581 semsg(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname); 4582 return; 4583 } 4584 4585 CLEAR_FIELD(prev_cell); 4586 4587 screen = vterm_obtain_screen(term->tl_vterm); 4588 state = vterm_obtain_state(term->tl_vterm); 4589 vterm_state_get_cursorpos(state, &cursor_pos); 4590 4591 for (pos.row = 0; (max_height == 0 || pos.row < max_height) 4592 && pos.row < term->tl_rows; ++pos.row) 4593 { 4594 int repeat = 0; 4595 4596 for (pos.col = 0; (max_width == 0 || pos.col < max_width) 4597 && pos.col < term->tl_cols; ++pos.col) 4598 { 4599 VTermScreenCell cell; 4600 int same_attr; 4601 int same_chars = TRUE; 4602 int i; 4603 int is_cursor_pos = (pos.col == cursor_pos.col 4604 && pos.row == cursor_pos.row); 4605 4606 if (vterm_screen_get_cell(screen, pos, &cell) == 0) 4607 CLEAR_FIELD(cell); 4608 4609 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i) 4610 { 4611 int c = cell.chars[i]; 4612 int pc = prev_cell.chars[i]; 4613 4614 // For the first character NUL is the same as space. 4615 if (i == 0) 4616 { 4617 c = (c == NUL) ? ' ' : c; 4618 pc = (pc == NUL) ? ' ' : pc; 4619 } 4620 if (c != pc) 4621 same_chars = FALSE; 4622 if (c == NUL || pc == NUL) 4623 break; 4624 } 4625 same_attr = vtermAttr2hl(cell.attrs) 4626 == vtermAttr2hl(prev_cell.attrs) 4627 && same_color(&cell.fg, &prev_cell.fg) 4628 && same_color(&cell.bg, &prev_cell.bg); 4629 if (same_chars && cell.width == prev_cell.width && same_attr 4630 && !is_cursor_pos) 4631 { 4632 ++repeat; 4633 } 4634 else 4635 { 4636 if (repeat > 0) 4637 { 4638 fprintf(fd, "@%d", repeat); 4639 repeat = 0; 4640 } 4641 fputs(is_cursor_pos ? ">" : "|", fd); 4642 4643 if (cell.chars[0] == NUL) 4644 fputs(" ", fd); 4645 else 4646 { 4647 char_u charbuf[10]; 4648 int len; 4649 4650 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL 4651 && cell.chars[i] != NUL; ++i) 4652 { 4653 len = utf_char2bytes(cell.chars[i], charbuf); 4654 fwrite(charbuf, len, 1, fd); 4655 } 4656 } 4657 4658 // When only the characters differ we don't write anything, the 4659 // following "|", "@" or NL will indicate using the same 4660 // attributes. 4661 if (cell.width != prev_cell.width || !same_attr) 4662 { 4663 if (cell.width == 2) 4664 fputs("*", fd); 4665 else 4666 fputs("+", fd); 4667 4668 if (same_attr) 4669 { 4670 fputs("&", fd); 4671 } 4672 else 4673 { 4674 fprintf(fd, "%d", vtermAttr2hl(cell.attrs)); 4675 if (same_color(&cell.fg, &prev_cell.fg)) 4676 fputs("&", fd); 4677 else 4678 { 4679 fputs("#", fd); 4680 dump_term_color(fd, &cell.fg); 4681 } 4682 if (same_color(&cell.bg, &prev_cell.bg)) 4683 fputs("&", fd); 4684 else 4685 { 4686 fputs("#", fd); 4687 dump_term_color(fd, &cell.bg); 4688 } 4689 } 4690 } 4691 4692 prev_cell = cell; 4693 } 4694 4695 if (cell.width == 2) 4696 ++pos.col; 4697 } 4698 if (repeat > 0) 4699 fprintf(fd, "@%d", repeat); 4700 fputs("\n", fd); 4701 } 4702 4703 fclose(fd); 4704 } 4705 4706 /* 4707 * Called when a dump is corrupted. Put a breakpoint here when debugging. 4708 */ 4709 static void 4710 dump_is_corrupt(garray_T *gap) 4711 { 4712 ga_concat(gap, (char_u *)"CORRUPT"); 4713 } 4714 4715 static void 4716 append_cell(garray_T *gap, cellattr_T *cell) 4717 { 4718 if (ga_grow(gap, 1) == OK) 4719 { 4720 *(((cellattr_T *)gap->ga_data) + gap->ga_len) = *cell; 4721 ++gap->ga_len; 4722 } 4723 } 4724 4725 /* 4726 * Read the dump file from "fd" and append lines to the current buffer. 4727 * Return the cell width of the longest line. 4728 */ 4729 static int 4730 read_dump_file(FILE *fd, VTermPos *cursor_pos) 4731 { 4732 int c; 4733 garray_T ga_text; 4734 garray_T ga_cell; 4735 char_u *prev_char = NULL; 4736 int attr = 0; 4737 cellattr_T cell; 4738 cellattr_T empty_cell; 4739 term_T *term = curbuf->b_term; 4740 int max_cells = 0; 4741 int start_row = term->tl_scrollback.ga_len; 4742 4743 ga_init2(&ga_text, 1, 90); 4744 ga_init2(&ga_cell, sizeof(cellattr_T), 90); 4745 CLEAR_FIELD(cell); 4746 CLEAR_FIELD(empty_cell); 4747 cursor_pos->row = -1; 4748 cursor_pos->col = -1; 4749 4750 c = fgetc(fd); 4751 for (;;) 4752 { 4753 if (c == EOF) 4754 break; 4755 if (c == '\r') 4756 { 4757 // DOS line endings? Ignore. 4758 c = fgetc(fd); 4759 } 4760 else if (c == '\n') 4761 { 4762 // End of a line: append it to the buffer. 4763 if (ga_text.ga_data == NULL) 4764 dump_is_corrupt(&ga_text); 4765 if (ga_grow(&term->tl_scrollback, 1) == OK) 4766 { 4767 sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data 4768 + term->tl_scrollback.ga_len; 4769 4770 if (max_cells < ga_cell.ga_len) 4771 max_cells = ga_cell.ga_len; 4772 line->sb_cols = ga_cell.ga_len; 4773 line->sb_cells = ga_cell.ga_data; 4774 line->sb_fill_attr = term->tl_default_color; 4775 ++term->tl_scrollback.ga_len; 4776 ga_init(&ga_cell); 4777 4778 ga_append(&ga_text, NUL); 4779 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data, 4780 ga_text.ga_len, FALSE); 4781 } 4782 else 4783 ga_clear(&ga_cell); 4784 ga_text.ga_len = 0; 4785 4786 c = fgetc(fd); 4787 } 4788 else if (c == '|' || c == '>') 4789 { 4790 int prev_len = ga_text.ga_len; 4791 4792 if (c == '>') 4793 { 4794 if (cursor_pos->row != -1) 4795 dump_is_corrupt(&ga_text); // duplicate cursor 4796 cursor_pos->row = term->tl_scrollback.ga_len - start_row; 4797 cursor_pos->col = ga_cell.ga_len; 4798 } 4799 4800 // normal character(s) followed by "+", "*", "|", "@" or NL 4801 c = fgetc(fd); 4802 if (c != EOF) 4803 ga_append(&ga_text, c); 4804 for (;;) 4805 { 4806 c = fgetc(fd); 4807 if (c == '+' || c == '*' || c == '|' || c == '>' || c == '@' 4808 || c == EOF || c == '\n') 4809 break; 4810 ga_append(&ga_text, c); 4811 } 4812 4813 // save the character for repeating it 4814 vim_free(prev_char); 4815 if (ga_text.ga_data != NULL) 4816 prev_char = vim_strnsave(((char_u *)ga_text.ga_data) + prev_len, 4817 ga_text.ga_len - prev_len); 4818 4819 if (c == '@' || c == '|' || c == '>' || c == '\n') 4820 { 4821 // use all attributes from previous cell 4822 } 4823 else if (c == '+' || c == '*') 4824 { 4825 int is_bg; 4826 4827 cell.width = c == '+' ? 1 : 2; 4828 4829 c = fgetc(fd); 4830 if (c == '&') 4831 { 4832 // use same attr as previous cell 4833 c = fgetc(fd); 4834 } 4835 else if (isdigit(c)) 4836 { 4837 // get the decimal attribute 4838 attr = 0; 4839 while (isdigit(c)) 4840 { 4841 attr = attr * 10 + (c - '0'); 4842 c = fgetc(fd); 4843 } 4844 hl2vtermAttr(attr, &cell); 4845 4846 // is_bg == 0: fg, is_bg == 1: bg 4847 for (is_bg = 0; is_bg <= 1; ++is_bg) 4848 { 4849 if (c == '&') 4850 { 4851 // use same color as previous cell 4852 c = fgetc(fd); 4853 } 4854 else if (c == '#') 4855 { 4856 int red, green, blue, index = 0; 4857 4858 c = fgetc(fd); 4859 red = hex2nr(c); 4860 c = fgetc(fd); 4861 red = (red << 4) + hex2nr(c); 4862 c = fgetc(fd); 4863 green = hex2nr(c); 4864 c = fgetc(fd); 4865 green = (green << 4) + hex2nr(c); 4866 c = fgetc(fd); 4867 blue = hex2nr(c); 4868 c = fgetc(fd); 4869 blue = (blue << 4) + hex2nr(c); 4870 c = fgetc(fd); 4871 if (!isdigit(c)) 4872 dump_is_corrupt(&ga_text); 4873 while (isdigit(c)) 4874 { 4875 index = index * 10 + (c - '0'); 4876 c = fgetc(fd); 4877 } 4878 4879 if (is_bg) 4880 { 4881 cell.bg.red = red; 4882 cell.bg.green = green; 4883 cell.bg.blue = blue; 4884 cell.bg.ansi_index = index; 4885 } 4886 else 4887 { 4888 cell.fg.red = red; 4889 cell.fg.green = green; 4890 cell.fg.blue = blue; 4891 cell.fg.ansi_index = index; 4892 } 4893 } 4894 else 4895 dump_is_corrupt(&ga_text); 4896 } 4897 } 4898 else 4899 dump_is_corrupt(&ga_text); 4900 } 4901 else 4902 dump_is_corrupt(&ga_text); 4903 4904 append_cell(&ga_cell, &cell); 4905 if (cell.width == 2) 4906 append_cell(&ga_cell, &empty_cell); 4907 } 4908 else if (c == '@') 4909 { 4910 if (prev_char == NULL) 4911 dump_is_corrupt(&ga_text); 4912 else 4913 { 4914 int count = 0; 4915 4916 // repeat previous character, get the count 4917 for (;;) 4918 { 4919 c = fgetc(fd); 4920 if (!isdigit(c)) 4921 break; 4922 count = count * 10 + (c - '0'); 4923 } 4924 4925 while (count-- > 0) 4926 { 4927 ga_concat(&ga_text, prev_char); 4928 append_cell(&ga_cell, &cell); 4929 } 4930 } 4931 } 4932 else 4933 { 4934 dump_is_corrupt(&ga_text); 4935 c = fgetc(fd); 4936 } 4937 } 4938 4939 if (ga_text.ga_len > 0) 4940 { 4941 // trailing characters after last NL 4942 dump_is_corrupt(&ga_text); 4943 ga_append(&ga_text, NUL); 4944 ml_append(curbuf->b_ml.ml_line_count, ga_text.ga_data, 4945 ga_text.ga_len, FALSE); 4946 } 4947 4948 ga_clear(&ga_text); 4949 ga_clear(&ga_cell); 4950 vim_free(prev_char); 4951 4952 return max_cells; 4953 } 4954 4955 /* 4956 * Return an allocated string with at least "text_width" "=" characters and 4957 * "fname" inserted in the middle. 4958 */ 4959 static char_u * 4960 get_separator(int text_width, char_u *fname) 4961 { 4962 int width = MAX(text_width, curwin->w_width); 4963 char_u *textline; 4964 int fname_size; 4965 char_u *p = fname; 4966 int i; 4967 size_t off; 4968 4969 textline = alloc(width + (int)STRLEN(fname) + 1); 4970 if (textline == NULL) 4971 return NULL; 4972 4973 fname_size = vim_strsize(fname); 4974 if (fname_size < width - 8) 4975 { 4976 // enough room, don't use the full window width 4977 width = MAX(text_width, fname_size + 8); 4978 } 4979 else if (fname_size > width - 8) 4980 { 4981 // full name doesn't fit, use only the tail 4982 p = gettail(fname); 4983 fname_size = vim_strsize(p); 4984 } 4985 // skip characters until the name fits 4986 while (fname_size > width - 8) 4987 { 4988 p += (*mb_ptr2len)(p); 4989 fname_size = vim_strsize(p); 4990 } 4991 4992 for (i = 0; i < (width - fname_size) / 2 - 1; ++i) 4993 textline[i] = '='; 4994 textline[i++] = ' '; 4995 4996 STRCPY(textline + i, p); 4997 off = STRLEN(textline); 4998 textline[off] = ' '; 4999 for (i = 1; i < (width - fname_size) / 2; ++i) 5000 textline[off + i] = '='; 5001 textline[off + i] = NUL; 5002 5003 return textline; 5004 } 5005 5006 /* 5007 * Common for "term_dumpdiff()" and "term_dumpload()". 5008 */ 5009 static void 5010 term_load_dump(typval_T *argvars, typval_T *rettv, int do_diff) 5011 { 5012 jobopt_T opt; 5013 buf_T *buf = NULL; 5014 char_u buf1[NUMBUFLEN]; 5015 char_u buf2[NUMBUFLEN]; 5016 char_u *fname1; 5017 char_u *fname2 = NULL; 5018 char_u *fname_tofree = NULL; 5019 FILE *fd1; 5020 FILE *fd2 = NULL; 5021 char_u *textline = NULL; 5022 5023 // First open the files. If this fails bail out. 5024 fname1 = tv_get_string_buf_chk(&argvars[0], buf1); 5025 if (do_diff) 5026 fname2 = tv_get_string_buf_chk(&argvars[1], buf2); 5027 if (fname1 == NULL || (do_diff && fname2 == NULL)) 5028 { 5029 emsg(_(e_invarg)); 5030 return; 5031 } 5032 fd1 = mch_fopen((char *)fname1, READBIN); 5033 if (fd1 == NULL) 5034 { 5035 semsg(_(e_notread), fname1); 5036 return; 5037 } 5038 if (do_diff) 5039 { 5040 fd2 = mch_fopen((char *)fname2, READBIN); 5041 if (fd2 == NULL) 5042 { 5043 fclose(fd1); 5044 semsg(_(e_notread), fname2); 5045 return; 5046 } 5047 } 5048 5049 init_job_options(&opt); 5050 if (argvars[do_diff ? 2 : 1].v_type != VAR_UNKNOWN 5051 && get_job_options(&argvars[do_diff ? 2 : 1], &opt, 0, 5052 JO2_TERM_NAME + JO2_TERM_COLS + JO2_TERM_ROWS 5053 + JO2_VERTICAL + JO2_CURWIN + JO2_NORESTORE) == FAIL) 5054 goto theend; 5055 5056 if (opt.jo_term_name == NULL) 5057 { 5058 size_t len = STRLEN(fname1) + 12; 5059 5060 fname_tofree = alloc(len); 5061 if (fname_tofree != NULL) 5062 { 5063 vim_snprintf((char *)fname_tofree, len, "dump diff %s", fname1); 5064 opt.jo_term_name = fname_tofree; 5065 } 5066 } 5067 5068 if (opt.jo_bufnr_buf != NULL) 5069 { 5070 win_T *wp = buf_jump_open_win(opt.jo_bufnr_buf); 5071 5072 // With "bufnr" argument: enter the window with this buffer and make it 5073 // empty. 5074 if (wp == NULL) 5075 semsg(_(e_invarg2), "bufnr"); 5076 else 5077 { 5078 buf = curbuf; 5079 while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) 5080 ml_delete((linenr_T)1, FALSE); 5081 free_scrollback(curbuf->b_term); 5082 redraw_later(NOT_VALID); 5083 } 5084 } 5085 else 5086 // Create a new terminal window. 5087 buf = term_start(&argvars[0], NULL, &opt, TERM_START_NOJOB); 5088 5089 if (buf != NULL && buf->b_term != NULL) 5090 { 5091 int i; 5092 linenr_T bot_lnum; 5093 linenr_T lnum; 5094 term_T *term = buf->b_term; 5095 int width; 5096 int width2; 5097 VTermPos cursor_pos1; 5098 VTermPos cursor_pos2; 5099 5100 init_default_colors(term, NULL); 5101 5102 rettv->vval.v_number = buf->b_fnum; 5103 5104 // read the files, fill the buffer with the diff 5105 width = read_dump_file(fd1, &cursor_pos1); 5106 5107 // position the cursor 5108 if (cursor_pos1.row >= 0) 5109 { 5110 curwin->w_cursor.lnum = cursor_pos1.row + 1; 5111 coladvance(cursor_pos1.col); 5112 } 5113 5114 // Delete the empty line that was in the empty buffer. 5115 ml_delete(1, FALSE); 5116 5117 // For term_dumpload() we are done here. 5118 if (!do_diff) 5119 goto theend; 5120 5121 term->tl_top_diff_rows = curbuf->b_ml.ml_line_count; 5122 5123 textline = get_separator(width, fname1); 5124 if (textline == NULL) 5125 goto theend; 5126 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK) 5127 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE); 5128 vim_free(textline); 5129 5130 textline = get_separator(width, fname2); 5131 if (textline == NULL) 5132 goto theend; 5133 if (add_empty_scrollback(term, &term->tl_default_color, 0) == OK) 5134 ml_append(curbuf->b_ml.ml_line_count, textline, 0, FALSE); 5135 textline[width] = NUL; 5136 5137 bot_lnum = curbuf->b_ml.ml_line_count; 5138 width2 = read_dump_file(fd2, &cursor_pos2); 5139 if (width2 > width) 5140 { 5141 vim_free(textline); 5142 textline = alloc(width2 + 1); 5143 if (textline == NULL) 5144 goto theend; 5145 width = width2; 5146 textline[width] = NUL; 5147 } 5148 term->tl_bot_diff_rows = curbuf->b_ml.ml_line_count - bot_lnum; 5149 5150 for (lnum = 1; lnum <= term->tl_top_diff_rows; ++lnum) 5151 { 5152 if (lnum + bot_lnum > curbuf->b_ml.ml_line_count) 5153 { 5154 // bottom part has fewer rows, fill with "-" 5155 for (i = 0; i < width; ++i) 5156 textline[i] = '-'; 5157 } 5158 else 5159 { 5160 char_u *line1; 5161 char_u *line2; 5162 char_u *p1; 5163 char_u *p2; 5164 int col; 5165 sb_line_T *sb_line = (sb_line_T *)term->tl_scrollback.ga_data; 5166 cellattr_T *cellattr1 = (sb_line + lnum - 1)->sb_cells; 5167 cellattr_T *cellattr2 = (sb_line + lnum + bot_lnum - 1) 5168 ->sb_cells; 5169 5170 // Make a copy, getting the second line will invalidate it. 5171 line1 = vim_strsave(ml_get(lnum)); 5172 if (line1 == NULL) 5173 break; 5174 p1 = line1; 5175 5176 line2 = ml_get(lnum + bot_lnum); 5177 p2 = line2; 5178 for (col = 0; col < width && *p1 != NUL && *p2 != NUL; ++col) 5179 { 5180 int len1 = utfc_ptr2len(p1); 5181 int len2 = utfc_ptr2len(p2); 5182 5183 textline[col] = ' '; 5184 if (len1 != len2 || STRNCMP(p1, p2, len1) != 0) 5185 // text differs 5186 textline[col] = 'X'; 5187 else if (lnum == cursor_pos1.row + 1 5188 && col == cursor_pos1.col 5189 && (cursor_pos1.row != cursor_pos2.row 5190 || cursor_pos1.col != cursor_pos2.col)) 5191 // cursor in first but not in second 5192 textline[col] = '>'; 5193 else if (lnum == cursor_pos2.row + 1 5194 && col == cursor_pos2.col 5195 && (cursor_pos1.row != cursor_pos2.row 5196 || cursor_pos1.col != cursor_pos2.col)) 5197 // cursor in second but not in first 5198 textline[col] = '<'; 5199 else if (cellattr1 != NULL && cellattr2 != NULL) 5200 { 5201 if ((cellattr1 + col)->width 5202 != (cellattr2 + col)->width) 5203 textline[col] = 'w'; 5204 else if (!same_color(&(cellattr1 + col)->fg, 5205 &(cellattr2 + col)->fg)) 5206 textline[col] = 'f'; 5207 else if (!same_color(&(cellattr1 + col)->bg, 5208 &(cellattr2 + col)->bg)) 5209 textline[col] = 'b'; 5210 else if (vtermAttr2hl((cellattr1 + col)->attrs) 5211 != vtermAttr2hl(((cellattr2 + col)->attrs))) 5212 textline[col] = 'a'; 5213 } 5214 p1 += len1; 5215 p2 += len2; 5216 // TODO: handle different width 5217 } 5218 5219 while (col < width) 5220 { 5221 if (*p1 == NUL && *p2 == NUL) 5222 textline[col] = '?'; 5223 else if (*p1 == NUL) 5224 { 5225 textline[col] = '+'; 5226 p2 += utfc_ptr2len(p2); 5227 } 5228 else 5229 { 5230 textline[col] = '-'; 5231 p1 += utfc_ptr2len(p1); 5232 } 5233 ++col; 5234 } 5235 5236 vim_free(line1); 5237 } 5238 if (add_empty_scrollback(term, &term->tl_default_color, 5239 term->tl_top_diff_rows) == OK) 5240 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE); 5241 ++bot_lnum; 5242 } 5243 5244 while (lnum + bot_lnum <= curbuf->b_ml.ml_line_count) 5245 { 5246 // bottom part has more rows, fill with "+" 5247 for (i = 0; i < width; ++i) 5248 textline[i] = '+'; 5249 if (add_empty_scrollback(term, &term->tl_default_color, 5250 term->tl_top_diff_rows) == OK) 5251 ml_append(term->tl_top_diff_rows + lnum, textline, 0, FALSE); 5252 ++lnum; 5253 ++bot_lnum; 5254 } 5255 5256 term->tl_cols = width; 5257 5258 // looks better without wrapping 5259 curwin->w_p_wrap = 0; 5260 } 5261 5262 theend: 5263 vim_free(textline); 5264 vim_free(fname_tofree); 5265 fclose(fd1); 5266 if (fd2 != NULL) 5267 fclose(fd2); 5268 } 5269 5270 /* 5271 * If the current buffer shows the output of term_dumpdiff(), swap the top and 5272 * bottom files. 5273 * Return FAIL when this is not possible. 5274 */ 5275 int 5276 term_swap_diff() 5277 { 5278 term_T *term = curbuf->b_term; 5279 linenr_T line_count; 5280 linenr_T top_rows; 5281 linenr_T bot_rows; 5282 linenr_T bot_start; 5283 linenr_T lnum; 5284 char_u *p; 5285 sb_line_T *sb_line; 5286 5287 if (term == NULL 5288 || !term_is_finished(curbuf) 5289 || term->tl_top_diff_rows == 0 5290 || term->tl_scrollback.ga_len == 0) 5291 return FAIL; 5292 5293 line_count = curbuf->b_ml.ml_line_count; 5294 top_rows = term->tl_top_diff_rows; 5295 bot_rows = term->tl_bot_diff_rows; 5296 bot_start = line_count - bot_rows; 5297 sb_line = (sb_line_T *)term->tl_scrollback.ga_data; 5298 5299 // move lines from top to above the bottom part 5300 for (lnum = 1; lnum <= top_rows; ++lnum) 5301 { 5302 p = vim_strsave(ml_get(1)); 5303 if (p == NULL) 5304 return OK; 5305 ml_append(bot_start, p, 0, FALSE); 5306 ml_delete(1, FALSE); 5307 vim_free(p); 5308 } 5309 5310 // move lines from bottom to the top 5311 for (lnum = 1; lnum <= bot_rows; ++lnum) 5312 { 5313 p = vim_strsave(ml_get(bot_start + lnum)); 5314 if (p == NULL) 5315 return OK; 5316 ml_delete(bot_start + lnum, FALSE); 5317 ml_append(lnum - 1, p, 0, FALSE); 5318 vim_free(p); 5319 } 5320 5321 // move top title to bottom 5322 p = vim_strsave(ml_get(bot_rows + 1)); 5323 if (p == NULL) 5324 return OK; 5325 ml_append(line_count - top_rows - 1, p, 0, FALSE); 5326 ml_delete(bot_rows + 1, FALSE); 5327 vim_free(p); 5328 5329 // move bottom title to top 5330 p = vim_strsave(ml_get(line_count - top_rows)); 5331 if (p == NULL) 5332 return OK; 5333 ml_delete(line_count - top_rows, FALSE); 5334 ml_append(bot_rows, p, 0, FALSE); 5335 vim_free(p); 5336 5337 if (top_rows == bot_rows) 5338 { 5339 // rows counts are equal, can swap cell properties 5340 for (lnum = 0; lnum < top_rows; ++lnum) 5341 { 5342 sb_line_T temp; 5343 5344 temp = *(sb_line + lnum); 5345 *(sb_line + lnum) = *(sb_line + bot_start + lnum); 5346 *(sb_line + bot_start + lnum) = temp; 5347 } 5348 } 5349 else 5350 { 5351 size_t size = sizeof(sb_line_T) * term->tl_scrollback.ga_len; 5352 sb_line_T *temp = alloc(size); 5353 5354 // need to copy cell properties into temp memory 5355 if (temp != NULL) 5356 { 5357 mch_memmove(temp, term->tl_scrollback.ga_data, size); 5358 mch_memmove(term->tl_scrollback.ga_data, 5359 temp + bot_start, 5360 sizeof(sb_line_T) * bot_rows); 5361 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data + bot_rows, 5362 temp + top_rows, 5363 sizeof(sb_line_T) * (line_count - top_rows - bot_rows)); 5364 mch_memmove((sb_line_T *)term->tl_scrollback.ga_data 5365 + line_count - top_rows, 5366 temp, 5367 sizeof(sb_line_T) * top_rows); 5368 vim_free(temp); 5369 } 5370 } 5371 5372 term->tl_top_diff_rows = bot_rows; 5373 term->tl_bot_diff_rows = top_rows; 5374 5375 update_screen(NOT_VALID); 5376 return OK; 5377 } 5378 5379 /* 5380 * "term_dumpdiff(filename, filename, options)" function 5381 */ 5382 void 5383 f_term_dumpdiff(typval_T *argvars, typval_T *rettv) 5384 { 5385 term_load_dump(argvars, rettv, TRUE); 5386 } 5387 5388 /* 5389 * "term_dumpload(filename, options)" function 5390 */ 5391 void 5392 f_term_dumpload(typval_T *argvars, typval_T *rettv) 5393 { 5394 term_load_dump(argvars, rettv, FALSE); 5395 } 5396 5397 /* 5398 * "term_getaltscreen(buf)" function 5399 */ 5400 void 5401 f_term_getaltscreen(typval_T *argvars, typval_T *rettv) 5402 { 5403 buf_T *buf = term_get_buf(argvars, "term_getaltscreen()"); 5404 5405 if (buf == NULL) 5406 return; 5407 rettv->vval.v_number = buf->b_term->tl_using_altscreen; 5408 } 5409 5410 /* 5411 * "term_getattr(attr, name)" function 5412 */ 5413 void 5414 f_term_getattr(typval_T *argvars, typval_T *rettv) 5415 { 5416 int attr; 5417 size_t i; 5418 char_u *name; 5419 5420 static struct { 5421 char *name; 5422 int attr; 5423 } attrs[] = { 5424 {"bold", HL_BOLD}, 5425 {"italic", HL_ITALIC}, 5426 {"underline", HL_UNDERLINE}, 5427 {"strike", HL_STRIKETHROUGH}, 5428 {"reverse", HL_INVERSE}, 5429 }; 5430 5431 attr = tv_get_number(&argvars[0]); 5432 name = tv_get_string_chk(&argvars[1]); 5433 if (name == NULL) 5434 return; 5435 5436 if (attr > HL_ALL) 5437 attr = syn_attr2attr(attr); 5438 for (i = 0; i < sizeof(attrs)/sizeof(attrs[0]); ++i) 5439 if (STRCMP(name, attrs[i].name) == 0) 5440 { 5441 rettv->vval.v_number = (attr & attrs[i].attr) != 0 ? 1 : 0; 5442 break; 5443 } 5444 } 5445 5446 /* 5447 * "term_getcursor(buf)" function 5448 */ 5449 void 5450 f_term_getcursor(typval_T *argvars, typval_T *rettv) 5451 { 5452 buf_T *buf = term_get_buf(argvars, "term_getcursor()"); 5453 term_T *term; 5454 list_T *l; 5455 dict_T *d; 5456 5457 if (rettv_list_alloc(rettv) == FAIL) 5458 return; 5459 if (buf == NULL) 5460 return; 5461 term = buf->b_term; 5462 5463 l = rettv->vval.v_list; 5464 list_append_number(l, term->tl_cursor_pos.row + 1); 5465 list_append_number(l, term->tl_cursor_pos.col + 1); 5466 5467 d = dict_alloc(); 5468 if (d != NULL) 5469 { 5470 dict_add_number(d, "visible", term->tl_cursor_visible); 5471 dict_add_number(d, "blink", blink_state_is_inverted() 5472 ? !term->tl_cursor_blink : term->tl_cursor_blink); 5473 dict_add_number(d, "shape", term->tl_cursor_shape); 5474 dict_add_string(d, "color", cursor_color_get(term->tl_cursor_color)); 5475 list_append_dict(l, d); 5476 } 5477 } 5478 5479 /* 5480 * "term_getjob(buf)" function 5481 */ 5482 void 5483 f_term_getjob(typval_T *argvars, typval_T *rettv) 5484 { 5485 buf_T *buf = term_get_buf(argvars, "term_getjob()"); 5486 5487 if (buf == NULL) 5488 { 5489 rettv->v_type = VAR_SPECIAL; 5490 rettv->vval.v_number = VVAL_NULL; 5491 return; 5492 } 5493 5494 rettv->v_type = VAR_JOB; 5495 rettv->vval.v_job = buf->b_term->tl_job; 5496 if (rettv->vval.v_job != NULL) 5497 ++rettv->vval.v_job->jv_refcount; 5498 } 5499 5500 static int 5501 get_row_number(typval_T *tv, term_T *term) 5502 { 5503 if (tv->v_type == VAR_STRING 5504 && tv->vval.v_string != NULL 5505 && STRCMP(tv->vval.v_string, ".") == 0) 5506 return term->tl_cursor_pos.row; 5507 return (int)tv_get_number(tv) - 1; 5508 } 5509 5510 /* 5511 * "term_getline(buf, row)" function 5512 */ 5513 void 5514 f_term_getline(typval_T *argvars, typval_T *rettv) 5515 { 5516 buf_T *buf = term_get_buf(argvars, "term_getline()"); 5517 term_T *term; 5518 int row; 5519 5520 rettv->v_type = VAR_STRING; 5521 if (buf == NULL) 5522 return; 5523 term = buf->b_term; 5524 row = get_row_number(&argvars[1], term); 5525 5526 if (term->tl_vterm == NULL) 5527 { 5528 linenr_T lnum = row + term->tl_scrollback_scrolled + 1; 5529 5530 // vterm is finished, get the text from the buffer 5531 if (lnum > 0 && lnum <= buf->b_ml.ml_line_count) 5532 rettv->vval.v_string = vim_strsave(ml_get_buf(buf, lnum, FALSE)); 5533 } 5534 else 5535 { 5536 VTermScreen *screen = vterm_obtain_screen(term->tl_vterm); 5537 VTermRect rect; 5538 int len; 5539 char_u *p; 5540 5541 if (row < 0 || row >= term->tl_rows) 5542 return; 5543 len = term->tl_cols * MB_MAXBYTES + 1; 5544 p = alloc(len); 5545 if (p == NULL) 5546 return; 5547 rettv->vval.v_string = p; 5548 5549 rect.start_col = 0; 5550 rect.end_col = term->tl_cols; 5551 rect.start_row = row; 5552 rect.end_row = row + 1; 5553 p[vterm_screen_get_text(screen, (char *)p, len, rect)] = NUL; 5554 } 5555 } 5556 5557 /* 5558 * "term_getscrolled(buf)" function 5559 */ 5560 void 5561 f_term_getscrolled(typval_T *argvars, typval_T *rettv) 5562 { 5563 buf_T *buf = term_get_buf(argvars, "term_getscrolled()"); 5564 5565 if (buf == NULL) 5566 return; 5567 rettv->vval.v_number = buf->b_term->tl_scrollback_scrolled; 5568 } 5569 5570 /* 5571 * "term_getsize(buf)" function 5572 */ 5573 void 5574 f_term_getsize(typval_T *argvars, typval_T *rettv) 5575 { 5576 buf_T *buf = term_get_buf(argvars, "term_getsize()"); 5577 list_T *l; 5578 5579 if (rettv_list_alloc(rettv) == FAIL) 5580 return; 5581 if (buf == NULL) 5582 return; 5583 5584 l = rettv->vval.v_list; 5585 list_append_number(l, buf->b_term->tl_rows); 5586 list_append_number(l, buf->b_term->tl_cols); 5587 } 5588 5589 /* 5590 * "term_setsize(buf, rows, cols)" function 5591 */ 5592 void 5593 f_term_setsize(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 5594 { 5595 buf_T *buf = term_get_buf(argvars, "term_setsize()"); 5596 term_T *term; 5597 varnumber_T rows, cols; 5598 5599 if (buf == NULL) 5600 { 5601 emsg(_("E955: Not a terminal buffer")); 5602 return; 5603 } 5604 if (buf->b_term->tl_vterm == NULL) 5605 return; 5606 term = buf->b_term; 5607 rows = tv_get_number(&argvars[1]); 5608 rows = rows <= 0 ? term->tl_rows : rows; 5609 cols = tv_get_number(&argvars[2]); 5610 cols = cols <= 0 ? term->tl_cols : cols; 5611 vterm_set_size(term->tl_vterm, rows, cols); 5612 // handle_resize() will resize the windows 5613 5614 // Get and remember the size we ended up with. Update the pty. 5615 vterm_get_size(term->tl_vterm, &term->tl_rows, &term->tl_cols); 5616 term_report_winsize(term, term->tl_rows, term->tl_cols); 5617 } 5618 5619 /* 5620 * "term_getstatus(buf)" function 5621 */ 5622 void 5623 f_term_getstatus(typval_T *argvars, typval_T *rettv) 5624 { 5625 buf_T *buf = term_get_buf(argvars, "term_getstatus()"); 5626 term_T *term; 5627 char_u val[100]; 5628 5629 rettv->v_type = VAR_STRING; 5630 if (buf == NULL) 5631 return; 5632 term = buf->b_term; 5633 5634 if (term_job_running(term)) 5635 STRCPY(val, "running"); 5636 else 5637 STRCPY(val, "finished"); 5638 if (term->tl_normal_mode) 5639 STRCAT(val, ",normal"); 5640 rettv->vval.v_string = vim_strsave(val); 5641 } 5642 5643 /* 5644 * "term_gettitle(buf)" function 5645 */ 5646 void 5647 f_term_gettitle(typval_T *argvars, typval_T *rettv) 5648 { 5649 buf_T *buf = term_get_buf(argvars, "term_gettitle()"); 5650 5651 rettv->v_type = VAR_STRING; 5652 if (buf == NULL) 5653 return; 5654 5655 if (buf->b_term->tl_title != NULL) 5656 rettv->vval.v_string = vim_strsave(buf->b_term->tl_title); 5657 } 5658 5659 /* 5660 * "term_gettty(buf)" function 5661 */ 5662 void 5663 f_term_gettty(typval_T *argvars, typval_T *rettv) 5664 { 5665 buf_T *buf = term_get_buf(argvars, "term_gettty()"); 5666 char_u *p = NULL; 5667 int num = 0; 5668 5669 rettv->v_type = VAR_STRING; 5670 if (buf == NULL) 5671 return; 5672 if (argvars[1].v_type != VAR_UNKNOWN) 5673 num = tv_get_number(&argvars[1]); 5674 5675 switch (num) 5676 { 5677 case 0: 5678 if (buf->b_term->tl_job != NULL) 5679 p = buf->b_term->tl_job->jv_tty_out; 5680 break; 5681 case 1: 5682 if (buf->b_term->tl_job != NULL) 5683 p = buf->b_term->tl_job->jv_tty_in; 5684 break; 5685 default: 5686 semsg(_(e_invarg2), tv_get_string(&argvars[1])); 5687 return; 5688 } 5689 if (p != NULL) 5690 rettv->vval.v_string = vim_strsave(p); 5691 } 5692 5693 /* 5694 * "term_list()" function 5695 */ 5696 void 5697 f_term_list(typval_T *argvars UNUSED, typval_T *rettv) 5698 { 5699 term_T *tp; 5700 list_T *l; 5701 5702 if (rettv_list_alloc(rettv) == FAIL || first_term == NULL) 5703 return; 5704 5705 l = rettv->vval.v_list; 5706 FOR_ALL_TERMS(tp) 5707 if (tp != NULL && tp->tl_buffer != NULL) 5708 if (list_append_number(l, 5709 (varnumber_T)tp->tl_buffer->b_fnum) == FAIL) 5710 return; 5711 } 5712 5713 /* 5714 * "term_scrape(buf, row)" function 5715 */ 5716 void 5717 f_term_scrape(typval_T *argvars, typval_T *rettv) 5718 { 5719 buf_T *buf = term_get_buf(argvars, "term_scrape()"); 5720 VTermScreen *screen = NULL; 5721 VTermPos pos; 5722 list_T *l; 5723 term_T *term; 5724 char_u *p; 5725 sb_line_T *line; 5726 5727 if (rettv_list_alloc(rettv) == FAIL) 5728 return; 5729 if (buf == NULL) 5730 return; 5731 term = buf->b_term; 5732 5733 l = rettv->vval.v_list; 5734 pos.row = get_row_number(&argvars[1], term); 5735 5736 if (term->tl_vterm != NULL) 5737 { 5738 screen = vterm_obtain_screen(term->tl_vterm); 5739 if (screen == NULL) // can't really happen 5740 return; 5741 p = NULL; 5742 line = NULL; 5743 } 5744 else 5745 { 5746 linenr_T lnum = pos.row + term->tl_scrollback_scrolled; 5747 5748 if (lnum < 0 || lnum >= term->tl_scrollback.ga_len) 5749 return; 5750 p = ml_get_buf(buf, lnum + 1, FALSE); 5751 line = (sb_line_T *)term->tl_scrollback.ga_data + lnum; 5752 } 5753 5754 for (pos.col = 0; pos.col < term->tl_cols; ) 5755 { 5756 dict_T *dcell; 5757 int width; 5758 VTermScreenCellAttrs attrs; 5759 VTermColor fg, bg; 5760 char_u rgb[8]; 5761 char_u mbs[MB_MAXBYTES * VTERM_MAX_CHARS_PER_CELL + 1]; 5762 int off = 0; 5763 int i; 5764 5765 if (screen == NULL) 5766 { 5767 cellattr_T *cellattr; 5768 int len; 5769 5770 // vterm has finished, get the cell from scrollback 5771 if (pos.col >= line->sb_cols) 5772 break; 5773 cellattr = line->sb_cells + pos.col; 5774 width = cellattr->width; 5775 attrs = cellattr->attrs; 5776 fg = cellattr->fg; 5777 bg = cellattr->bg; 5778 len = mb_ptr2len(p); 5779 mch_memmove(mbs, p, len); 5780 mbs[len] = NUL; 5781 p += len; 5782 } 5783 else 5784 { 5785 VTermScreenCell cell; 5786 if (vterm_screen_get_cell(screen, pos, &cell) == 0) 5787 break; 5788 for (i = 0; i < VTERM_MAX_CHARS_PER_CELL; ++i) 5789 { 5790 if (cell.chars[i] == 0) 5791 break; 5792 off += (*utf_char2bytes)((int)cell.chars[i], mbs + off); 5793 } 5794 mbs[off] = NUL; 5795 width = cell.width; 5796 attrs = cell.attrs; 5797 fg = cell.fg; 5798 bg = cell.bg; 5799 } 5800 dcell = dict_alloc(); 5801 if (dcell == NULL) 5802 break; 5803 list_append_dict(l, dcell); 5804 5805 dict_add_string(dcell, "chars", mbs); 5806 5807 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x", 5808 fg.red, fg.green, fg.blue); 5809 dict_add_string(dcell, "fg", rgb); 5810 vim_snprintf((char *)rgb, 8, "#%02x%02x%02x", 5811 bg.red, bg.green, bg.blue); 5812 dict_add_string(dcell, "bg", rgb); 5813 5814 dict_add_number(dcell, "attr", cell2attr(term, NULL, attrs, fg, bg)); 5815 dict_add_number(dcell, "width", width); 5816 5817 ++pos.col; 5818 if (width == 2) 5819 ++pos.col; 5820 } 5821 } 5822 5823 /* 5824 * "term_sendkeys(buf, keys)" function 5825 */ 5826 void 5827 f_term_sendkeys(typval_T *argvars, typval_T *rettv UNUSED) 5828 { 5829 buf_T *buf = term_get_buf(argvars, "term_sendkeys()"); 5830 char_u *msg; 5831 term_T *term; 5832 5833 if (buf == NULL) 5834 return; 5835 5836 msg = tv_get_string_chk(&argvars[1]); 5837 if (msg == NULL) 5838 return; 5839 term = buf->b_term; 5840 if (term->tl_vterm == NULL) 5841 return; 5842 5843 while (*msg != NUL) 5844 { 5845 int c; 5846 5847 if (*msg == K_SPECIAL && msg[1] != NUL && msg[2] != NUL) 5848 { 5849 c = TO_SPECIAL(msg[1], msg[2]); 5850 msg += 3; 5851 } 5852 else 5853 { 5854 c = PTR2CHAR(msg); 5855 msg += MB_CPTR2LEN(msg); 5856 } 5857 send_keys_to_term(term, c, 0, FALSE); 5858 } 5859 } 5860 5861 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) || defined(PROTO) 5862 /* 5863 * "term_getansicolors(buf)" function 5864 */ 5865 void 5866 f_term_getansicolors(typval_T *argvars, typval_T *rettv) 5867 { 5868 buf_T *buf = term_get_buf(argvars, "term_getansicolors()"); 5869 term_T *term; 5870 VTermState *state; 5871 VTermColor color; 5872 char_u hexbuf[10]; 5873 int index; 5874 list_T *list; 5875 5876 if (rettv_list_alloc(rettv) == FAIL) 5877 return; 5878 5879 if (buf == NULL) 5880 return; 5881 term = buf->b_term; 5882 if (term->tl_vterm == NULL) 5883 return; 5884 5885 list = rettv->vval.v_list; 5886 state = vterm_obtain_state(term->tl_vterm); 5887 for (index = 0; index < 16; index++) 5888 { 5889 vterm_state_get_palette_color(state, index, &color); 5890 sprintf((char *)hexbuf, "#%02x%02x%02x", 5891 color.red, color.green, color.blue); 5892 if (list_append_string(list, hexbuf, 7) == FAIL) 5893 return; 5894 } 5895 } 5896 5897 /* 5898 * "term_setansicolors(buf, list)" function 5899 */ 5900 void 5901 f_term_setansicolors(typval_T *argvars, typval_T *rettv UNUSED) 5902 { 5903 buf_T *buf = term_get_buf(argvars, "term_setansicolors()"); 5904 term_T *term; 5905 5906 if (buf == NULL) 5907 return; 5908 term = buf->b_term; 5909 if (term->tl_vterm == NULL) 5910 return; 5911 5912 if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL) 5913 { 5914 emsg(_(e_listreq)); 5915 return; 5916 } 5917 5918 if (set_ansi_colors_list(term->tl_vterm, argvars[1].vval.v_list) == FAIL) 5919 emsg(_(e_invarg)); 5920 } 5921 #endif 5922 5923 /* 5924 * "term_setapi(buf, api)" function 5925 */ 5926 void 5927 f_term_setapi(typval_T *argvars, typval_T *rettv UNUSED) 5928 { 5929 buf_T *buf = term_get_buf(argvars, "term_setapi()"); 5930 term_T *term; 5931 char_u *api; 5932 5933 if (buf == NULL) 5934 return; 5935 term = buf->b_term; 5936 vim_free(term->tl_api); 5937 api = tv_get_string_chk(&argvars[1]); 5938 if (api != NULL) 5939 term->tl_api = vim_strsave(api); 5940 else 5941 term->tl_api = NULL; 5942 } 5943 5944 /* 5945 * "term_setrestore(buf, command)" function 5946 */ 5947 void 5948 f_term_setrestore(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 5949 { 5950 #if defined(FEAT_SESSION) 5951 buf_T *buf = term_get_buf(argvars, "term_setrestore()"); 5952 term_T *term; 5953 char_u *cmd; 5954 5955 if (buf == NULL) 5956 return; 5957 term = buf->b_term; 5958 vim_free(term->tl_command); 5959 cmd = tv_get_string_chk(&argvars[1]); 5960 if (cmd != NULL) 5961 term->tl_command = vim_strsave(cmd); 5962 else 5963 term->tl_command = NULL; 5964 #endif 5965 } 5966 5967 /* 5968 * "term_setkill(buf, how)" function 5969 */ 5970 void 5971 f_term_setkill(typval_T *argvars UNUSED, typval_T *rettv UNUSED) 5972 { 5973 buf_T *buf = term_get_buf(argvars, "term_setkill()"); 5974 term_T *term; 5975 char_u *how; 5976 5977 if (buf == NULL) 5978 return; 5979 term = buf->b_term; 5980 vim_free(term->tl_kill); 5981 how = tv_get_string_chk(&argvars[1]); 5982 if (how != NULL) 5983 term->tl_kill = vim_strsave(how); 5984 else 5985 term->tl_kill = NULL; 5986 } 5987 5988 /* 5989 * "term_start(command, options)" function 5990 */ 5991 void 5992 f_term_start(typval_T *argvars, typval_T *rettv) 5993 { 5994 jobopt_T opt; 5995 buf_T *buf; 5996 5997 init_job_options(&opt); 5998 if (argvars[1].v_type != VAR_UNKNOWN 5999 && get_job_options(&argvars[1], &opt, 6000 JO_TIMEOUT_ALL + JO_STOPONEXIT 6001 + JO_CALLBACK + JO_OUT_CALLBACK + JO_ERR_CALLBACK 6002 + JO_EXIT_CB + JO_CLOSE_CALLBACK + JO_OUT_IO, 6003 JO2_TERM_NAME + JO2_TERM_FINISH + JO2_HIDDEN + JO2_TERM_OPENCMD 6004 + JO2_TERM_COLS + JO2_TERM_ROWS + JO2_VERTICAL + JO2_CURWIN 6005 + JO2_CWD + JO2_ENV + JO2_EOF_CHARS 6006 + JO2_NORESTORE + JO2_TERM_KILL + JO2_TERM_HIGHLIGHT 6007 + JO2_ANSI_COLORS + JO2_TTY_TYPE + JO2_TERM_API) == FAIL) 6008 return; 6009 6010 buf = term_start(&argvars[0], NULL, &opt, 0); 6011 6012 if (buf != NULL && buf->b_term != NULL) 6013 rettv->vval.v_number = buf->b_fnum; 6014 } 6015 6016 /* 6017 * "term_wait" function 6018 */ 6019 void 6020 f_term_wait(typval_T *argvars, typval_T *rettv UNUSED) 6021 { 6022 buf_T *buf = term_get_buf(argvars, "term_wait()"); 6023 6024 if (buf == NULL) 6025 return; 6026 if (buf->b_term->tl_job == NULL) 6027 { 6028 ch_log(NULL, "term_wait(): no job to wait for"); 6029 return; 6030 } 6031 if (buf->b_term->tl_job->jv_channel == NULL) 6032 // channel is closed, nothing to do 6033 return; 6034 6035 // Get the job status, this will detect a job that finished. 6036 if (!buf->b_term->tl_job->jv_channel->ch_keep_open 6037 && STRCMP(job_status(buf->b_term->tl_job), "dead") == 0) 6038 { 6039 // The job is dead, keep reading channel I/O until the channel is 6040 // closed. buf->b_term may become NULL if the terminal was closed while 6041 // waiting. 6042 ch_log(NULL, "term_wait(): waiting for channel to close"); 6043 while (buf->b_term != NULL && !buf->b_term->tl_channel_closed) 6044 { 6045 term_flush_messages(); 6046 6047 ui_delay(10L, FALSE); 6048 if (!buf_valid(buf)) 6049 // If the terminal is closed when the channel is closed the 6050 // buffer disappears. 6051 break; 6052 } 6053 6054 term_flush_messages(); 6055 } 6056 else 6057 { 6058 long wait = 10L; 6059 6060 term_flush_messages(); 6061 6062 // Wait for some time for any channel I/O. 6063 if (argvars[1].v_type != VAR_UNKNOWN) 6064 wait = tv_get_number(&argvars[1]); 6065 ui_delay(wait, TRUE); 6066 6067 // Flushing messages on channels is hopefully sufficient. 6068 // TODO: is there a better way? 6069 term_flush_messages(); 6070 } 6071 } 6072 6073 /* 6074 * Called when a channel has sent all the lines to a terminal. 6075 * Send a CTRL-D to mark the end of the text. 6076 */ 6077 void 6078 term_send_eof(channel_T *ch) 6079 { 6080 term_T *term; 6081 6082 FOR_ALL_TERMS(term) 6083 if (term->tl_job == ch->ch_job) 6084 { 6085 if (term->tl_eof_chars != NULL) 6086 { 6087 channel_send(ch, PART_IN, term->tl_eof_chars, 6088 (int)STRLEN(term->tl_eof_chars), NULL); 6089 channel_send(ch, PART_IN, (char_u *)"\r", 1, NULL); 6090 } 6091 # ifdef MSWIN 6092 else 6093 // Default: CTRL-D 6094 channel_send(ch, PART_IN, (char_u *)"\004\r", 2, NULL); 6095 # endif 6096 } 6097 } 6098 6099 #if defined(FEAT_GUI) || defined(PROTO) 6100 job_T * 6101 term_getjob(term_T *term) 6102 { 6103 return term != NULL ? term->tl_job : NULL; 6104 } 6105 #endif 6106 6107 # if defined(MSWIN) || defined(PROTO) 6108 6109 /////////////////////////////////////// 6110 // 2. MS-Windows implementation. 6111 #ifdef PROTO 6112 typedef int COORD; 6113 typedef int DWORD; 6114 typedef int HANDLE; 6115 typedef int *DWORD_PTR; 6116 typedef int HPCON; 6117 typedef int HRESULT; 6118 typedef int LPPROC_THREAD_ATTRIBUTE_LIST; 6119 typedef int SIZE_T; 6120 typedef int PSIZE_T; 6121 typedef int PVOID; 6122 typedef int BOOL; 6123 # define WINAPI 6124 #endif 6125 6126 HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON*); 6127 HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD); 6128 HRESULT (WINAPI *pClosePseudoConsole)(HPCON); 6129 BOOL (WINAPI *pInitializeProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T); 6130 BOOL (WINAPI *pUpdateProcThreadAttribute)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T); 6131 void (WINAPI *pDeleteProcThreadAttributeList)(LPPROC_THREAD_ATTRIBUTE_LIST); 6132 6133 static int 6134 dyn_conpty_init(int verbose) 6135 { 6136 static HMODULE hKerneldll = NULL; 6137 int i; 6138 static struct 6139 { 6140 char *name; 6141 FARPROC *ptr; 6142 } conpty_entry[] = 6143 { 6144 {"CreatePseudoConsole", (FARPROC*)&pCreatePseudoConsole}, 6145 {"ResizePseudoConsole", (FARPROC*)&pResizePseudoConsole}, 6146 {"ClosePseudoConsole", (FARPROC*)&pClosePseudoConsole}, 6147 {"InitializeProcThreadAttributeList", 6148 (FARPROC*)&pInitializeProcThreadAttributeList}, 6149 {"UpdateProcThreadAttribute", 6150 (FARPROC*)&pUpdateProcThreadAttribute}, 6151 {"DeleteProcThreadAttributeList", 6152 (FARPROC*)&pDeleteProcThreadAttributeList}, 6153 {NULL, NULL} 6154 }; 6155 6156 if (!has_conpty_working()) 6157 { 6158 if (verbose) 6159 emsg(_("E982: ConPTY is not available")); 6160 return FAIL; 6161 } 6162 6163 // No need to initialize twice. 6164 if (hKerneldll) 6165 return OK; 6166 6167 hKerneldll = vimLoadLib("kernel32.dll"); 6168 for (i = 0; conpty_entry[i].name != NULL 6169 && conpty_entry[i].ptr != NULL; ++i) 6170 { 6171 if ((*conpty_entry[i].ptr = (FARPROC)GetProcAddress(hKerneldll, 6172 conpty_entry[i].name)) == NULL) 6173 { 6174 if (verbose) 6175 semsg(_(e_loadfunc), conpty_entry[i].name); 6176 hKerneldll = NULL; 6177 return FAIL; 6178 } 6179 } 6180 6181 return OK; 6182 } 6183 6184 static int 6185 conpty_term_and_job_init( 6186 term_T *term, 6187 typval_T *argvar, 6188 char **argv UNUSED, 6189 jobopt_T *opt, 6190 jobopt_T *orig_opt) 6191 { 6192 WCHAR *cmd_wchar = NULL; 6193 WCHAR *cmd_wchar_copy = NULL; 6194 WCHAR *cwd_wchar = NULL; 6195 WCHAR *env_wchar = NULL; 6196 channel_T *channel = NULL; 6197 job_T *job = NULL; 6198 HANDLE jo = NULL; 6199 garray_T ga_cmd, ga_env; 6200 char_u *cmd = NULL; 6201 HRESULT hr; 6202 COORD consize; 6203 SIZE_T breq; 6204 PROCESS_INFORMATION proc_info; 6205 HANDLE i_theirs = NULL; 6206 HANDLE o_theirs = NULL; 6207 HANDLE i_ours = NULL; 6208 HANDLE o_ours = NULL; 6209 6210 ga_init2(&ga_cmd, (int)sizeof(char*), 20); 6211 ga_init2(&ga_env, (int)sizeof(char*), 20); 6212 6213 if (argvar->v_type == VAR_STRING) 6214 { 6215 cmd = argvar->vval.v_string; 6216 } 6217 else if (argvar->v_type == VAR_LIST) 6218 { 6219 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL) 6220 goto failed; 6221 cmd = ga_cmd.ga_data; 6222 } 6223 if (cmd == NULL || *cmd == NUL) 6224 { 6225 emsg(_(e_invarg)); 6226 goto failed; 6227 } 6228 6229 term->tl_arg0_cmd = vim_strsave(cmd); 6230 6231 cmd_wchar = enc_to_utf16(cmd, NULL); 6232 6233 if (cmd_wchar != NULL) 6234 { 6235 // Request by CreateProcessW 6236 breq = wcslen(cmd_wchar) + 1 + 1; // Addition of NUL by API 6237 cmd_wchar_copy = ALLOC_MULT(WCHAR, breq); 6238 wcsncpy(cmd_wchar_copy, cmd_wchar, breq - 1); 6239 } 6240 6241 ga_clear(&ga_cmd); 6242 if (cmd_wchar == NULL) 6243 goto failed; 6244 if (opt->jo_cwd != NULL) 6245 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL); 6246 6247 win32_build_env(opt->jo_env, &ga_env, TRUE); 6248 env_wchar = ga_env.ga_data; 6249 6250 if (!CreatePipe(&i_theirs, &i_ours, NULL, 0)) 6251 goto failed; 6252 if (!CreatePipe(&o_ours, &o_theirs, NULL, 0)) 6253 goto failed; 6254 6255 consize.X = term->tl_cols; 6256 consize.Y = term->tl_rows; 6257 hr = pCreatePseudoConsole(consize, i_theirs, o_theirs, 0, 6258 &term->tl_conpty); 6259 if (FAILED(hr)) 6260 goto failed; 6261 6262 term->tl_siex.StartupInfo.cb = sizeof(term->tl_siex); 6263 6264 // Set up pipe inheritance safely: Vista or later. 6265 pInitializeProcThreadAttributeList(NULL, 1, 0, &breq); 6266 term->tl_siex.lpAttributeList = alloc(breq); 6267 if (!term->tl_siex.lpAttributeList) 6268 goto failed; 6269 if (!pInitializeProcThreadAttributeList(term->tl_siex.lpAttributeList, 1, 6270 0, &breq)) 6271 goto failed; 6272 if (!pUpdateProcThreadAttribute( 6273 term->tl_siex.lpAttributeList, 0, 6274 PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, term->tl_conpty, 6275 sizeof(HPCON), NULL, NULL)) 6276 goto failed; 6277 6278 channel = add_channel(); 6279 if (channel == NULL) 6280 goto failed; 6281 6282 job = job_alloc(); 6283 if (job == NULL) 6284 goto failed; 6285 if (argvar->v_type == VAR_STRING) 6286 { 6287 int argc; 6288 6289 build_argv_from_string(cmd, &job->jv_argv, &argc); 6290 } 6291 else 6292 { 6293 int argc; 6294 6295 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc); 6296 } 6297 6298 if (opt->jo_set & JO_IN_BUF) 6299 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]); 6300 6301 if (!CreateProcessW(NULL, cmd_wchar_copy, NULL, NULL, FALSE, 6302 EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT 6303 | CREATE_SUSPENDED | CREATE_DEFAULT_ERROR_MODE, 6304 env_wchar, cwd_wchar, 6305 &term->tl_siex.StartupInfo, &proc_info)) 6306 goto failed; 6307 6308 CloseHandle(i_theirs); 6309 CloseHandle(o_theirs); 6310 6311 channel_set_pipes(channel, 6312 (sock_T)i_ours, 6313 (sock_T)o_ours, 6314 (sock_T)o_ours); 6315 6316 // Write lines with CR instead of NL. 6317 channel->ch_write_text_mode = TRUE; 6318 6319 // Use to explicitly delete anonymous pipe handle. 6320 channel->ch_anonymous_pipe = TRUE; 6321 6322 jo = CreateJobObject(NULL, NULL); 6323 if (jo == NULL) 6324 goto failed; 6325 6326 if (!AssignProcessToJobObject(jo, proc_info.hProcess)) 6327 { 6328 // Failed, switch the way to terminate process with TerminateProcess. 6329 CloseHandle(jo); 6330 jo = NULL; 6331 } 6332 6333 ResumeThread(proc_info.hThread); 6334 CloseHandle(proc_info.hThread); 6335 6336 vim_free(cmd_wchar); 6337 vim_free(cmd_wchar_copy); 6338 vim_free(cwd_wchar); 6339 vim_free(env_wchar); 6340 6341 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL) 6342 goto failed; 6343 6344 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) 6345 if (opt->jo_set2 & JO2_ANSI_COLORS) 6346 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors); 6347 else 6348 init_vterm_ansi_colors(term->tl_vterm); 6349 #endif 6350 6351 channel_set_job(channel, job, opt); 6352 job_set_options(job, opt); 6353 6354 job->jv_channel = channel; 6355 job->jv_proc_info = proc_info; 6356 job->jv_job_object = jo; 6357 job->jv_status = JOB_STARTED; 6358 job->jv_tty_type = vim_strsave((char_u *)"conpty"); 6359 ++job->jv_refcount; 6360 term->tl_job = job; 6361 6362 // Redirecting stdout and stderr doesn't work at the job level. Instead 6363 // open the file here and handle it in. opt->jo_io was changed in 6364 // setup_job_options(), use the original flags here. 6365 if (orig_opt->jo_io[PART_OUT] == JIO_FILE) 6366 { 6367 char_u *fname = opt->jo_io_name[PART_OUT]; 6368 6369 ch_log(channel, "Opening output file %s", fname); 6370 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN); 6371 if (term->tl_out_fd == NULL) 6372 semsg(_(e_notopen), fname); 6373 } 6374 6375 return OK; 6376 6377 failed: 6378 ga_clear(&ga_cmd); 6379 ga_clear(&ga_env); 6380 vim_free(cmd_wchar); 6381 vim_free(cmd_wchar_copy); 6382 vim_free(cwd_wchar); 6383 if (channel != NULL) 6384 channel_clear(channel); 6385 if (job != NULL) 6386 { 6387 job->jv_channel = NULL; 6388 job_cleanup(job); 6389 } 6390 term->tl_job = NULL; 6391 if (jo != NULL) 6392 CloseHandle(jo); 6393 6394 if (term->tl_siex.lpAttributeList != NULL) 6395 { 6396 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList); 6397 vim_free(term->tl_siex.lpAttributeList); 6398 } 6399 term->tl_siex.lpAttributeList = NULL; 6400 if (o_theirs != NULL) 6401 CloseHandle(o_theirs); 6402 if (o_ours != NULL) 6403 CloseHandle(o_ours); 6404 if (i_ours != NULL) 6405 CloseHandle(i_ours); 6406 if (i_theirs != NULL) 6407 CloseHandle(i_theirs); 6408 if (term->tl_conpty != NULL) 6409 pClosePseudoConsole(term->tl_conpty); 6410 term->tl_conpty = NULL; 6411 return FAIL; 6412 } 6413 6414 static void 6415 conpty_term_report_winsize(term_T *term, int rows, int cols) 6416 { 6417 COORD consize; 6418 6419 consize.X = cols; 6420 consize.Y = rows; 6421 pResizePseudoConsole(term->tl_conpty, consize); 6422 } 6423 6424 static void 6425 term_free_conpty(term_T *term) 6426 { 6427 if (term->tl_siex.lpAttributeList != NULL) 6428 { 6429 pDeleteProcThreadAttributeList(term->tl_siex.lpAttributeList); 6430 vim_free(term->tl_siex.lpAttributeList); 6431 } 6432 term->tl_siex.lpAttributeList = NULL; 6433 if (term->tl_conpty != NULL) 6434 pClosePseudoConsole(term->tl_conpty); 6435 term->tl_conpty = NULL; 6436 } 6437 6438 int 6439 use_conpty(void) 6440 { 6441 return has_conpty; 6442 } 6443 6444 # ifndef PROTO 6445 6446 #define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ul 6447 #define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull 6448 #define WINPTY_MOUSE_MODE_FORCE 2 6449 6450 void* (*winpty_config_new)(UINT64, void*); 6451 void* (*winpty_open)(void*, void*); 6452 void* (*winpty_spawn_config_new)(UINT64, void*, LPCWSTR, void*, void*, void*); 6453 BOOL (*winpty_spawn)(void*, void*, HANDLE*, HANDLE*, DWORD*, void*); 6454 void (*winpty_config_set_mouse_mode)(void*, int); 6455 void (*winpty_config_set_initial_size)(void*, int, int); 6456 LPCWSTR (*winpty_conin_name)(void*); 6457 LPCWSTR (*winpty_conout_name)(void*); 6458 LPCWSTR (*winpty_conerr_name)(void*); 6459 void (*winpty_free)(void*); 6460 void (*winpty_config_free)(void*); 6461 void (*winpty_spawn_config_free)(void*); 6462 void (*winpty_error_free)(void*); 6463 LPCWSTR (*winpty_error_msg)(void*); 6464 BOOL (*winpty_set_size)(void*, int, int, void*); 6465 HANDLE (*winpty_agent_process)(void*); 6466 6467 #define WINPTY_DLL "winpty.dll" 6468 6469 static HINSTANCE hWinPtyDLL = NULL; 6470 # endif 6471 6472 static int 6473 dyn_winpty_init(int verbose) 6474 { 6475 int i; 6476 static struct 6477 { 6478 char *name; 6479 FARPROC *ptr; 6480 } winpty_entry[] = 6481 { 6482 {"winpty_conerr_name", (FARPROC*)&winpty_conerr_name}, 6483 {"winpty_config_free", (FARPROC*)&winpty_config_free}, 6484 {"winpty_config_new", (FARPROC*)&winpty_config_new}, 6485 {"winpty_config_set_mouse_mode", 6486 (FARPROC*)&winpty_config_set_mouse_mode}, 6487 {"winpty_config_set_initial_size", 6488 (FARPROC*)&winpty_config_set_initial_size}, 6489 {"winpty_conin_name", (FARPROC*)&winpty_conin_name}, 6490 {"winpty_conout_name", (FARPROC*)&winpty_conout_name}, 6491 {"winpty_error_free", (FARPROC*)&winpty_error_free}, 6492 {"winpty_free", (FARPROC*)&winpty_free}, 6493 {"winpty_open", (FARPROC*)&winpty_open}, 6494 {"winpty_spawn", (FARPROC*)&winpty_spawn}, 6495 {"winpty_spawn_config_free", (FARPROC*)&winpty_spawn_config_free}, 6496 {"winpty_spawn_config_new", (FARPROC*)&winpty_spawn_config_new}, 6497 {"winpty_error_msg", (FARPROC*)&winpty_error_msg}, 6498 {"winpty_set_size", (FARPROC*)&winpty_set_size}, 6499 {"winpty_agent_process", (FARPROC*)&winpty_agent_process}, 6500 {NULL, NULL} 6501 }; 6502 6503 // No need to initialize twice. 6504 if (hWinPtyDLL) 6505 return OK; 6506 // Load winpty.dll, prefer using the 'winptydll' option, fall back to just 6507 // winpty.dll. 6508 if (*p_winptydll != NUL) 6509 hWinPtyDLL = vimLoadLib((char *)p_winptydll); 6510 if (!hWinPtyDLL) 6511 hWinPtyDLL = vimLoadLib(WINPTY_DLL); 6512 if (!hWinPtyDLL) 6513 { 6514 if (verbose) 6515 semsg(_(e_loadlib), *p_winptydll != NUL ? p_winptydll 6516 : (char_u *)WINPTY_DLL); 6517 return FAIL; 6518 } 6519 for (i = 0; winpty_entry[i].name != NULL 6520 && winpty_entry[i].ptr != NULL; ++i) 6521 { 6522 if ((*winpty_entry[i].ptr = (FARPROC)GetProcAddress(hWinPtyDLL, 6523 winpty_entry[i].name)) == NULL) 6524 { 6525 if (verbose) 6526 semsg(_(e_loadfunc), winpty_entry[i].name); 6527 hWinPtyDLL = NULL; 6528 return FAIL; 6529 } 6530 } 6531 6532 return OK; 6533 } 6534 6535 static int 6536 winpty_term_and_job_init( 6537 term_T *term, 6538 typval_T *argvar, 6539 char **argv UNUSED, 6540 jobopt_T *opt, 6541 jobopt_T *orig_opt) 6542 { 6543 WCHAR *cmd_wchar = NULL; 6544 WCHAR *cwd_wchar = NULL; 6545 WCHAR *env_wchar = NULL; 6546 channel_T *channel = NULL; 6547 job_T *job = NULL; 6548 DWORD error; 6549 HANDLE jo = NULL; 6550 HANDLE child_process_handle; 6551 HANDLE child_thread_handle; 6552 void *winpty_err = NULL; 6553 void *spawn_config = NULL; 6554 garray_T ga_cmd, ga_env; 6555 char_u *cmd = NULL; 6556 6557 ga_init2(&ga_cmd, (int)sizeof(char*), 20); 6558 ga_init2(&ga_env, (int)sizeof(char*), 20); 6559 6560 if (argvar->v_type == VAR_STRING) 6561 { 6562 cmd = argvar->vval.v_string; 6563 } 6564 else if (argvar->v_type == VAR_LIST) 6565 { 6566 if (win32_build_cmd(argvar->vval.v_list, &ga_cmd) == FAIL) 6567 goto failed; 6568 cmd = ga_cmd.ga_data; 6569 } 6570 if (cmd == NULL || *cmd == NUL) 6571 { 6572 emsg(_(e_invarg)); 6573 goto failed; 6574 } 6575 6576 term->tl_arg0_cmd = vim_strsave(cmd); 6577 6578 cmd_wchar = enc_to_utf16(cmd, NULL); 6579 ga_clear(&ga_cmd); 6580 if (cmd_wchar == NULL) 6581 goto failed; 6582 if (opt->jo_cwd != NULL) 6583 cwd_wchar = enc_to_utf16(opt->jo_cwd, NULL); 6584 6585 win32_build_env(opt->jo_env, &ga_env, TRUE); 6586 env_wchar = ga_env.ga_data; 6587 6588 term->tl_winpty_config = winpty_config_new(0, &winpty_err); 6589 if (term->tl_winpty_config == NULL) 6590 goto failed; 6591 6592 winpty_config_set_mouse_mode(term->tl_winpty_config, 6593 WINPTY_MOUSE_MODE_FORCE); 6594 winpty_config_set_initial_size(term->tl_winpty_config, 6595 term->tl_cols, term->tl_rows); 6596 term->tl_winpty = winpty_open(term->tl_winpty_config, &winpty_err); 6597 if (term->tl_winpty == NULL) 6598 goto failed; 6599 6600 spawn_config = winpty_spawn_config_new( 6601 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN | 6602 WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN, 6603 NULL, 6604 cmd_wchar, 6605 cwd_wchar, 6606 env_wchar, 6607 &winpty_err); 6608 if (spawn_config == NULL) 6609 goto failed; 6610 6611 channel = add_channel(); 6612 if (channel == NULL) 6613 goto failed; 6614 6615 job = job_alloc(); 6616 if (job == NULL) 6617 goto failed; 6618 if (argvar->v_type == VAR_STRING) 6619 { 6620 int argc; 6621 6622 build_argv_from_string(cmd, &job->jv_argv, &argc); 6623 } 6624 else 6625 { 6626 int argc; 6627 6628 build_argv_from_list(argvar->vval.v_list, &job->jv_argv, &argc); 6629 } 6630 6631 if (opt->jo_set & JO_IN_BUF) 6632 job->jv_in_buf = buflist_findnr(opt->jo_io_buf[PART_IN]); 6633 6634 if (!winpty_spawn(term->tl_winpty, spawn_config, &child_process_handle, 6635 &child_thread_handle, &error, &winpty_err)) 6636 goto failed; 6637 6638 channel_set_pipes(channel, 6639 (sock_T)CreateFileW( 6640 winpty_conin_name(term->tl_winpty), 6641 GENERIC_WRITE, 0, NULL, 6642 OPEN_EXISTING, 0, NULL), 6643 (sock_T)CreateFileW( 6644 winpty_conout_name(term->tl_winpty), 6645 GENERIC_READ, 0, NULL, 6646 OPEN_EXISTING, 0, NULL), 6647 (sock_T)CreateFileW( 6648 winpty_conerr_name(term->tl_winpty), 6649 GENERIC_READ, 0, NULL, 6650 OPEN_EXISTING, 0, NULL)); 6651 6652 // Write lines with CR instead of NL. 6653 channel->ch_write_text_mode = TRUE; 6654 6655 jo = CreateJobObject(NULL, NULL); 6656 if (jo == NULL) 6657 goto failed; 6658 6659 if (!AssignProcessToJobObject(jo, child_process_handle)) 6660 { 6661 // Failed, switch the way to terminate process with TerminateProcess. 6662 CloseHandle(jo); 6663 jo = NULL; 6664 } 6665 6666 winpty_spawn_config_free(spawn_config); 6667 vim_free(cmd_wchar); 6668 vim_free(cwd_wchar); 6669 vim_free(env_wchar); 6670 6671 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL) 6672 goto failed; 6673 6674 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) 6675 if (opt->jo_set2 & JO2_ANSI_COLORS) 6676 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors); 6677 else 6678 init_vterm_ansi_colors(term->tl_vterm); 6679 #endif 6680 6681 channel_set_job(channel, job, opt); 6682 job_set_options(job, opt); 6683 6684 job->jv_channel = channel; 6685 job->jv_proc_info.hProcess = child_process_handle; 6686 job->jv_proc_info.dwProcessId = GetProcessId(child_process_handle); 6687 job->jv_job_object = jo; 6688 job->jv_status = JOB_STARTED; 6689 job->jv_tty_in = utf16_to_enc( 6690 (short_u *)winpty_conin_name(term->tl_winpty), NULL); 6691 job->jv_tty_out = utf16_to_enc( 6692 (short_u *)winpty_conout_name(term->tl_winpty), NULL); 6693 job->jv_tty_type = vim_strsave((char_u *)"winpty"); 6694 ++job->jv_refcount; 6695 term->tl_job = job; 6696 6697 // Redirecting stdout and stderr doesn't work at the job level. Instead 6698 // open the file here and handle it in. opt->jo_io was changed in 6699 // setup_job_options(), use the original flags here. 6700 if (orig_opt->jo_io[PART_OUT] == JIO_FILE) 6701 { 6702 char_u *fname = opt->jo_io_name[PART_OUT]; 6703 6704 ch_log(channel, "Opening output file %s", fname); 6705 term->tl_out_fd = mch_fopen((char *)fname, WRITEBIN); 6706 if (term->tl_out_fd == NULL) 6707 semsg(_(e_notopen), fname); 6708 } 6709 6710 return OK; 6711 6712 failed: 6713 ga_clear(&ga_cmd); 6714 ga_clear(&ga_env); 6715 vim_free(cmd_wchar); 6716 vim_free(cwd_wchar); 6717 if (spawn_config != NULL) 6718 winpty_spawn_config_free(spawn_config); 6719 if (channel != NULL) 6720 channel_clear(channel); 6721 if (job != NULL) 6722 { 6723 job->jv_channel = NULL; 6724 job_cleanup(job); 6725 } 6726 term->tl_job = NULL; 6727 if (jo != NULL) 6728 CloseHandle(jo); 6729 if (term->tl_winpty != NULL) 6730 winpty_free(term->tl_winpty); 6731 term->tl_winpty = NULL; 6732 if (term->tl_winpty_config != NULL) 6733 winpty_config_free(term->tl_winpty_config); 6734 term->tl_winpty_config = NULL; 6735 if (winpty_err != NULL) 6736 { 6737 char *msg = (char *)utf16_to_enc( 6738 (short_u *)winpty_error_msg(winpty_err), NULL); 6739 6740 emsg(msg); 6741 winpty_error_free(winpty_err); 6742 } 6743 return FAIL; 6744 } 6745 6746 /* 6747 * Create a new terminal of "rows" by "cols" cells. 6748 * Store a reference in "term". 6749 * Return OK or FAIL. 6750 */ 6751 static int 6752 term_and_job_init( 6753 term_T *term, 6754 typval_T *argvar, 6755 char **argv, 6756 jobopt_T *opt, 6757 jobopt_T *orig_opt) 6758 { 6759 int use_winpty = FALSE; 6760 int use_conpty = FALSE; 6761 int tty_type = *p_twt; 6762 6763 has_winpty = dyn_winpty_init(FALSE) != FAIL ? TRUE : FALSE; 6764 has_conpty = dyn_conpty_init(FALSE) != FAIL ? TRUE : FALSE; 6765 6766 if (!has_winpty && !has_conpty) 6767 // If neither is available give the errors for winpty, since when 6768 // conpty is not available it can't be installed either. 6769 return dyn_winpty_init(TRUE); 6770 6771 if (opt->jo_tty_type != NUL) 6772 tty_type = opt->jo_tty_type; 6773 6774 if (tty_type == NUL) 6775 { 6776 if (has_conpty && (is_conpty_stable() || !has_winpty)) 6777 use_conpty = TRUE; 6778 else if (has_winpty) 6779 use_winpty = TRUE; 6780 // else: error 6781 } 6782 else if (tty_type == 'w') // winpty 6783 { 6784 if (has_winpty) 6785 use_winpty = TRUE; 6786 } 6787 else if (tty_type == 'c') // conpty 6788 { 6789 if (has_conpty) 6790 use_conpty = TRUE; 6791 else 6792 return dyn_conpty_init(TRUE); 6793 } 6794 6795 if (use_conpty) 6796 return conpty_term_and_job_init(term, argvar, argv, opt, orig_opt); 6797 6798 if (use_winpty) 6799 return winpty_term_and_job_init(term, argvar, argv, opt, orig_opt); 6800 6801 // error 6802 return dyn_winpty_init(TRUE); 6803 } 6804 6805 static int 6806 create_pty_only(term_T *term, jobopt_T *options) 6807 { 6808 HANDLE hPipeIn = INVALID_HANDLE_VALUE; 6809 HANDLE hPipeOut = INVALID_HANDLE_VALUE; 6810 char in_name[80], out_name[80]; 6811 channel_T *channel = NULL; 6812 6813 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL) 6814 return FAIL; 6815 6816 vim_snprintf(in_name, sizeof(in_name), "\\\\.\\pipe\\vim-%d-in-%d", 6817 GetCurrentProcessId(), 6818 curbuf->b_fnum); 6819 hPipeIn = CreateNamedPipe(in_name, PIPE_ACCESS_OUTBOUND, 6820 PIPE_TYPE_MESSAGE | PIPE_NOWAIT, 6821 PIPE_UNLIMITED_INSTANCES, 6822 0, 0, NMPWAIT_NOWAIT, NULL); 6823 if (hPipeIn == INVALID_HANDLE_VALUE) 6824 goto failed; 6825 6826 vim_snprintf(out_name, sizeof(out_name), "\\\\.\\pipe\\vim-%d-out-%d", 6827 GetCurrentProcessId(), 6828 curbuf->b_fnum); 6829 hPipeOut = CreateNamedPipe(out_name, PIPE_ACCESS_INBOUND, 6830 PIPE_TYPE_MESSAGE | PIPE_NOWAIT, 6831 PIPE_UNLIMITED_INSTANCES, 6832 0, 0, 0, NULL); 6833 if (hPipeOut == INVALID_HANDLE_VALUE) 6834 goto failed; 6835 6836 ConnectNamedPipe(hPipeIn, NULL); 6837 ConnectNamedPipe(hPipeOut, NULL); 6838 6839 term->tl_job = job_alloc(); 6840 if (term->tl_job == NULL) 6841 goto failed; 6842 ++term->tl_job->jv_refcount; 6843 6844 // behave like the job is already finished 6845 term->tl_job->jv_status = JOB_FINISHED; 6846 6847 channel = add_channel(); 6848 if (channel == NULL) 6849 goto failed; 6850 term->tl_job->jv_channel = channel; 6851 channel->ch_keep_open = TRUE; 6852 channel->ch_named_pipe = TRUE; 6853 6854 channel_set_pipes(channel, 6855 (sock_T)hPipeIn, 6856 (sock_T)hPipeOut, 6857 (sock_T)hPipeOut); 6858 channel_set_job(channel, term->tl_job, options); 6859 term->tl_job->jv_tty_in = vim_strsave((char_u*)in_name); 6860 term->tl_job->jv_tty_out = vim_strsave((char_u*)out_name); 6861 6862 return OK; 6863 6864 failed: 6865 if (hPipeIn != NULL) 6866 CloseHandle(hPipeIn); 6867 if (hPipeOut != NULL) 6868 CloseHandle(hPipeOut); 6869 return FAIL; 6870 } 6871 6872 /* 6873 * Free the terminal emulator part of "term". 6874 */ 6875 static void 6876 term_free_vterm(term_T *term) 6877 { 6878 term_free_conpty(term); 6879 if (term->tl_winpty != NULL) 6880 winpty_free(term->tl_winpty); 6881 term->tl_winpty = NULL; 6882 if (term->tl_winpty_config != NULL) 6883 winpty_config_free(term->tl_winpty_config); 6884 term->tl_winpty_config = NULL; 6885 if (term->tl_vterm != NULL) 6886 vterm_free(term->tl_vterm); 6887 term->tl_vterm = NULL; 6888 } 6889 6890 /* 6891 * Report the size to the terminal. 6892 */ 6893 static void 6894 term_report_winsize(term_T *term, int rows, int cols) 6895 { 6896 if (term->tl_conpty) 6897 conpty_term_report_winsize(term, rows, cols); 6898 if (term->tl_winpty) 6899 winpty_set_size(term->tl_winpty, cols, rows, NULL); 6900 } 6901 6902 int 6903 terminal_enabled(void) 6904 { 6905 return dyn_winpty_init(FALSE) == OK || dyn_conpty_init(FALSE) == OK; 6906 } 6907 6908 # else 6909 6910 /////////////////////////////////////// 6911 // 3. Unix-like implementation. 6912 6913 /* 6914 * Create a new terminal of "rows" by "cols" cells. 6915 * Start job for "cmd". 6916 * Store the pointers in "term". 6917 * When "argv" is not NULL then "argvar" is not used. 6918 * Return OK or FAIL. 6919 */ 6920 static int 6921 term_and_job_init( 6922 term_T *term, 6923 typval_T *argvar, 6924 char **argv, 6925 jobopt_T *opt, 6926 jobopt_T *orig_opt UNUSED) 6927 { 6928 term->tl_arg0_cmd = NULL; 6929 6930 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL) 6931 return FAIL; 6932 6933 #if defined(FEAT_GUI) || defined(FEAT_TERMGUICOLORS) 6934 if (opt->jo_set2 & JO2_ANSI_COLORS) 6935 set_vterm_palette(term->tl_vterm, opt->jo_ansi_colors); 6936 else 6937 init_vterm_ansi_colors(term->tl_vterm); 6938 #endif 6939 6940 // This may change a string in "argvar". 6941 term->tl_job = job_start(argvar, argv, opt, &term->tl_job); 6942 if (term->tl_job != NULL) 6943 ++term->tl_job->jv_refcount; 6944 6945 return term->tl_job != NULL 6946 && term->tl_job->jv_channel != NULL 6947 && term->tl_job->jv_status != JOB_FAILED ? OK : FAIL; 6948 } 6949 6950 static int 6951 create_pty_only(term_T *term, jobopt_T *opt) 6952 { 6953 if (create_vterm(term, term->tl_rows, term->tl_cols) == FAIL) 6954 return FAIL; 6955 6956 term->tl_job = job_alloc(); 6957 if (term->tl_job == NULL) 6958 return FAIL; 6959 ++term->tl_job->jv_refcount; 6960 6961 // behave like the job is already finished 6962 term->tl_job->jv_status = JOB_FINISHED; 6963 6964 return mch_create_pty_channel(term->tl_job, opt); 6965 } 6966 6967 /* 6968 * Free the terminal emulator part of "term". 6969 */ 6970 static void 6971 term_free_vterm(term_T *term) 6972 { 6973 if (term->tl_vterm != NULL) 6974 vterm_free(term->tl_vterm); 6975 term->tl_vterm = NULL; 6976 } 6977 6978 /* 6979 * Report the size to the terminal. 6980 */ 6981 static void 6982 term_report_winsize(term_T *term, int rows, int cols) 6983 { 6984 // Use an ioctl() to report the new window size to the job. 6985 if (term->tl_job != NULL && term->tl_job->jv_channel != NULL) 6986 { 6987 int fd = -1; 6988 int part; 6989 6990 for (part = PART_OUT; part < PART_COUNT; ++part) 6991 { 6992 fd = term->tl_job->jv_channel->ch_part[part].ch_fd; 6993 if (mch_isatty(fd)) 6994 break; 6995 } 6996 if (part < PART_COUNT && mch_report_winsize(fd, rows, cols) == OK) 6997 mch_signal_job(term->tl_job, (char_u *)"winch"); 6998 } 6999 } 7000 7001 # endif 7002 7003 #endif // FEAT_TERMINAL 7004