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