terminal.c (73238B)
1 // VT220/xterm-like terminal emulator. 2 // Powered by libvterm http://www.leonerd.org.uk/code/libvterm 3 // 4 // libvterm is a pure C99 terminal emulation library with abstract input and 5 // display. This means that the library needs to read data from the master fd 6 // and feed VTerm instances, which will invoke user callbacks with screen 7 // update instructions that must be mirrored to the real display. 8 // 9 // Keys are sent to VTerm instances by calling 10 // vterm_keyboard_key/vterm_keyboard_unichar, which generates byte streams that 11 // must be fed back to the master fd. 12 // 13 // Nvim buffers are used as the display mechanism for both the visible screen 14 // and the scrollback buffer. 15 // 16 // When a line becomes invisible due to a decrease in screen height or because 17 // a line was pushed up during normal terminal output, we store the line 18 // information in the scrollback buffer, which is mirrored in the nvim buffer 19 // by appending lines just above the visible part of the buffer. 20 // 21 // When the screen height increases, libvterm will ask for a row in the 22 // scrollback buffer, which is mirrored in the nvim buffer displaying lines 23 // that were previously invisible. 24 // 25 // The vterm->nvim synchronization is performed in intervals of 10 milliseconds, 26 // to minimize screen updates when receiving large bursts of data. 27 // 28 // This module is decoupled from the processes that normally feed it data, so 29 // it's possible to use it as a general purpose console buffer (possibly as a 30 // log/display mechanism for nvim in the future) 31 // 32 // Inspired by: vimshell http://www.wana.at/vimshell 33 // Conque https://code.google.com/p/conque 34 // Some code from pangoterm http://www.leonerd.org.uk/code/pangoterm 35 36 #include <assert.h> 37 #include <limits.h> 38 #include <stdbool.h> 39 #include <stdint.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 44 #include "klib/kvec.h" 45 #include "nvim/api/private/helpers.h" 46 #include "nvim/ascii_defs.h" 47 #include "nvim/autocmd.h" 48 #include "nvim/autocmd_defs.h" 49 #include "nvim/buffer.h" 50 #include "nvim/buffer_defs.h" 51 #include "nvim/change.h" 52 #include "nvim/channel.h" 53 #include "nvim/channel_defs.h" 54 #include "nvim/cursor.h" 55 #include "nvim/cursor_shape.h" 56 #include "nvim/drawline.h" 57 #include "nvim/drawscreen.h" 58 #include "nvim/eval.h" 59 #include "nvim/eval/typval.h" 60 #include "nvim/eval/typval_defs.h" 61 #include "nvim/eval/vars.h" 62 #include "nvim/event/defs.h" 63 #include "nvim/event/loop.h" 64 #include "nvim/event/multiqueue.h" 65 #include "nvim/event/time.h" 66 #include "nvim/ex_docmd.h" 67 #include "nvim/getchar.h" 68 #include "nvim/globals.h" 69 #include "nvim/grid.h" 70 #include "nvim/highlight.h" 71 #include "nvim/highlight_defs.h" 72 #include "nvim/highlight_group.h" 73 #include "nvim/keycodes.h" 74 #include "nvim/macros_defs.h" 75 #include "nvim/main.h" 76 #include "nvim/map_defs.h" 77 #include "nvim/mark.h" 78 #include "nvim/mbyte.h" 79 #include "nvim/memline.h" 80 #include "nvim/memory.h" 81 #include "nvim/mouse.h" 82 #include "nvim/move.h" 83 #include "nvim/msgpack_rpc/channel_defs.h" 84 #include "nvim/normal_defs.h" 85 #include "nvim/ops.h" 86 #include "nvim/option.h" 87 #include "nvim/option_defs.h" 88 #include "nvim/option_vars.h" 89 #include "nvim/optionstr.h" 90 #include "nvim/pos_defs.h" 91 #include "nvim/state.h" 92 #include "nvim/state_defs.h" 93 #include "nvim/strings.h" 94 #include "nvim/terminal.h" 95 #include "nvim/types_defs.h" 96 #include "nvim/ui.h" 97 #include "nvim/vim_defs.h" 98 #include "nvim/vterm/keyboard.h" 99 #include "nvim/vterm/mouse.h" 100 #include "nvim/vterm/parser.h" 101 #include "nvim/vterm/pen.h" 102 #include "nvim/vterm/screen.h" 103 #include "nvim/vterm/state.h" 104 #include "nvim/vterm/vterm.h" 105 #include "nvim/vterm/vterm_keycodes_defs.h" 106 #include "nvim/window.h" 107 108 typedef struct { 109 VimState state; 110 Terminal *term; 111 int save_rd; ///< saved value of RedrawingDisabled 112 bool close; 113 bool got_bsl; ///< if the last input was <C-\> 114 bool got_bsl_o; ///< if left terminal mode with <c-\><c-o> 115 bool cursor_visible; ///< cursor's current visibility; ensures matched busy_start/stop UI events 116 117 // These fields remember the prior values of window options before entering terminal mode. 118 // Valid only when save_curwin_handle != 0. 119 handle_T save_curwin_handle; 120 bool save_w_p_cul; 121 char *save_w_p_culopt; 122 uint8_t save_w_p_culopt_flags; 123 int save_w_p_cuc; 124 OptInt save_w_p_so; 125 OptInt save_w_p_siso; 126 } TerminalState; 127 128 #include "terminal.c.generated.h" 129 130 // Delay for refreshing the terminal buffer after receiving updates from 131 // libvterm. Improves performance when receiving large bursts of data. 132 #define REFRESH_DELAY 10 133 134 #define TEXTBUF_SIZE 0x1fff 135 #define SELECTIONBUF_SIZE 0x0400 136 137 static TimeWatcher refresh_timer; 138 static bool refresh_pending = false; 139 140 typedef struct { 141 size_t cols; 142 VTermScreenCell cells[]; 143 } ScrollbackLine; 144 145 struct terminal { 146 TerminalOptions opts; // options passed to terminal_alloc() 147 VTerm *vt; 148 VTermScreen *vts; 149 // buffer used to: 150 // - convert VTermScreen cell arrays into utf8 strings 151 // - receive data from libvterm as a result of key presses. 152 char textbuf[TEXTBUF_SIZE]; 153 154 ScrollbackLine **sb_buffer; ///< Scrollback storage. 155 size_t sb_current; ///< Lines stored in sb_buffer. 156 size_t sb_size; ///< Capacity of sb_buffer. 157 /// "virtual index" that points to the first sb_buffer row that we need to 158 /// push to the terminal buffer when refreshing the scrollback. 159 int sb_pending; 160 size_t sb_deleted; ///< Lines deleted from sb_buffer. 161 size_t old_sb_deleted; ///< Value of sb_deleted on last refresh_scrollback(). 162 /// Lines in the terminal buffer belonging to the screen instead of the scrollback. 163 int old_height; 164 165 char *title; // VTermStringFragment buffer 166 size_t title_len; 167 size_t title_size; 168 169 // buf_T instance that acts as a "drawing surface" for libvterm 170 // we can't store a direct reference to the buffer because the 171 // refresh_timer_cb may be called after the buffer was freed, and there's 172 // no way to know if the memory was reused. 173 handle_T buf_handle; 174 bool in_altscreen; 175 // program suspended 176 bool suspended; 177 // program exited 178 bool closed; 179 // when true, the terminal's destruction is already enqueued. 180 bool destroy; 181 182 // some vterm properties 183 bool forward_mouse; 184 int invalid_start, invalid_end; // invalid rows in libvterm screen 185 struct { 186 int row, col; 187 int shape; 188 bool visible; ///< Terminal wants to show cursor. 189 ///< `TerminalState.cursor_visible` indicates whether it is actually shown. 190 bool blink; 191 } cursor; 192 193 struct { 194 bool resize; ///< pending width/height 195 bool cursor; ///< pending cursor shape or blink change 196 StringBuilder *send; ///< When there is a pending TermRequest autocommand, block and store input. 197 MultiQueue *events; ///< Events waiting for refresh. 198 } pending; 199 200 bool theme_updates; ///< Send a theme update notification when 'bg' changes 201 202 bool color_set[16]; 203 204 char *selection_buffer; ///< libvterm selection buffer 205 StringBuilder selection; ///< Growable array containing full selection data 206 207 StringBuilder termrequest_buffer; ///< Growable array containing unfinished request sequence 208 VTermTerminator termrequest_terminator; ///< Terminator (BEL or ST) used in the termrequest 209 210 size_t refcount; // reference count 211 }; 212 213 static VTermScreenCallbacks vterm_screen_callbacks = { 214 .damage = term_damage, 215 .moverect = term_moverect, 216 .movecursor = term_movecursor, 217 .settermprop = term_settermprop, 218 .bell = term_bell, 219 .theme = term_theme, 220 .sb_pushline = term_sb_push, // Called before a line goes offscreen. 221 .sb_popline = term_sb_pop, 222 .sb_clear = term_sb_clear, 223 }; 224 225 static VTermSelectionCallbacks vterm_selection_callbacks = { 226 .set = term_selection_set, 227 // For security reasons we don't support querying the system clipboard from the embedded terminal 228 .query = NULL, 229 }; 230 231 static Set(ptr_t) invalidated_terminals = SET_INIT; 232 233 static void emit_termrequest(void **argv) 234 { 235 handle_T buf_handle = (handle_T)(intptr_t)argv[0]; 236 char *sequence = argv[1]; 237 size_t sequence_length = (size_t)argv[2]; 238 StringBuilder *pending_send = argv[3]; 239 int row = (int)(intptr_t)argv[4]; 240 int col = (int)(intptr_t)argv[5]; 241 size_t sb_deleted = (size_t)(intptr_t)argv[6]; 242 VTermTerminator terminator = (VTermTerminator)(intptr_t)argv[7]; 243 244 buf_T *buf = handle_get_buffer(buf_handle); 245 if (!buf || buf->terminal == NULL) { // Terminal already closed. 246 xfree(sequence); 247 return; 248 } 249 Terminal *term = buf->terminal; 250 251 if (term->sb_pending > 0) { 252 // Don't emit the event while there is pending scrollback because we need 253 // the buffer contents to be fully updated. If this is the case, schedule 254 // the event onto the pending queue where it will be executed after the 255 // terminal is refreshed and the pending scrollback is cleared. 256 multiqueue_put(term->pending.events, emit_termrequest, argv[0], argv[1], argv[2], 257 argv[3], argv[4], argv[5], argv[6], argv[7]); 258 return; 259 } 260 261 set_vim_var_string(VV_TERMREQUEST, sequence, (ptrdiff_t)sequence_length); 262 263 MAXSIZE_TEMP_ARRAY(cursor, 2); 264 ADD_C(cursor, INTEGER_OBJ(row - (int64_t)(term->sb_deleted - sb_deleted))); 265 ADD_C(cursor, INTEGER_OBJ(col)); 266 267 MAXSIZE_TEMP_DICT(data, 3); 268 String termrequest = { .data = sequence, .size = sequence_length }; 269 PUT_C(data, "sequence", STRING_OBJ(termrequest)); 270 PUT_C(data, "cursor", ARRAY_OBJ(cursor)); 271 PUT_C(data, "terminator", 272 terminator == 273 VTERM_TERMINATOR_BEL ? STATIC_CSTR_AS_OBJ("\x07") : STATIC_CSTR_AS_OBJ("\x1b\\")); 274 275 term->refcount++; 276 apply_autocmds_group(EVENT_TERMREQUEST, NULL, NULL, true, AUGROUP_ALL, buf, NULL, 277 &DICT_OBJ(data)); 278 term->refcount--; 279 xfree(sequence); 280 281 StringBuilder *term_pending_send = term->pending.send; 282 term->pending.send = NULL; 283 if (kv_size(*pending_send)) { 284 terminal_send(term, pending_send->items, pending_send->size); 285 kv_destroy(*pending_send); 286 } 287 if (term_pending_send != pending_send) { 288 term->pending.send = term_pending_send; 289 } 290 xfree(pending_send); 291 292 // Terminal buffer closed during TermRequest in Normal mode: destroy the terminal. 293 // In Terminal mode term->refcount should still be non-zero here. 294 if (term->buf_handle == 0 && !term->refcount) { 295 term->destroy = true; 296 term->opts.close_cb(term->opts.data); 297 } 298 } 299 300 static void schedule_termrequest(Terminal *term) 301 { 302 term->pending.send = xmalloc(sizeof(StringBuilder)); 303 kv_init(*term->pending.send); 304 305 int line = row_to_linenr(term, term->cursor.row); 306 multiqueue_put(main_loop.events, emit_termrequest, (void *)(intptr_t)term->buf_handle, 307 xmemdup(term->termrequest_buffer.items, term->termrequest_buffer.size), 308 (void *)(intptr_t)term->termrequest_buffer.size, term->pending.send, 309 (void *)(intptr_t)line, (void *)(intptr_t)term->cursor.col, 310 (void *)(intptr_t)term->sb_deleted, 311 (void *)(intptr_t)term->termrequest_terminator); 312 } 313 314 static int parse_osc8(const char *str, int *attr) 315 FUNC_ATTR_NONNULL_ALL 316 { 317 // Parse the URI from the OSC 8 sequence and add the URL to our URL set. 318 // Skip the ID, we don't use it (for now) 319 size_t i = 0; 320 for (; str[i] != NUL; i++) { 321 if (str[i] == ';') { 322 break; 323 } 324 } 325 326 if (str[i] != ';') { 327 // Invalid OSC sequence 328 return 0; 329 } 330 331 // Move past the semicolon 332 i++; 333 334 if (str[i] == NUL) { 335 // Empty OSC 8, no URL 336 *attr = 0; 337 return 1; 338 } 339 340 *attr = hl_add_url(0, str + i); 341 return 1; 342 } 343 344 static int on_osc(int command, VTermStringFragment frag, void *user) 345 FUNC_ATTR_NONNULL_ALL 346 { 347 Terminal *term = user; 348 349 if (frag.str == NULL || frag.len == 0) { 350 return 0; 351 } 352 353 if (command != 8 && !has_event(EVENT_TERMREQUEST)) { 354 return 1; 355 } 356 357 if (frag.initial) { 358 kv_size(term->termrequest_buffer) = 0; 359 kv_printf(term->termrequest_buffer, "\x1b]%d;", command); 360 } 361 kv_concat_len(term->termrequest_buffer, frag.str, frag.len); 362 if (frag.final) { 363 term->termrequest_terminator = frag.terminator; 364 if (has_event(EVENT_TERMREQUEST)) { 365 schedule_termrequest(term); 366 } 367 if (command == 8) { 368 kv_push(term->termrequest_buffer, NUL); 369 const size_t off = STRLEN_LITERAL("\x1b]8;"); 370 int attr = 0; 371 if (parse_osc8(term->termrequest_buffer.items + off, &attr)) { 372 VTermState *state = vterm_obtain_state(term->vt); 373 VTermValue value = { .number = attr }; 374 vterm_state_set_penattr(state, VTERM_ATTR_URI, VTERM_VALUETYPE_INT, &value); 375 } 376 } 377 } 378 return 1; 379 } 380 381 static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) 382 { 383 Terminal *term = user; 384 385 if (command == NULL || frag.str == NULL) { 386 return 0; 387 } 388 if (!has_event(EVENT_TERMREQUEST)) { 389 return 1; 390 } 391 392 if (frag.initial) { 393 kv_size(term->termrequest_buffer) = 0; 394 kv_printf(term->termrequest_buffer, "\x1bP%.*s", (int)commandlen, command); 395 } 396 kv_concat_len(term->termrequest_buffer, frag.str, frag.len); 397 if (frag.final) { 398 term->termrequest_terminator = frag.terminator; 399 schedule_termrequest(term); 400 } 401 return 1; 402 } 403 404 static int on_apc(VTermStringFragment frag, void *user) 405 { 406 Terminal *term = user; 407 if (frag.str == NULL || frag.len == 0) { 408 return 0; 409 } 410 411 if (!has_event(EVENT_TERMREQUEST)) { 412 return 1; 413 } 414 415 if (frag.initial) { 416 kv_size(term->termrequest_buffer) = 0; 417 kv_printf(term->termrequest_buffer, "\x1b_"); 418 } 419 kv_concat_len(term->termrequest_buffer, frag.str, frag.len); 420 if (frag.final) { 421 term->termrequest_terminator = frag.terminator; 422 schedule_termrequest(term); 423 } 424 return 1; 425 } 426 427 static VTermStateFallbacks vterm_fallbacks = { 428 .control = NULL, 429 .csi = NULL, 430 .osc = on_osc, 431 .dcs = on_dcs, 432 .apc = on_apc, 433 .pm = NULL, 434 .sos = NULL, 435 }; 436 437 void terminal_init(void) 438 { 439 time_watcher_init(&main_loop, &refresh_timer, NULL); 440 // refresh_timer_cb will redraw the screen which can call vimscript 441 refresh_timer.events = multiqueue_new_child(main_loop.events); 442 } 443 444 void terminal_teardown(void) 445 { 446 time_watcher_stop(&refresh_timer); 447 multiqueue_free(refresh_timer.events); 448 time_watcher_close(&refresh_timer, NULL); 449 set_destroy(ptr_t, &invalidated_terminals); 450 // terminal_destroy might be called after terminal_teardown is invoked 451 // make sure it is in an empty, valid state 452 invalidated_terminals = (Set(ptr_t)) SET_INIT; 453 } 454 455 static void term_output_callback(const char *s, size_t len, void *user_data) 456 { 457 terminal_send((Terminal *)user_data, s, len); 458 } 459 460 /// Allocates a terminal's scrollback buffer if it hasn't been allocated yet. 461 /// Does nothing if it's already allocated, unlike adjust_scrollback(). 462 /// 463 /// @param term Terminal instance. 464 /// @param buf The terminal's buffer, or NULL to get it from buf_handle. 465 /// 466 /// @return whether the terminal now has a scrollback buffer. 467 static bool term_may_alloc_scrollback(Terminal *term, buf_T *buf) 468 { 469 if (term->sb_buffer != NULL) { 470 return true; 471 } 472 if (buf == NULL) { 473 buf = handle_get_buffer(term->buf_handle); 474 if (buf == NULL) { // No need to allocate scrollback if buffer is deleted. 475 return false; 476 } 477 } 478 479 if (buf->b_p_scbk < 1) { 480 buf->b_p_scbk = SB_MAX; 481 } 482 // Configure the scrollback buffer. 483 term->sb_size = (size_t)buf->b_p_scbk; 484 term->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * term->sb_size); 485 return true; 486 } 487 488 // public API {{{ 489 490 /// Allocates a terminal instance and initializes terminal properties. 491 /// 492 /// The PTY process (TerminalOptions.data) was already started by jobstart(), 493 /// via ex_terminal() or the term:// BufReadCmd. 494 /// 495 /// @param buf Buffer used for presentation of the terminal. 496 /// @param opts PTY process channel, various terminal properties and callbacks. 497 /// 498 /// @return the terminal instance. 499 Terminal *terminal_alloc(buf_T *buf, TerminalOptions opts) 500 FUNC_ATTR_NONNULL_ALL 501 { 502 // Create a new terminal instance and configure it 503 Terminal *term = xcalloc(1, sizeof(Terminal)); 504 term->opts = opts; 505 506 // Associate the terminal instance with the new buffer 507 term->buf_handle = buf->handle; 508 buf->terminal = term; 509 // Create VTerm 510 term->vt = vterm_new(opts.height, opts.width); 511 vterm_set_utf8(term->vt, 1); 512 // Setup state 513 VTermState *state = vterm_obtain_state(term->vt); 514 // Set up screen 515 term->vts = vterm_obtain_screen(term->vt); 516 vterm_screen_enable_altscreen(term->vts, true); 517 vterm_screen_enable_reflow(term->vts, true); 518 // delete empty lines at the end of the buffer 519 vterm_screen_set_callbacks(term->vts, &vterm_screen_callbacks, term); 520 vterm_screen_set_unrecognised_fallbacks(term->vts, &vterm_fallbacks, term); 521 vterm_screen_set_damage_merge(term->vts, VTERM_DAMAGE_SCROLL); 522 vterm_screen_reset(term->vts, 1); 523 vterm_output_set_callback(term->vt, term_output_callback, term); 524 525 term->selection_buffer = xcalloc(SELECTIONBUF_SIZE, 1); 526 vterm_state_set_selection_callbacks(state, &vterm_selection_callbacks, term, 527 term->selection_buffer, SELECTIONBUF_SIZE); 528 529 VTermValue cursor_shape; 530 switch (shape_table[SHAPE_IDX_TERM].shape) { 531 case SHAPE_BLOCK: 532 cursor_shape.number = VTERM_PROP_CURSORSHAPE_BLOCK; 533 break; 534 case SHAPE_HOR: 535 cursor_shape.number = VTERM_PROP_CURSORSHAPE_UNDERLINE; 536 break; 537 case SHAPE_VER: 538 cursor_shape.number = VTERM_PROP_CURSORSHAPE_BAR_LEFT; 539 break; 540 } 541 vterm_state_set_termprop(state, VTERM_PROP_CURSORSHAPE, &cursor_shape); 542 543 VTermValue cursor_blink; 544 if (shape_table[SHAPE_IDX_TERM].blinkon != 0 && shape_table[SHAPE_IDX_TERM].blinkoff != 0) { 545 cursor_blink.boolean = true; 546 } else { 547 cursor_blink.boolean = false; 548 } 549 vterm_state_set_termprop(state, VTERM_PROP_CURSORBLINK, &cursor_blink); 550 551 // Force a initial refresh of the screen to ensure the buffer will always 552 // have as many lines as screen rows when refresh_scrollback() is called. 553 term->invalid_start = 0; 554 term->invalid_end = opts.height; 555 556 // Create a separate queue for events which need to wait for a terminal 557 // refresh. We cannot reschedule events back onto the main queue because this 558 // can create an infinite loop (#32753). 559 // This queue is never processed directly: when the terminal is refreshed, all 560 // events from this queue are copied back onto the main event queue. 561 term->pending.events = multiqueue_new(NULL, NULL); 562 563 if (!(buf->b_ml.ml_flags & ML_EMPTY)) { 564 linenr_T line_count = buf->b_ml.ml_line_count; 565 while (!(buf->b_ml.ml_flags & ML_EMPTY)) { 566 ml_delete_buf(buf, 1, false); 567 } 568 deleted_lines_buf(buf, 1, line_count); 569 } 570 term->old_height = 1; 571 572 return term; 573 } 574 575 /// Triggers TermOpen and allocates terminal scrollback buffer. 576 /// 577 /// @param termpp Pointer to the terminal channel's `term` field. 578 /// @param buf Buffer used for presentation of the terminal. 579 void terminal_open(Terminal **termpp, buf_T *buf) 580 FUNC_ATTR_NONNULL_ALL 581 { 582 Terminal *term = *termpp; 583 assert(term != NULL); 584 585 aco_save_T aco; 586 aucmd_prepbuf(&aco, buf); 587 588 if (term->sb_buffer != NULL) { 589 // If scrollback has been allocated by autocommands between terminal_alloc() 590 // and terminal_open(), it also needs to be refreshed. 591 refresh_scrollback(term, buf); 592 } else { 593 assert(term->invalid_start >= 0); 594 } 595 refresh_screen(term, buf); 596 buf->b_locked++; 597 set_option_value(kOptBuftype, STATIC_CSTR_AS_OPTVAL("terminal"), OPT_LOCAL); 598 buf->b_locked--; 599 600 if (buf->b_ffname != NULL) { 601 buf_set_term_title(buf, buf->b_ffname, strlen(buf->b_ffname)); 602 } 603 RESET_BINDING(curwin); 604 // Reset cursor in current window. 605 curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 }; 606 607 // Apply TermOpen autocmds _before_ configuring the scrollback buffer, to avoid 608 // over-allocating in case TermOpen reduces 'scrollback'. 609 // In the rare case where TermOpen polls for events, the scrollback buffer will be 610 // allocated anyway if needed. 611 apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf); 612 613 aucmd_restbuf(&aco); 614 615 if (*termpp == NULL || term->buf_handle == 0) { 616 return; // Terminal has already been destroyed. 617 } 618 619 // Local 'scrollback' _after_ autocmds. 620 if (!term_may_alloc_scrollback(term, buf)) { 621 abort(); 622 } 623 624 VTermState *state = vterm_obtain_state(term->vt); 625 // Configure the color palette. Try to get the color from: 626 // 627 // - b:terminal_color_{NUM} 628 // - g:terminal_color_{NUM} 629 // - the VTerm instance 630 for (int i = 0; i < 16; i++) { 631 char var[64]; 632 snprintf(var, sizeof(var), "terminal_color_%d", i); 633 char *name = get_config_string(buf, var); 634 if (name) { 635 int dummy; 636 RgbValue color_val = name_to_color(name, &dummy); 637 638 if (color_val != -1) { 639 VTermColor color; 640 vterm_color_rgb(&color, 641 (uint8_t)((color_val >> 16) & 0xFF), 642 (uint8_t)((color_val >> 8) & 0xFF), 643 (uint8_t)((color_val >> 0) & 0xFF)); 644 vterm_state_set_palette_color(state, i, &color); 645 term->color_set[i] = true; 646 } 647 } 648 } 649 } 650 651 /// Closes the Terminal buffer. 652 /// 653 /// May call terminal_destroy, which sets caller storage to NULL. 654 void terminal_close(Terminal **termpp, int status) 655 FUNC_ATTR_NONNULL_ALL 656 { 657 Terminal *term = *termpp; 658 659 #ifdef EXITFREE 660 if (entered_free_all_mem) { 661 // If called from buf_close_terminal() inside free_all_mem(), the main loop has 662 // already been freed, so it is not safe to call the close callback here. 663 terminal_destroy(termpp); 664 return; 665 } 666 #endif 667 668 if (term->destroy) { // Destruction already scheduled on the main loop. 669 return; 670 } 671 672 bool only_destroy = false; 673 674 if (term->closed) { 675 // If called from buf_close_terminal() after the process has already exited, we 676 // only need to call the close callback to clean up the terminal object. 677 only_destroy = true; 678 } else { 679 // flush any pending changes to the buffer 680 if (!exiting) { 681 block_autocmds(); 682 refresh_terminal(term); 683 unblock_autocmds(); 684 } 685 term->closed = true; 686 } 687 688 buf_T *buf = handle_get_buffer(term->buf_handle); 689 690 if (status == -1 || exiting) { 691 // If this was called by buf_close_terminal() (status is -1), or if exiting, we 692 // must inform the buffer the terminal no longer exists so that buf_freeall() 693 // won't call buf_close_terminal() again. 694 // If inside Terminal mode event handling, setting buf_handle to 0 also 695 // informs terminal_enter() to call the close callback before returning. 696 term->buf_handle = 0; 697 if (buf) { 698 buf->terminal = NULL; 699 } 700 if (!term->refcount) { 701 // Not inside Terminal mode event handling. 702 // We should not wait for the user to press a key. 703 term->destroy = true; 704 term->opts.close_cb(term->opts.data); 705 } 706 } else if (!only_destroy) { 707 // Associated channel has been closed and the editor is not exiting. 708 // Do not call the close callback now. Wait for the user to press a key. 709 char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; 710 if (((Channel *)term->opts.data)->streamtype == kChannelStreamInternal) { 711 snprintf(msg, sizeof msg, "\r\n[Terminal closed]"); 712 } else { 713 snprintf(msg, sizeof msg, "\r\n[Process exited %d]", status); 714 } 715 terminal_receive(term, msg, strlen(msg)); 716 } 717 718 if (only_destroy) { 719 return; 720 } 721 722 if (buf && !is_autocmd_blocked()) { 723 save_v_event_T save_v_event; 724 dict_T *dict = get_v_event(&save_v_event); 725 tv_dict_add_nr(dict, S_LEN("status"), status); 726 tv_dict_set_keys_readonly(dict); 727 apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf); 728 restore_v_event(dict, &save_v_event); 729 } 730 } 731 732 static void terminal_state_change_event(void **argv) 733 { 734 handle_T buf_handle = (handle_T)(intptr_t)argv[0]; 735 buf_T *buf = handle_get_buffer(buf_handle); 736 if (buf && buf->terminal) { 737 // Don't change the actual terminal content to indicate the suspended state here, 738 // as unlike the process exit case the change needs to be reversed on resume. 739 // Instead, the code in win_update() will add a "[Process suspended]" virtual text 740 // at the botton-left of the buffer. 741 redraw_buf_line_later(buf, buf->b_ml.ml_line_count, false); 742 } 743 } 744 745 /// Updates the suspended state of the terminal program. 746 void terminal_set_state(Terminal *term, bool suspended) 747 FUNC_ATTR_NONNULL_ALL 748 { 749 if (term->suspended != suspended) { 750 // Trigger a main loop iteration to redraw the buffer. 751 multiqueue_put(refresh_timer.events, terminal_state_change_event, 752 (void *)(intptr_t)term->buf_handle); 753 } 754 term->suspended = suspended; 755 } 756 757 void terminal_check_size(Terminal *term) 758 FUNC_ATTR_NONNULL_ALL 759 { 760 if (term->closed) { 761 return; 762 } 763 764 int curwidth, curheight; 765 vterm_get_size(term->vt, &curheight, &curwidth); 766 uint16_t width = 0; 767 uint16_t height = 0; 768 769 // Check if there is a window that displays the terminal and find the maximum width and height. 770 // Skip the autocommand window which isn't actually displayed. 771 FOR_ALL_TAB_WINDOWS(tp, wp) { 772 if (is_aucmd_win(wp)) { 773 continue; 774 } 775 if (wp->w_buffer && wp->w_buffer->terminal == term) { 776 const uint16_t win_width = 777 (uint16_t)(MAX(0, wp->w_view_width - win_col_off(wp))); 778 width = MAX(width, win_width); 779 height = (uint16_t)MAX(height, wp->w_view_height); 780 } 781 } 782 783 // if no window displays the terminal, or such all windows are zero-height, 784 // don't resize the terminal. 785 if ((curheight == height && curwidth == width) || height == 0 || width == 0) { 786 return; 787 } 788 789 vterm_set_size(term->vt, height, width); 790 vterm_screen_flush_damage(term->vts); 791 term->pending.resize = true; 792 invalidate_terminal(term, -1, -1); 793 } 794 795 static void set_terminal_winopts(TerminalState *const s) 796 FUNC_ATTR_NONNULL_ALL 797 { 798 assert(s->save_curwin_handle == 0); 799 800 // Disable these options in terminal-mode. They are nonsense because cursor is 801 // placed at end of buffer to "follow" output. #11072 802 s->save_curwin_handle = curwin->handle; 803 s->save_w_p_cul = curwin->w_p_cul; 804 s->save_w_p_culopt = NULL; 805 s->save_w_p_culopt_flags = curwin->w_p_culopt_flags; 806 s->save_w_p_cuc = curwin->w_p_cuc; 807 s->save_w_p_so = curwin->w_p_so; 808 s->save_w_p_siso = curwin->w_p_siso; 809 810 if (curwin->w_p_cul && curwin->w_p_culopt_flags & kOptCuloptFlagNumber) { 811 if (!strequal(curwin->w_p_culopt, "number")) { 812 s->save_w_p_culopt = curwin->w_p_culopt; 813 curwin->w_p_culopt = xstrdup("number"); 814 } 815 curwin->w_p_culopt_flags = kOptCuloptFlagNumber; 816 } else { 817 curwin->w_p_cul = false; 818 } 819 curwin->w_p_cuc = false; 820 curwin->w_p_so = 0; 821 curwin->w_p_siso = 0; 822 823 if (curwin->w_p_cuc != s->save_w_p_cuc) { 824 redraw_later(curwin, UPD_SOME_VALID); 825 } else if (curwin->w_p_cul != s->save_w_p_cul 826 || (curwin->w_p_cul && curwin->w_p_culopt_flags != s->save_w_p_culopt_flags)) { 827 redraw_later(curwin, UPD_VALID); 828 } 829 } 830 831 static void unset_terminal_winopts(TerminalState *const s) 832 FUNC_ATTR_NONNULL_ALL 833 { 834 assert(s->save_curwin_handle != 0); 835 836 win_T *const wp = handle_get_window(s->save_curwin_handle); 837 if (!wp) { 838 goto end; 839 } 840 841 winopt_T *winopts = NULL; 842 if (wp->w_buffer->handle != s->term->buf_handle) { // Buffer no longer in "wp". 843 buf_T *buf = handle_get_buffer(s->term->buf_handle); 844 if (buf == NULL) { 845 goto end; // Nothing to restore as the buffer was deleted. 846 } 847 for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) { 848 WinInfo *wip = kv_A(buf->b_wininfo, i); 849 if (wip->wi_win == wp && wip->wi_optset) { 850 winopts = &wip->wi_opt; 851 break; 852 } 853 } 854 if (winopts == NULL) { 855 goto end; // Nothing to restore as there is no matching WinInfo. 856 } 857 } else { 858 winopts = &wp->w_onebuf_opt; 859 if (win_valid(wp)) { // No need to redraw if window not in curtab. 860 if (s->save_w_p_cuc != wp->w_p_cuc) { 861 redraw_later(wp, UPD_SOME_VALID); 862 } else if (s->save_w_p_cul != wp->w_p_cul 863 || (s->save_w_p_cul && s->save_w_p_culopt_flags != wp->w_p_culopt_flags)) { 864 redraw_later(wp, UPD_VALID); 865 } 866 } 867 wp->w_p_culopt_flags = s->save_w_p_culopt_flags; 868 } 869 870 if (s->save_w_p_culopt) { 871 free_string_option(winopts->wo_culopt); 872 winopts->wo_culopt = s->save_w_p_culopt; 873 s->save_w_p_culopt = NULL; 874 } 875 winopts->wo_cul = s->save_w_p_cul; 876 winopts->wo_cuc = s->save_w_p_cuc; 877 winopts->wo_so = s->save_w_p_so; 878 winopts->wo_siso = s->save_w_p_siso; 879 880 end: 881 free_string_option(s->save_w_p_culopt); 882 s->save_curwin_handle = 0; 883 } 884 885 /// Implements MODE_TERMINAL state. :help Terminal-mode 886 bool terminal_enter(void) 887 { 888 buf_T *buf = curbuf; 889 assert(buf->terminal); // Should only be called when curbuf has a terminal. 890 TerminalState s[1] = { 0 }; 891 s->term = buf->terminal; 892 s->cursor_visible = true; // Assume visible; may change via refresh_cursor later. 893 stop_insert_mode = false; 894 895 // Ensure the terminal is properly sized. Ideally window size management 896 // code should always have resized the terminal already, but check here to 897 // be sure. 898 terminal_check_size(s->term); 899 900 int save_state = State; 901 s->save_rd = RedrawingDisabled; 902 State = MODE_TERMINAL; 903 mapped_ctrl_c |= MODE_TERMINAL; // Always map CTRL-C to avoid interrupt. 904 RedrawingDisabled = false; 905 906 set_terminal_winopts(s); 907 908 s->term->pending.cursor = true; // Update the cursor shape table 909 adjust_topline_cursor(s->term, buf, 0); // scroll to end 910 showmode(); 911 ui_cursor_shape(); 912 913 // Tell the terminal it has focus 914 terminal_focus(s->term, true); 915 // Don't fire TextChangedT from changes in Normal mode. 916 curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); 917 918 apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf); 919 may_trigger_modechanged(); 920 921 s->state.execute = terminal_execute; 922 s->state.check = terminal_check; 923 state_enter(&s->state); 924 925 if (!s->got_bsl_o) { 926 restart_edit = 0; 927 } 928 State = save_state; 929 RedrawingDisabled = s->save_rd; 930 if (!s->cursor_visible) { 931 // If cursor was hidden, show it again. Do so right after restoring State. 932 ui_busy_stop(); 933 } 934 935 // Restore the terminal cursor to what is set in 'guicursor' 936 (void)parse_shape_opt(SHAPE_CURSOR); 937 938 unset_terminal_winopts(s); 939 940 // Tell the terminal it lost focus 941 terminal_focus(s->term, false); 942 // Don't fire TextChanged from changes in terminal mode. 943 curbuf->b_last_changedtick = buf_get_changedtick(curbuf); 944 945 if (curbuf->terminal == s->term && !s->close) { 946 terminal_check_cursor(); 947 } 948 if (restart_edit) { 949 showmode(); 950 } else { 951 unshowmode(true); 952 } 953 ui_cursor_shape(); 954 955 // If we're to close the terminal, don't let TermLeave autocommands free it first! 956 if (s->close) { 957 s->term->refcount++; 958 } 959 apply_autocmds(EVENT_TERMLEAVE, NULL, NULL, false, curbuf); 960 if (s->close) { 961 s->term->refcount--; 962 const handle_T buf_handle = s->term->buf_handle; // Callback may free s->term. 963 s->term->destroy = true; 964 s->term->opts.close_cb(s->term->opts.data); 965 if (buf_handle != 0) { 966 do_buffer(DOBUF_WIPE, DOBUF_FIRST, FORWARD, buf_handle, true); 967 } 968 } 969 970 return s->got_bsl_o; 971 } 972 973 static void terminal_check_cursor(void) 974 { 975 Terminal *term = curbuf->terminal; 976 curwin->w_cursor.lnum = MIN(curbuf->b_ml.ml_line_count, 977 row_to_linenr(term, term->cursor.row)); 978 const linenr_T topline = MAX(curbuf->b_ml.ml_line_count - curwin->w_view_height + 1, 1); 979 // Don't update topline if unchanged to avoid unnecessary redraws. 980 if (topline != curwin->w_topline) { 981 set_topline(curwin, topline); 982 } 983 if (term->suspended && (State & MODE_TERMINAL)) { 984 // Put cursor at the "[Process suspended]" text to hint that pressing a key will 985 // change the suspended state. 986 curwin->w_cursor = (pos_T){ .lnum = curbuf->b_ml.ml_line_count }; 987 } else { 988 // Nudge cursor when returning to normal-mode. 989 int off = (State & MODE_TERMINAL) ? 0 : (curwin->w_p_rl ? 1 : -1); 990 coladvance(curwin, MAX(0, term->cursor.col + off)); 991 } 992 } 993 994 static bool terminal_check_focus(TerminalState *const s) 995 FUNC_ATTR_NONNULL_ALL 996 { 997 if (curbuf->terminal == NULL) { 998 return false; 999 } 1000 1001 if (s->save_curwin_handle != curwin->handle) { 1002 // Terminal window changed, update window options. 1003 unset_terminal_winopts(s); 1004 set_terminal_winopts(s); 1005 } 1006 if (s->term != curbuf->terminal) { 1007 // Active terminal changed, flush terminal's cursor state to the UI. 1008 terminal_focus(s->term, false); 1009 if (s->close) { 1010 s->term->destroy = true; 1011 s->term->opts.close_cb(s->term->opts.data); 1012 s->close = false; 1013 } 1014 1015 s->term = curbuf->terminal; 1016 s->term->pending.cursor = true; 1017 invalidate_terminal(s->term, -1, -1); 1018 terminal_focus(s->term, true); 1019 } 1020 return true; 1021 } 1022 1023 /// Function executed before each iteration of terminal mode. 1024 /// 1025 /// @return: 1026 /// 1 if the iteration should continue normally 1027 /// 0 if the main loop must exit 1028 static int terminal_check(VimState *state) 1029 { 1030 TerminalState *const s = (TerminalState *)state; 1031 1032 // Shouldn't reach here when pressing a key to close the terminal buffer. 1033 assert(!s->close || (s->term->buf_handle == 0 && s->term != curbuf->terminal)); 1034 1035 if (stop_insert_mode || !terminal_check_focus(s)) { 1036 return 0; 1037 } 1038 1039 terminal_check_refresh(); 1040 1041 // Validate topline and cursor position for autocommands. Especially important for WinScrolled. 1042 terminal_check_cursor(); 1043 validate_cursor(curwin); 1044 1045 // Don't let autocommands free the terminal from under our fingers. 1046 s->term->refcount++; 1047 if (has_event(EVENT_TEXTCHANGEDT) 1048 && curbuf->b_last_changedtick_i != buf_get_changedtick(curbuf)) { 1049 apply_autocmds(EVENT_TEXTCHANGEDT, NULL, NULL, false, curbuf); 1050 curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf); 1051 } 1052 may_trigger_win_scrolled_resized(); 1053 s->term->refcount--; 1054 if (s->term->buf_handle == 0) { 1055 s->close = true; 1056 } 1057 1058 // Autocommands above may have changed focus, scrolled, or moved the cursor. 1059 if (!terminal_check_focus(s)) { 1060 return 0; 1061 } 1062 terminal_check_cursor(); 1063 validate_cursor(curwin); 1064 1065 show_cursor_info_later(false); 1066 if (must_redraw) { 1067 update_screen(); 1068 } else { 1069 redraw_statuslines(); 1070 if (clear_cmdline || redraw_cmdline || redraw_mode) { 1071 showmode(); // clear cmdline and show mode 1072 } 1073 } 1074 1075 setcursor(); 1076 refresh_cursor(s->term, &s->cursor_visible); 1077 ui_flush(); 1078 return 1; 1079 } 1080 1081 /// Processes one char of terminal-mode input. 1082 static int terminal_execute(VimState *state, int key) 1083 { 1084 TerminalState *s = (TerminalState *)state; 1085 1086 // Check for certain control keys like Ctrl-C and Ctrl-\. We still send the 1087 // unmerged key and modifiers to the terminal. 1088 int tmp_mod_mask = mod_mask; 1089 int mod_key = merge_modifiers(key, &tmp_mod_mask); 1090 1091 switch (mod_key) { 1092 case K_LEFTMOUSE: 1093 case K_LEFTDRAG: 1094 case K_LEFTRELEASE: 1095 case K_MIDDLEMOUSE: 1096 case K_MIDDLEDRAG: 1097 case K_MIDDLERELEASE: 1098 case K_RIGHTMOUSE: 1099 case K_RIGHTDRAG: 1100 case K_RIGHTRELEASE: 1101 case K_X1MOUSE: 1102 case K_X1DRAG: 1103 case K_X1RELEASE: 1104 case K_X2MOUSE: 1105 case K_X2DRAG: 1106 case K_X2RELEASE: 1107 case K_MOUSEDOWN: 1108 case K_MOUSEUP: 1109 case K_MOUSELEFT: 1110 case K_MOUSERIGHT: 1111 case K_MOUSEMOVE: 1112 if (send_mouse_event(s->term, key)) { 1113 return 0; 1114 } 1115 break; 1116 1117 case K_PASTE_START: 1118 paste_repeat(1); 1119 break; 1120 1121 case K_EVENT: 1122 // We cannot let an event free the terminal yet. It is still needed. 1123 s->term->refcount++; 1124 state_handle_k_event(); 1125 s->term->refcount--; 1126 if (s->term->buf_handle == 0) { 1127 s->close = true; 1128 } 1129 break; 1130 1131 case K_COMMAND: 1132 do_cmdline(NULL, getcmdkeycmd, NULL, 0); 1133 break; 1134 1135 case K_LUA: 1136 map_execute_lua(false, false); 1137 break; 1138 1139 case K_IGNORE: 1140 case K_NOP: 1141 // Do not interrupt a Ctrl-\ sequence or close a finished terminal. 1142 break; 1143 1144 case Ctrl_N: 1145 if (s->got_bsl) { 1146 return 0; 1147 } 1148 FALLTHROUGH; 1149 1150 case Ctrl_O: 1151 if (s->got_bsl) { 1152 s->got_bsl_o = true; 1153 restart_edit = 'I'; 1154 return 0; 1155 } 1156 FALLTHROUGH; 1157 1158 default: 1159 if (mod_key == Ctrl_C) { 1160 // terminal_enter() always sets `mapped_ctrl_c` to avoid `got_int`. 8eeda7169aa4 1161 // But `got_int` may be set elsewhere, e.g. by interrupt() or an autocommand, 1162 // so ensure that it is cleared. 1163 got_int = false; 1164 } 1165 if (mod_key == Ctrl_BSL && !s->got_bsl) { 1166 s->got_bsl = true; 1167 break; 1168 } 1169 if (s->term->suspended) { 1170 s->term->opts.resume_cb(s->term->opts.data); 1171 // XXX: detecting continued process via waitpid() on SIGCHLD doesn't always work 1172 // (e.g. on macOS), so also consider it continued after sending SIGCONT. 1173 terminal_set_state(s->term, false); 1174 break; 1175 } 1176 if (s->term->closed) { 1177 s->close = true; 1178 return 0; 1179 } 1180 1181 s->got_bsl = false; 1182 terminal_send_key(s->term, key); 1183 } 1184 1185 return 1; 1186 } 1187 1188 /// Frees the given Terminal structure and sets the caller storage to NULL (in the spirit of 1189 /// XFREE_CLEAR). 1190 void terminal_destroy(Terminal **termpp) 1191 FUNC_ATTR_NONNULL_ALL 1192 { 1193 Terminal *term = *termpp; 1194 buf_T *buf = handle_get_buffer(term->buf_handle); 1195 if (buf) { 1196 term->buf_handle = 0; 1197 buf->terminal = NULL; 1198 } 1199 1200 if (!term->refcount) { 1201 if (set_has(ptr_t, &invalidated_terminals, term)) { 1202 // flush any pending changes to the buffer 1203 block_autocmds(); 1204 refresh_terminal(term); 1205 unblock_autocmds(); 1206 set_del(ptr_t, &invalidated_terminals, term); 1207 } 1208 for (size_t i = 0; i < term->sb_current; i++) { 1209 xfree(term->sb_buffer[i]); 1210 } 1211 xfree(term->sb_buffer); 1212 xfree(term->title); 1213 xfree(term->selection_buffer); 1214 kv_destroy(term->selection); 1215 kv_destroy(term->termrequest_buffer); 1216 vterm_free(term->vt); 1217 xfree(term->pending.send); 1218 multiqueue_free(term->pending.events); 1219 xfree(term); 1220 *termpp = NULL; // coverity[dead-store] 1221 } 1222 } 1223 1224 static void terminal_send(Terminal *term, const char *data, size_t size) 1225 { 1226 if (term->closed) { 1227 return; 1228 } 1229 if (term->pending.send) { 1230 kv_concat_len(*term->pending.send, data, size); 1231 return; 1232 } 1233 term->opts.write_cb(data, size, term->opts.data); 1234 } 1235 1236 static bool is_filter_char(int c) 1237 { 1238 unsigned flag = 0; 1239 switch (c) { 1240 case 0x08: 1241 flag = kOptTpfFlagBS; 1242 break; 1243 case 0x09: 1244 flag = kOptTpfFlagHT; 1245 break; 1246 case 0x0A: 1247 case 0x0D: 1248 break; 1249 case 0x0C: 1250 flag = kOptTpfFlagFF; 1251 break; 1252 case 0x1b: 1253 flag = kOptTpfFlagESC; 1254 break; 1255 case 0x7F: 1256 flag = kOptTpfFlagDEL; 1257 break; 1258 default: 1259 if (c < ' ') { 1260 flag = kOptTpfFlagC0; 1261 } else if (c >= 0x80 && c <= 0x9F) { 1262 flag = kOptTpfFlagC1; 1263 } 1264 } 1265 return !!(tpf_flags & flag); 1266 } 1267 1268 void terminal_paste(int count, String *y_array, size_t y_size) 1269 { 1270 if (y_size == 0) { 1271 return; 1272 } 1273 vterm_keyboard_start_paste(curbuf->terminal->vt); 1274 size_t buff_len = y_array[0].size; 1275 char *buff = xmalloc(buff_len); 1276 for (int i = 0; i < count; i++) { 1277 // feed the lines to the terminal 1278 for (size_t j = 0; j < y_size; j++) { 1279 if (j) { 1280 // terminate the previous line 1281 #ifdef MSWIN 1282 terminal_send(curbuf->terminal, "\r\n", 2); 1283 #else 1284 terminal_send(curbuf->terminal, "\n", 1); 1285 #endif 1286 } 1287 size_t len = y_array[j].size; 1288 if (len > buff_len) { 1289 buff = xrealloc(buff, len); 1290 buff_len = len; 1291 } 1292 char *dst = buff; 1293 char *src = y_array[j].data; 1294 while (*src != NUL) { 1295 len = (size_t)utf_ptr2len(src); 1296 int c = utf_ptr2char(src); 1297 if (!is_filter_char(c)) { 1298 memcpy(dst, src, len); 1299 dst += len; 1300 } 1301 src += len; 1302 } 1303 terminal_send(curbuf->terminal, buff, (size_t)(dst - buff)); 1304 } 1305 } 1306 xfree(buff); 1307 vterm_keyboard_end_paste(curbuf->terminal->vt); 1308 } 1309 1310 static void terminal_send_key(Terminal *term, int c) 1311 { 1312 VTermModifier mod = VTERM_MOD_NONE; 1313 1314 // Convert K_ZERO back to ASCII 1315 if (c == K_ZERO) { 1316 c = Ctrl_AT; 1317 } 1318 1319 VTermKey key = convert_key(&c, &mod); 1320 1321 if (key != VTERM_KEY_NONE) { 1322 vterm_keyboard_key(term->vt, key, mod); 1323 } else if (!IS_SPECIAL(c)) { 1324 vterm_keyboard_unichar(term->vt, (uint32_t)c, mod); 1325 } 1326 } 1327 1328 void terminal_receive(Terminal *term, const char *data, size_t len) 1329 { 1330 if (!data) { 1331 return; 1332 } 1333 1334 if (term->opts.force_crlf) { 1335 StringBuilder crlf_data = KV_INITIAL_VALUE; 1336 1337 for (size_t i = 0; i < len; i++) { 1338 if (data[i] == '\n' && (i == 0 || (i > 0 && data[i - 1] != '\r'))) { 1339 kv_push(crlf_data, '\r'); 1340 } 1341 kv_push(crlf_data, data[i]); 1342 } 1343 1344 vterm_input_write(term->vt, crlf_data.items, kv_size(crlf_data)); 1345 kv_destroy(crlf_data); 1346 } else { 1347 vterm_input_write(term->vt, data, len); 1348 } 1349 vterm_screen_flush_damage(term->vts); 1350 } 1351 1352 static int get_rgb(VTermState *state, VTermColor color) 1353 { 1354 vterm_state_convert_color_to_rgb(state, &color); 1355 return RGB_(color.rgb.red, color.rgb.green, color.rgb.blue); 1356 } 1357 1358 static int get_underline_hl_flag(VTermScreenCellAttrs attrs) 1359 { 1360 switch (attrs.underline) { 1361 case VTERM_UNDERLINE_OFF: 1362 return 0; 1363 case VTERM_UNDERLINE_SINGLE: 1364 return HL_UNDERLINE; 1365 case VTERM_UNDERLINE_DOUBLE: 1366 return HL_UNDERDOUBLE; 1367 case VTERM_UNDERLINE_CURLY: 1368 return HL_UNDERCURL; 1369 default: 1370 return HL_UNDERLINE; 1371 } 1372 } 1373 1374 void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *term_attrs) 1375 { 1376 int height, width; 1377 vterm_get_size(term->vt, &height, &width); 1378 VTermState *state = vterm_obtain_state(term->vt); 1379 assert(linenr); 1380 int row = linenr_to_row(term, linenr); 1381 if (row >= height) { 1382 // Terminal height was decreased but the change wasn't reflected into the 1383 // buffer yet 1384 return; 1385 } 1386 1387 width = MIN(TERM_ATTRS_MAX, width); 1388 for (int col = 0; col < width; col++) { 1389 VTermScreenCell cell; 1390 bool color_valid = fetch_cell(term, row, col, &cell); 1391 bool fg_default = !color_valid || VTERM_COLOR_IS_DEFAULT_FG(&cell.fg); 1392 bool bg_default = !color_valid || VTERM_COLOR_IS_DEFAULT_BG(&cell.bg); 1393 1394 // Get the rgb value set by libvterm. 1395 int vt_fg = fg_default ? -1 : get_rgb(state, cell.fg); 1396 int vt_bg = bg_default ? -1 : get_rgb(state, cell.bg); 1397 1398 bool fg_indexed = VTERM_COLOR_IS_INDEXED(&cell.fg); 1399 bool bg_indexed = VTERM_COLOR_IS_INDEXED(&cell.bg); 1400 1401 int16_t vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0); 1402 int16_t vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0); 1403 1404 bool fg_set = vt_fg_idx && vt_fg_idx <= 16 && term->color_set[vt_fg_idx - 1]; 1405 bool bg_set = vt_bg_idx && vt_bg_idx <= 16 && term->color_set[vt_bg_idx - 1]; 1406 1407 int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0) 1408 | (cell.attrs.dim ? HL_DIM : 0) 1409 | (cell.attrs.blink ? HL_BLINK : 0) 1410 | (cell.attrs.conceal ? HL_CONCEALED : 0) 1411 | (cell.attrs.overline ? HL_OVERLINE : 0) 1412 | (cell.attrs.italic ? HL_ITALIC : 0) 1413 | (cell.attrs.reverse ? HL_INVERSE : 0) 1414 | get_underline_hl_flag(cell.attrs) 1415 | (cell.attrs.strike ? HL_STRIKETHROUGH : 0) 1416 | ((fg_indexed && !fg_set) ? HL_FG_INDEXED : 0) 1417 | ((bg_indexed && !bg_set) ? HL_BG_INDEXED : 0); 1418 1419 int attr_id = 0; 1420 1421 if (hl_attrs || !fg_default || !bg_default) { 1422 attr_id = hl_get_term_attr(&(HlAttrs) { 1423 .cterm_ae_attr = (int32_t)hl_attrs, 1424 .cterm_fg_color = vt_fg_idx, 1425 .cterm_bg_color = vt_bg_idx, 1426 .rgb_ae_attr = (int32_t)hl_attrs, 1427 .rgb_fg_color = vt_fg, 1428 .rgb_bg_color = vt_bg, 1429 .rgb_sp_color = -1, 1430 .hl_blend = -1, 1431 .url = -1, 1432 }); 1433 } 1434 1435 if (cell.uri > 0) { 1436 attr_id = hl_combine_attr(attr_id, cell.uri); 1437 } 1438 1439 term_attrs[col] = attr_id; 1440 } 1441 } 1442 1443 Buffer terminal_buf(const Terminal *term) 1444 FUNC_ATTR_NONNULL_ALL 1445 { 1446 return term->buf_handle; 1447 } 1448 1449 bool terminal_running(const Terminal *term) 1450 FUNC_ATTR_NONNULL_ALL 1451 { 1452 return !term->closed; 1453 } 1454 1455 bool terminal_suspended(const Terminal *term) 1456 FUNC_ATTR_NONNULL_ALL 1457 { 1458 return term->suspended; 1459 } 1460 1461 void terminal_notify_theme(Terminal *term, bool dark) 1462 FUNC_ATTR_NONNULL_ALL 1463 { 1464 if (!term->theme_updates) { 1465 return; 1466 } 1467 1468 char buf[10]; 1469 ssize_t ret = snprintf(buf, sizeof(buf), "\x1b[997;%cn", dark ? '1' : '2'); 1470 assert(ret > 0); 1471 assert((size_t)ret <= sizeof(buf)); 1472 terminal_send(term, buf, (size_t)ret); 1473 } 1474 1475 static void terminal_focus(const Terminal *term, bool focus) 1476 FUNC_ATTR_NONNULL_ALL 1477 { 1478 VTermState *state = vterm_obtain_state(term->vt); 1479 if (focus) { 1480 vterm_state_focus_in(state); 1481 } else { 1482 vterm_state_focus_out(state); 1483 } 1484 } 1485 1486 // }}} 1487 // libvterm callbacks {{{ 1488 1489 static int term_damage(VTermRect rect, void *data) 1490 { 1491 invalidate_terminal(data, rect.start_row, rect.end_row); 1492 return 1; 1493 } 1494 1495 static int term_moverect(VTermRect dest, VTermRect src, void *data) 1496 { 1497 invalidate_terminal(data, MIN(dest.start_row, src.start_row), 1498 MAX(dest.end_row, src.end_row)); 1499 return 1; 1500 } 1501 1502 static int term_movecursor(VTermPos new_pos, VTermPos old_pos, int visible, void *data) 1503 { 1504 Terminal *term = data; 1505 term->cursor.row = new_pos.row; 1506 term->cursor.col = new_pos.col; 1507 invalidate_terminal(term, -1, -1); 1508 return 1; 1509 } 1510 1511 static void buf_set_term_title(buf_T *buf, const char *title, size_t len) 1512 { 1513 if (!buf) { 1514 return; // In case of receiving OSC 2 between buffer close and job exit. 1515 } 1516 1517 Error err = ERROR_INIT; 1518 buf->b_locked++; 1519 dict_set_var(buf->b_vars, 1520 STATIC_CSTR_AS_STRING("term_title"), 1521 STRING_OBJ(((String){ .data = (char *)title, .size = len })), 1522 false, 1523 false, 1524 NULL, 1525 &err); 1526 buf->b_locked--; 1527 api_clear_error(&err); 1528 status_redraw_buf(buf); 1529 } 1530 1531 static int term_settermprop(VTermProp prop, VTermValue *val, void *data) 1532 { 1533 Terminal *term = data; 1534 1535 switch (prop) { 1536 case VTERM_PROP_ALTSCREEN: 1537 term->in_altscreen = val->boolean; 1538 break; 1539 1540 case VTERM_PROP_CURSORVISIBLE: 1541 term->cursor.visible = val->boolean; 1542 invalidate_terminal(term, -1, -1); 1543 break; 1544 1545 case VTERM_PROP_TITLE: { 1546 buf_T *buf = handle_get_buffer(term->buf_handle); // May be NULL 1547 VTermStringFragment frag = val->string; 1548 1549 if (frag.initial && frag.final) { 1550 buf_set_term_title(buf, frag.str, frag.len); 1551 break; 1552 } 1553 1554 if (frag.initial) { 1555 term->title_len = 0; 1556 term->title_size = MAX(frag.len, 1024); 1557 term->title = xmalloc(sizeof(char *) * term->title_size); 1558 } else if (term->title_len + frag.len > term->title_size) { 1559 term->title_size *= 2; 1560 term->title = xrealloc(term->title, sizeof(char *) * term->title_size); 1561 } 1562 1563 memcpy(term->title + term->title_len, frag.str, frag.len); 1564 term->title_len += frag.len; 1565 1566 if (frag.final) { 1567 buf_set_term_title(buf, term->title, term->title_len); 1568 xfree(term->title); 1569 term->title = NULL; 1570 } 1571 break; 1572 } 1573 1574 case VTERM_PROP_MOUSE: 1575 term->forward_mouse = (bool)val->number; 1576 break; 1577 1578 case VTERM_PROP_CURSORBLINK: 1579 term->cursor.blink = val->boolean; 1580 term->pending.cursor = true; 1581 invalidate_terminal(term, -1, -1); 1582 break; 1583 1584 case VTERM_PROP_CURSORSHAPE: 1585 term->cursor.shape = val->number; 1586 term->pending.cursor = true; 1587 invalidate_terminal(term, -1, -1); 1588 break; 1589 1590 case VTERM_PROP_THEMEUPDATES: 1591 term->theme_updates = val->boolean; 1592 break; 1593 1594 default: 1595 return 0; 1596 } 1597 1598 return 1; 1599 } 1600 1601 /// Called when the terminal wants to ring the system bell. 1602 static int term_bell(void *data) 1603 { 1604 vim_beep(kOptBoFlagTerm); 1605 return 1; 1606 } 1607 1608 /// Called when the terminal wants to query the system theme. 1609 static int term_theme(bool *dark, void *data) 1610 FUNC_ATTR_NONNULL_ALL 1611 { 1612 *dark = (*p_bg == 'd'); 1613 return 1; 1614 } 1615 1616 /// Scrollback push handler: called just before a line goes offscreen (and libvterm will forget it), 1617 /// giving us a chance to store it. 1618 /// 1619 /// Code adapted from pangoterm. 1620 static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) 1621 { 1622 Terminal *term = data; 1623 1624 if (!term_may_alloc_scrollback(term, NULL)) { 1625 return 0; 1626 } 1627 assert(term->sb_size > 0); 1628 1629 // copy vterm cells into sb_buffer 1630 size_t c = (size_t)cols; 1631 ScrollbackLine *sbrow = NULL; 1632 if (term->sb_current == term->sb_size) { 1633 if (term->sb_buffer[term->sb_current - 1]->cols == c) { 1634 // Recycle old row if it's the right size 1635 sbrow = term->sb_buffer[term->sb_current - 1]; 1636 } else { 1637 xfree(term->sb_buffer[term->sb_current - 1]); 1638 } 1639 term->sb_deleted++; 1640 1641 // Make room at the start by shifting to the right. 1642 memmove(term->sb_buffer + 1, term->sb_buffer, 1643 sizeof(term->sb_buffer[0]) * (term->sb_current - 1)); 1644 } else if (term->sb_current > 0) { 1645 // Make room at the start by shifting to the right. 1646 memmove(term->sb_buffer + 1, term->sb_buffer, 1647 sizeof(term->sb_buffer[0]) * term->sb_current); 1648 } 1649 1650 if (!sbrow) { 1651 sbrow = xmalloc(sizeof(ScrollbackLine) + c * sizeof(sbrow->cells[0])); 1652 sbrow->cols = c; 1653 } 1654 1655 // New row is added at the start of the storage buffer. 1656 term->sb_buffer[0] = sbrow; 1657 if (term->sb_current < term->sb_size) { 1658 term->sb_current++; 1659 } 1660 1661 if (term->sb_pending < (int)term->sb_size) { 1662 term->sb_pending++; 1663 } 1664 1665 memcpy(sbrow->cells, cells, sizeof(cells[0]) * c); 1666 set_put(ptr_t, &invalidated_terminals, term); 1667 1668 return 1; 1669 } 1670 1671 /// Scrollback pop handler (from pangoterm). 1672 /// 1673 /// @param cols 1674 /// @param cells VTerm state to update. 1675 /// @param data Terminal 1676 static int term_sb_pop(int cols, VTermScreenCell *cells, void *data) 1677 { 1678 Terminal *term = data; 1679 1680 if (!term->sb_current) { 1681 return 0; 1682 } 1683 1684 if (term->sb_pending > 0) { 1685 term->sb_pending--; 1686 } else { 1687 term->old_height++; 1688 } 1689 1690 ScrollbackLine *sbrow = term->sb_buffer[0]; 1691 term->sb_current--; 1692 // Forget the "popped" row by shifting the rest onto it. 1693 memmove(term->sb_buffer, term->sb_buffer + 1, 1694 sizeof(term->sb_buffer[0]) * (term->sb_current)); 1695 1696 size_t cols_to_copy = MIN((size_t)cols, sbrow->cols); 1697 1698 // copy to vterm state 1699 memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy); 1700 for (size_t col = cols_to_copy; col < (size_t)cols; col++) { 1701 cells[col].schar = 0; 1702 cells[col].width = 1; 1703 } 1704 1705 xfree(sbrow); 1706 set_put(ptr_t, &invalidated_terminals, term); 1707 1708 return 1; 1709 } 1710 1711 static int term_sb_clear(void *data) 1712 { 1713 Terminal *term = data; 1714 1715 if (term->in_altscreen || !term->sb_size || !term->sb_current) { 1716 return 1; 1717 } 1718 1719 for (size_t i = 0; i < term->sb_current; i++) { 1720 xfree(term->sb_buffer[i]); 1721 } 1722 1723 term->sb_deleted += term->sb_current; 1724 term->sb_current = 0; 1725 term->sb_pending = 0; 1726 invalidate_terminal(term, -1, -1); 1727 1728 return 1; 1729 } 1730 1731 static void term_clipboard_set(void **argv) 1732 { 1733 VTermSelectionMask mask = (VTermSelectionMask)(long)argv[0]; 1734 char *data = argv[1]; 1735 1736 char regname; 1737 switch (mask) { 1738 case VTERM_SELECTION_CLIPBOARD: 1739 regname = '+'; 1740 break; 1741 case VTERM_SELECTION_PRIMARY: 1742 regname = '*'; 1743 break; 1744 default: 1745 regname = '+'; 1746 break; 1747 } 1748 1749 list_T *lines = tv_list_alloc(1); 1750 tv_list_append_allocated_string(lines, data); 1751 1752 list_T *args = tv_list_alloc(3); 1753 tv_list_append_list(args, lines); 1754 1755 const char regtype = 'v'; 1756 tv_list_append_string(args, ®type, 1); 1757 1758 tv_list_append_string(args, ®name, 1); 1759 eval_call_provider("clipboard", "set", args, true); 1760 } 1761 1762 static int term_selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user) 1763 { 1764 Terminal *term = user; 1765 if (frag.initial) { 1766 kv_size(term->selection) = 0; 1767 } 1768 1769 kv_concat_len(term->selection, frag.str, frag.len); 1770 1771 if (frag.final) { 1772 char *data = xmemdupz(term->selection.items, kv_size(term->selection)); 1773 multiqueue_put(main_loop.events, term_clipboard_set, (void *)mask, data); 1774 } 1775 1776 return 1; 1777 } 1778 1779 // }}} 1780 // input handling {{{ 1781 1782 static void convert_modifiers(int *key, VTermModifier *statep) 1783 { 1784 if (mod_mask & MOD_MASK_SHIFT) { 1785 *statep |= VTERM_MOD_SHIFT; 1786 } 1787 if (mod_mask & MOD_MASK_CTRL) { 1788 *statep |= VTERM_MOD_CTRL; 1789 if (!(mod_mask & MOD_MASK_SHIFT) && *key >= 'A' && *key <= 'Z') { 1790 // vterm interprets CTRL+A as SHIFT+CTRL, change to CTRL+a 1791 *key += ('a' - 'A'); 1792 } 1793 } 1794 if (mod_mask & MOD_MASK_ALT) { 1795 *statep |= VTERM_MOD_ALT; 1796 } 1797 1798 switch (*key) { 1799 case K_S_TAB: 1800 case K_S_UP: 1801 case K_S_DOWN: 1802 case K_S_LEFT: 1803 case K_S_RIGHT: 1804 case K_S_HOME: 1805 case K_S_END: 1806 case K_S_F1: 1807 case K_S_F2: 1808 case K_S_F3: 1809 case K_S_F4: 1810 case K_S_F5: 1811 case K_S_F6: 1812 case K_S_F7: 1813 case K_S_F8: 1814 case K_S_F9: 1815 case K_S_F10: 1816 case K_S_F11: 1817 case K_S_F12: 1818 *statep |= VTERM_MOD_SHIFT; 1819 break; 1820 1821 case K_C_LEFT: 1822 case K_C_RIGHT: 1823 case K_C_HOME: 1824 case K_C_END: 1825 *statep |= VTERM_MOD_CTRL; 1826 break; 1827 } 1828 } 1829 1830 static VTermKey convert_key(int *key, VTermModifier *statep) 1831 { 1832 convert_modifiers(key, statep); 1833 1834 switch (*key) { 1835 case K_BS: 1836 return VTERM_KEY_BACKSPACE; 1837 case K_S_TAB: 1838 FALLTHROUGH; 1839 case TAB: 1840 return VTERM_KEY_TAB; 1841 case Ctrl_M: 1842 return VTERM_KEY_ENTER; 1843 case ESC: 1844 return VTERM_KEY_ESCAPE; 1845 1846 case K_S_UP: 1847 FALLTHROUGH; 1848 case K_UP: 1849 return VTERM_KEY_UP; 1850 case K_S_DOWN: 1851 FALLTHROUGH; 1852 case K_DOWN: 1853 return VTERM_KEY_DOWN; 1854 case K_S_LEFT: 1855 FALLTHROUGH; 1856 case K_C_LEFT: 1857 FALLTHROUGH; 1858 case K_LEFT: 1859 return VTERM_KEY_LEFT; 1860 case K_S_RIGHT: 1861 FALLTHROUGH; 1862 case K_C_RIGHT: 1863 FALLTHROUGH; 1864 case K_RIGHT: 1865 return VTERM_KEY_RIGHT; 1866 1867 case K_INS: 1868 return VTERM_KEY_INS; 1869 case K_DEL: 1870 return VTERM_KEY_DEL; 1871 case K_S_HOME: 1872 FALLTHROUGH; 1873 case K_C_HOME: 1874 FALLTHROUGH; 1875 case K_HOME: 1876 return VTERM_KEY_HOME; 1877 case K_S_END: 1878 FALLTHROUGH; 1879 case K_C_END: 1880 FALLTHROUGH; 1881 case K_END: 1882 return VTERM_KEY_END; 1883 case K_PAGEUP: 1884 return VTERM_KEY_PAGEUP; 1885 case K_PAGEDOWN: 1886 return VTERM_KEY_PAGEDOWN; 1887 1888 case K_K0: 1889 FALLTHROUGH; 1890 case K_KINS: 1891 return VTERM_KEY_KP_0; 1892 case K_K1: 1893 FALLTHROUGH; 1894 case K_KEND: 1895 return VTERM_KEY_KP_1; 1896 case K_K2: 1897 FALLTHROUGH; 1898 case K_KDOWN: 1899 return VTERM_KEY_KP_2; 1900 case K_K3: 1901 FALLTHROUGH; 1902 case K_KPAGEDOWN: 1903 return VTERM_KEY_KP_3; 1904 case K_K4: 1905 FALLTHROUGH; 1906 case K_KLEFT: 1907 return VTERM_KEY_KP_4; 1908 case K_K5: 1909 FALLTHROUGH; 1910 case K_KORIGIN: 1911 return VTERM_KEY_KP_5; 1912 case K_K6: 1913 FALLTHROUGH; 1914 case K_KRIGHT: 1915 return VTERM_KEY_KP_6; 1916 case K_K7: 1917 FALLTHROUGH; 1918 case K_KHOME: 1919 return VTERM_KEY_KP_7; 1920 case K_K8: 1921 FALLTHROUGH; 1922 case K_KUP: 1923 return VTERM_KEY_KP_8; 1924 case K_K9: 1925 FALLTHROUGH; 1926 case K_KPAGEUP: 1927 return VTERM_KEY_KP_9; 1928 case K_KDEL: 1929 FALLTHROUGH; 1930 case K_KPOINT: 1931 return VTERM_KEY_KP_PERIOD; 1932 case K_KENTER: 1933 return VTERM_KEY_KP_ENTER; 1934 case K_KPLUS: 1935 return VTERM_KEY_KP_PLUS; 1936 case K_KMINUS: 1937 return VTERM_KEY_KP_MINUS; 1938 case K_KMULTIPLY: 1939 return VTERM_KEY_KP_MULT; 1940 case K_KDIVIDE: 1941 return VTERM_KEY_KP_DIVIDE; 1942 1943 case K_S_F1: 1944 FALLTHROUGH; 1945 case K_F1: 1946 return VTERM_KEY_FUNCTION(1); 1947 case K_S_F2: 1948 FALLTHROUGH; 1949 case K_F2: 1950 return VTERM_KEY_FUNCTION(2); 1951 case K_S_F3: 1952 FALLTHROUGH; 1953 case K_F3: 1954 return VTERM_KEY_FUNCTION(3); 1955 case K_S_F4: 1956 FALLTHROUGH; 1957 case K_F4: 1958 return VTERM_KEY_FUNCTION(4); 1959 case K_S_F5: 1960 FALLTHROUGH; 1961 case K_F5: 1962 return VTERM_KEY_FUNCTION(5); 1963 case K_S_F6: 1964 FALLTHROUGH; 1965 case K_F6: 1966 return VTERM_KEY_FUNCTION(6); 1967 case K_S_F7: 1968 FALLTHROUGH; 1969 case K_F7: 1970 return VTERM_KEY_FUNCTION(7); 1971 case K_S_F8: 1972 FALLTHROUGH; 1973 case K_F8: 1974 return VTERM_KEY_FUNCTION(8); 1975 case K_S_F9: 1976 FALLTHROUGH; 1977 case K_F9: 1978 return VTERM_KEY_FUNCTION(9); 1979 case K_S_F10: 1980 FALLTHROUGH; 1981 case K_F10: 1982 return VTERM_KEY_FUNCTION(10); 1983 case K_S_F11: 1984 FALLTHROUGH; 1985 case K_F11: 1986 return VTERM_KEY_FUNCTION(11); 1987 case K_S_F12: 1988 FALLTHROUGH; 1989 case K_F12: 1990 return VTERM_KEY_FUNCTION(12); 1991 1992 case K_F13: 1993 return VTERM_KEY_FUNCTION(13); 1994 case K_F14: 1995 return VTERM_KEY_FUNCTION(14); 1996 case K_F15: 1997 return VTERM_KEY_FUNCTION(15); 1998 case K_F16: 1999 return VTERM_KEY_FUNCTION(16); 2000 case K_F17: 2001 return VTERM_KEY_FUNCTION(17); 2002 case K_F18: 2003 return VTERM_KEY_FUNCTION(18); 2004 case K_F19: 2005 return VTERM_KEY_FUNCTION(19); 2006 case K_F20: 2007 return VTERM_KEY_FUNCTION(20); 2008 case K_F21: 2009 return VTERM_KEY_FUNCTION(21); 2010 case K_F22: 2011 return VTERM_KEY_FUNCTION(22); 2012 case K_F23: 2013 return VTERM_KEY_FUNCTION(23); 2014 case K_F24: 2015 return VTERM_KEY_FUNCTION(24); 2016 case K_F25: 2017 return VTERM_KEY_FUNCTION(25); 2018 case K_F26: 2019 return VTERM_KEY_FUNCTION(26); 2020 case K_F27: 2021 return VTERM_KEY_FUNCTION(27); 2022 case K_F28: 2023 return VTERM_KEY_FUNCTION(28); 2024 case K_F29: 2025 return VTERM_KEY_FUNCTION(29); 2026 case K_F30: 2027 return VTERM_KEY_FUNCTION(30); 2028 case K_F31: 2029 return VTERM_KEY_FUNCTION(31); 2030 case K_F32: 2031 return VTERM_KEY_FUNCTION(32); 2032 case K_F33: 2033 return VTERM_KEY_FUNCTION(33); 2034 case K_F34: 2035 return VTERM_KEY_FUNCTION(34); 2036 case K_F35: 2037 return VTERM_KEY_FUNCTION(35); 2038 case K_F36: 2039 return VTERM_KEY_FUNCTION(36); 2040 case K_F37: 2041 return VTERM_KEY_FUNCTION(37); 2042 case K_F38: 2043 return VTERM_KEY_FUNCTION(38); 2044 case K_F39: 2045 return VTERM_KEY_FUNCTION(39); 2046 case K_F40: 2047 return VTERM_KEY_FUNCTION(40); 2048 case K_F41: 2049 return VTERM_KEY_FUNCTION(41); 2050 case K_F42: 2051 return VTERM_KEY_FUNCTION(42); 2052 case K_F43: 2053 return VTERM_KEY_FUNCTION(43); 2054 case K_F44: 2055 return VTERM_KEY_FUNCTION(44); 2056 case K_F45: 2057 return VTERM_KEY_FUNCTION(45); 2058 case K_F46: 2059 return VTERM_KEY_FUNCTION(46); 2060 case K_F47: 2061 return VTERM_KEY_FUNCTION(47); 2062 case K_F48: 2063 return VTERM_KEY_FUNCTION(48); 2064 case K_F49: 2065 return VTERM_KEY_FUNCTION(49); 2066 case K_F50: 2067 return VTERM_KEY_FUNCTION(50); 2068 case K_F51: 2069 return VTERM_KEY_FUNCTION(51); 2070 case K_F52: 2071 return VTERM_KEY_FUNCTION(52); 2072 case K_F53: 2073 return VTERM_KEY_FUNCTION(53); 2074 case K_F54: 2075 return VTERM_KEY_FUNCTION(54); 2076 case K_F55: 2077 return VTERM_KEY_FUNCTION(55); 2078 case K_F56: 2079 return VTERM_KEY_FUNCTION(56); 2080 case K_F57: 2081 return VTERM_KEY_FUNCTION(57); 2082 case K_F58: 2083 return VTERM_KEY_FUNCTION(58); 2084 case K_F59: 2085 return VTERM_KEY_FUNCTION(59); 2086 case K_F60: 2087 return VTERM_KEY_FUNCTION(60); 2088 case K_F61: 2089 return VTERM_KEY_FUNCTION(61); 2090 case K_F62: 2091 return VTERM_KEY_FUNCTION(62); 2092 case K_F63: 2093 return VTERM_KEY_FUNCTION(63); 2094 2095 default: 2096 return VTERM_KEY_NONE; 2097 } 2098 } 2099 2100 static void mouse_action(Terminal *term, int button, int row, int col, bool pressed, 2101 VTermModifier mod) 2102 { 2103 vterm_mouse_move(term->vt, row, col, mod); 2104 if (button) { 2105 vterm_mouse_button(term->vt, button, pressed, mod); 2106 } 2107 } 2108 2109 // process a mouse event while the terminal is focused. return true if the 2110 // terminal should lose focus 2111 static bool send_mouse_event(Terminal *term, int c) 2112 { 2113 int row = mouse_row; 2114 int col = mouse_col; 2115 int grid = mouse_grid; 2116 win_T *mouse_win = mouse_find_win_inner(&grid, &row, &col); 2117 if (mouse_win == NULL) { 2118 goto end; 2119 } 2120 2121 int offset; 2122 if (!term->suspended && !term->closed 2123 && term->forward_mouse && mouse_win->w_buffer->terminal == term && row >= 0 2124 && (grid > 1 || row + mouse_win->w_winbar_height < mouse_win->w_height) 2125 && col >= (offset = win_col_off(mouse_win)) 2126 && (grid > 1 || col < mouse_win->w_width)) { 2127 // event in the terminal window and mouse events was enabled by the 2128 // program. translate and forward the event 2129 int button; 2130 bool pressed = false; 2131 2132 switch (c) { 2133 case K_LEFTDRAG: 2134 case K_LEFTMOUSE: 2135 pressed = true; FALLTHROUGH; 2136 case K_LEFTRELEASE: 2137 button = 1; break; 2138 case K_MIDDLEDRAG: 2139 case K_MIDDLEMOUSE: 2140 pressed = true; FALLTHROUGH; 2141 case K_MIDDLERELEASE: 2142 button = 2; break; 2143 case K_RIGHTDRAG: 2144 case K_RIGHTMOUSE: 2145 pressed = true; FALLTHROUGH; 2146 case K_RIGHTRELEASE: 2147 button = 3; break; 2148 case K_X1DRAG: 2149 case K_X1MOUSE: 2150 pressed = true; FALLTHROUGH; 2151 case K_X1RELEASE: 2152 button = 8; break; 2153 case K_X2DRAG: 2154 case K_X2MOUSE: 2155 pressed = true; FALLTHROUGH; 2156 case K_X2RELEASE: 2157 button = 9; break; 2158 case K_MOUSEDOWN: 2159 pressed = true; button = 4; break; 2160 case K_MOUSEUP: 2161 pressed = true; button = 5; break; 2162 case K_MOUSELEFT: 2163 pressed = true; button = 7; break; 2164 case K_MOUSERIGHT: 2165 pressed = true; button = 6; break; 2166 case K_MOUSEMOVE: 2167 button = 0; break; 2168 default: 2169 return false; 2170 } 2171 2172 VTermModifier mod = VTERM_MOD_NONE; 2173 convert_modifiers(&c, &mod); 2174 mouse_action(term, button, row, col - offset, pressed, mod); 2175 return false; 2176 } 2177 2178 if (c == K_MOUSEUP || c == K_MOUSEDOWN || c == K_MOUSELEFT || c == K_MOUSERIGHT) { 2179 win_T *save_curwin = curwin; 2180 // switch window/buffer to perform the scroll 2181 curwin = mouse_win; 2182 curbuf = curwin->w_buffer; 2183 2184 cmdarg_T cap; 2185 oparg_T oa; 2186 CLEAR_FIELD(cap); 2187 clear_oparg(&oa); 2188 cap.oap = &oa; 2189 2190 switch (cap.cmdchar = c) { 2191 case K_MOUSEUP: 2192 cap.arg = MSCR_UP; 2193 break; 2194 case K_MOUSEDOWN: 2195 cap.arg = MSCR_DOWN; 2196 break; 2197 case K_MOUSELEFT: 2198 cap.arg = MSCR_LEFT; 2199 break; 2200 case K_MOUSERIGHT: 2201 cap.arg = MSCR_RIGHT; 2202 break; 2203 default: 2204 abort(); 2205 } 2206 2207 // Call the common mouse scroll function shared with other modes. 2208 do_mousescroll(&cap); 2209 2210 curwin->w_redr_status = true; 2211 curwin = save_curwin; 2212 curbuf = curwin->w_buffer; 2213 redraw_later(mouse_win, UPD_NOT_VALID); 2214 invalidate_terminal(term, -1, -1); 2215 // Only need to exit focus if the scrolled window is the terminal window 2216 return mouse_win == curwin; 2217 } 2218 2219 end: 2220 // Ignore left release action if it was not forwarded to prevent 2221 // leaving Terminal mode after entering to it using a mouse. 2222 if ((c == K_LEFTRELEASE && mouse_win != NULL && mouse_win->w_buffer->terminal == term) 2223 || c == K_MOUSEMOVE) { 2224 return false; 2225 } 2226 2227 int len = ins_char_typebuf(vgetc_char, vgetc_mod_mask, true); 2228 if (KeyTyped) { 2229 ungetchars(len); 2230 } 2231 return true; 2232 } 2233 2234 // }}} 2235 // terminal buffer refresh & misc {{{ 2236 2237 static void fetch_row(Terminal *term, int row, int end_col) 2238 { 2239 int col = 0; 2240 size_t line_len = 0; 2241 char *ptr = term->textbuf; 2242 2243 while (col < end_col) { 2244 VTermScreenCell cell; 2245 fetch_cell(term, row, col, &cell); 2246 if (cell.schar) { 2247 schar_get_adv(&ptr, cell.schar); 2248 line_len = (size_t)(ptr - term->textbuf); 2249 } else { 2250 *ptr++ = ' '; 2251 } 2252 col += cell.width; 2253 } 2254 2255 // end of line 2256 term->textbuf[line_len] = NUL; 2257 } 2258 2259 static bool fetch_cell(Terminal *term, int row, int col, VTermScreenCell *cell) 2260 { 2261 if (row < 0) { 2262 ScrollbackLine *sbrow = term->sb_buffer[-row - 1]; 2263 if ((size_t)col < sbrow->cols) { 2264 *cell = sbrow->cells[col]; 2265 } else { 2266 // fill the pointer with an empty cell 2267 *cell = (VTermScreenCell) { 2268 .schar = 0, 2269 .width = 1, 2270 }; 2271 return false; 2272 } 2273 } else { 2274 vterm_screen_get_cell(term->vts, (VTermPos){ .row = row, .col = col }, 2275 cell); 2276 } 2277 return true; 2278 } 2279 2280 // queue a terminal instance for refresh 2281 static void invalidate_terminal(Terminal *term, int start_row, int end_row) 2282 { 2283 if (start_row != -1 && end_row != -1) { 2284 term->invalid_start = MIN(term->invalid_start, start_row); 2285 term->invalid_end = MAX(term->invalid_end, end_row); 2286 } 2287 2288 set_put(ptr_t, &invalidated_terminals, term); 2289 if (!refresh_pending) { 2290 time_watcher_start(&refresh_timer, refresh_timer_cb, REFRESH_DELAY, 0); 2291 refresh_pending = true; 2292 } 2293 } 2294 2295 /// Normally refresh_timer_cb() is called when processing main_loop.events, but with 2296 /// partial mappings main_loop.events isn't processed, while terminal buffers still 2297 /// need refreshing after processing a key, so call this function before redrawing. 2298 void terminal_check_refresh(void) 2299 { 2300 multiqueue_process_events(refresh_timer.events); 2301 } 2302 2303 static void refresh_terminal(Terminal *term) 2304 { 2305 buf_T *buf = handle_get_buffer(term->buf_handle); 2306 if (!buf) { 2307 // Destroyed by `buf_freeall()`. Do not do anything else. 2308 return; 2309 } 2310 linenr_T ml_before = buf->b_ml.ml_line_count; 2311 2312 bool resized = refresh_size(term, buf); 2313 refresh_scrollback(term, buf); 2314 refresh_screen(term, buf); 2315 2316 int ml_added = buf->b_ml.ml_line_count - ml_before; 2317 adjust_topline_cursor(term, buf, ml_added); 2318 2319 // Resized window may have scrolled horizontally to keep its cursor in-view using the old terminal 2320 // size. Reset the scroll, and let curs_columns correct it if that sends the cursor out-of-view. 2321 if (resized) { 2322 FOR_ALL_TAB_WINDOWS(tp, wp) { 2323 if (wp->w_buffer == buf && wp->w_leftcol != 0) { 2324 wp->w_leftcol = 0; 2325 curs_columns(wp, true); 2326 } 2327 } 2328 } 2329 2330 // Copy pending events back to the main event queue 2331 multiqueue_move_events(main_loop.events, term->pending.events); 2332 } 2333 2334 static void refresh_cursor(Terminal *term, bool *cursor_visible) 2335 FUNC_ATTR_NONNULL_ALL 2336 { 2337 if (!is_focused(term)) { 2338 return; 2339 } 2340 if (term->cursor.visible != *cursor_visible) { 2341 *cursor_visible = term->cursor.visible; 2342 if (*cursor_visible) { 2343 ui_busy_stop(); 2344 } else { 2345 ui_busy_start(); 2346 } 2347 } 2348 2349 if (!term->pending.cursor) { 2350 return; 2351 } 2352 term->pending.cursor = false; 2353 2354 if (term->cursor.blink) { 2355 // For the TUI, this value doesn't actually matter, as long as it's non-zero. The terminal 2356 // emulator dictates the blink frequency, not the application. 2357 // For GUIs we just pick an arbitrary value, for now. 2358 shape_table[SHAPE_IDX_TERM].blinkon = 500; 2359 shape_table[SHAPE_IDX_TERM].blinkoff = 500; 2360 } else { 2361 shape_table[SHAPE_IDX_TERM].blinkon = 0; 2362 shape_table[SHAPE_IDX_TERM].blinkoff = 0; 2363 } 2364 2365 switch (term->cursor.shape) { 2366 case VTERM_PROP_CURSORSHAPE_BLOCK: 2367 shape_table[SHAPE_IDX_TERM].shape = SHAPE_BLOCK; 2368 break; 2369 case VTERM_PROP_CURSORSHAPE_UNDERLINE: 2370 shape_table[SHAPE_IDX_TERM].shape = SHAPE_HOR; 2371 shape_table[SHAPE_IDX_TERM].percentage = 20; 2372 break; 2373 case VTERM_PROP_CURSORSHAPE_BAR_LEFT: 2374 shape_table[SHAPE_IDX_TERM].shape = SHAPE_VER; 2375 shape_table[SHAPE_IDX_TERM].percentage = 25; 2376 break; 2377 } 2378 2379 ui_mode_info_set(); 2380 } 2381 2382 /// Calls refresh_terminal() on all invalidated_terminals. 2383 static void refresh_timer_cb(TimeWatcher *watcher, void *data) 2384 { 2385 refresh_pending = false; 2386 if (exiting) { // Cannot redraw (requires event loop) during teardown/exit. 2387 return; 2388 } 2389 Terminal *term; 2390 void *stub; (void)(stub); 2391 // don't process autocommands while updating terminal buffers 2392 block_autocmds(); 2393 set_foreach(&invalidated_terminals, term, { 2394 refresh_terminal(term); 2395 }); 2396 set_clear(ptr_t, &invalidated_terminals); 2397 unblock_autocmds(); 2398 } 2399 2400 static bool refresh_size(Terminal *term, buf_T *buf) 2401 { 2402 if (!term->pending.resize || term->closed) { 2403 return false; 2404 } 2405 2406 term->pending.resize = false; 2407 int width, height; 2408 vterm_get_size(term->vt, &height, &width); 2409 term->invalid_start = 0; 2410 term->invalid_end = height; 2411 term->opts.resize_cb((uint16_t)width, (uint16_t)height, term->opts.data); 2412 return true; 2413 } 2414 2415 void on_scrollback_option_changed(Terminal *term) 2416 { 2417 // Scrollback buffer may not exist yet, e.g. if 'scrollback' is set in a TermOpen autocmd. 2418 if (term->sb_buffer != NULL) { 2419 refresh_terminal(term); 2420 } 2421 } 2422 2423 /// Adjusts scrollback storage and the terminal buffer scrollback lines 2424 static void adjust_scrollback(Terminal *term, buf_T *buf) 2425 { 2426 if (buf->b_p_scbk < 1) { // Local 'scrollback' was set to -1. 2427 buf->b_p_scbk = SB_MAX; 2428 } 2429 const size_t scbk = (size_t)buf->b_p_scbk; 2430 assert(term->sb_current < SIZE_MAX); 2431 if (term->sb_pending > 0) { // Pending rows must be processed first. 2432 abort(); 2433 } 2434 2435 // Delete lines exceeding the new 'scrollback' limit. 2436 if (scbk < term->sb_current) { 2437 size_t diff = term->sb_current - scbk; 2438 for (size_t i = 0; i < diff; i++) { 2439 ml_delete_buf(buf, 1, false); 2440 term->sb_current--; 2441 xfree(term->sb_buffer[term->sb_current]); 2442 } 2443 mark_adjust_buf(buf, 1, (linenr_T)diff, MAXLNUM, -(linenr_T)diff, true, 2444 kMarkAdjustTerm, kExtmarkUndo); 2445 deleted_lines_buf(buf, 1, (linenr_T)diff); 2446 } 2447 2448 // Resize the scrollback storage. 2449 size_t sb_region = sizeof(ScrollbackLine *) * scbk; 2450 if (scbk != term->sb_size) { 2451 term->sb_buffer = xrealloc(term->sb_buffer, sb_region); 2452 } 2453 2454 term->sb_size = scbk; 2455 } 2456 2457 // Refresh the scrollback of an invalidated terminal. 2458 static void refresh_scrollback(Terminal *term, buf_T *buf) 2459 { 2460 linenr_T deleted = (linenr_T)(term->sb_deleted - term->old_sb_deleted); 2461 deleted = MIN(deleted, buf->b_ml.ml_line_count); 2462 mark_adjust_buf(buf, 1, deleted, MAXLNUM, -deleted, true, kMarkAdjustTerm, kExtmarkUndo); 2463 term->old_sb_deleted = term->sb_deleted; 2464 2465 int old_height = term->old_height; 2466 int width, height; 2467 vterm_get_size(term->vt, &height, &width); 2468 2469 // Remove deleted scrollback lines at the top, but don't unnecessarily remove 2470 // lines that will be overwritten by refresh_screen(). 2471 while (deleted > 0 && buf->b_ml.ml_line_count > old_height) { 2472 ml_delete_buf(buf, 1, false); 2473 deleted_lines_buf(buf, 1, 1); 2474 deleted--; 2475 } 2476 2477 // Clamp old_height in case buffer lines have been deleted by the user. 2478 old_height = MIN(old_height, buf->b_ml.ml_line_count); 2479 while (term->sb_pending > 0) { 2480 // This means that either the window height has decreased or the screen 2481 // became full and libvterm had to push all rows up. Convert the first 2482 // pending scrollback row into a string and append it just above the visible 2483 // section of the buffer. 2484 fetch_row(term, -term->sb_pending, width); 2485 int buf_index = buf->b_ml.ml_line_count - old_height; 2486 ml_append_buf(buf, buf_index, term->textbuf, 0, false); 2487 appended_lines_buf(buf, buf_index, 1); 2488 term->sb_pending--; 2489 } 2490 2491 int max_line_count = (int)term->sb_current + height; 2492 // Remove extra lines at the bottom. 2493 while (buf->b_ml.ml_line_count > max_line_count) { 2494 ml_delete_buf(buf, buf->b_ml.ml_line_count, false); 2495 deleted_lines_buf(buf, buf->b_ml.ml_line_count, 1); 2496 } 2497 2498 adjust_scrollback(term, buf); 2499 } 2500 2501 // Refresh the screen (visible part of the buffer when the terminal is 2502 // focused) of a invalidated terminal 2503 static void refresh_screen(Terminal *term, buf_T *buf) 2504 { 2505 int changed = 0; 2506 int added = 0; 2507 int height; 2508 int width; 2509 vterm_get_size(term->vt, &height, &width); 2510 // Terminal height may have decreased before `invalid_end` reflects it. 2511 term->invalid_end = MIN(term->invalid_end, height); 2512 2513 // There are no invalid rows. 2514 if (term->invalid_start >= term->invalid_end) { 2515 term->invalid_start = INT_MAX; 2516 term->invalid_end = -1; 2517 return; 2518 } 2519 2520 for (int r = term->invalid_start, linenr = row_to_linenr(term, r); 2521 r < term->invalid_end; r++, linenr++) { 2522 fetch_row(term, r, width); 2523 2524 if (linenr <= buf->b_ml.ml_line_count) { 2525 ml_replace_buf(buf, linenr, term->textbuf, true, false); 2526 changed++; 2527 } else { 2528 ml_append_buf(buf, linenr - 1, term->textbuf, 0, false); 2529 added++; 2530 } 2531 } 2532 term->old_height = height; 2533 2534 int change_start = row_to_linenr(term, term->invalid_start); 2535 int change_end = change_start + changed; 2536 changed_lines(buf, change_start, 0, change_end, added, true); 2537 term->invalid_start = INT_MAX; 2538 term->invalid_end = -1; 2539 } 2540 2541 static void adjust_topline_cursor(Terminal *term, buf_T *buf, int added) 2542 { 2543 linenr_T ml_end = buf->b_ml.ml_line_count; 2544 2545 FOR_ALL_TAB_WINDOWS(tp, wp) { 2546 if (wp->w_buffer == buf) { 2547 if (wp == curwin && is_focused(term)) { 2548 // Move window cursor to terminal cursor's position and "follow" output. 2549 terminal_check_cursor(); 2550 continue; 2551 } 2552 2553 bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end? 2554 if (following) { 2555 // "Follow" the terminal output 2556 wp->w_cursor.lnum = ml_end; 2557 set_topline(wp, MAX(wp->w_cursor.lnum - wp->w_view_height + 1, 1)); 2558 } else { 2559 // Ensure valid cursor for each window displaying this terminal. 2560 wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end); 2561 } 2562 mb_check_adjust_col(wp); 2563 } 2564 } 2565 2566 if (ml_end == buf->b_last_cursor.mark.lnum + added) { 2567 buf->b_last_cursor.mark.lnum = ml_end; 2568 } 2569 2570 for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) { 2571 WinInfo *wip = kv_A(buf->b_wininfo, i); 2572 if (ml_end == wip->wi_mark.mark.lnum + added) { 2573 wip->wi_mark.mark.lnum = ml_end; 2574 } 2575 } 2576 } 2577 2578 static int row_to_linenr(Terminal *term, int row) 2579 { 2580 return row != INT_MAX ? row + (int)term->sb_current + 1 : INT_MAX; 2581 } 2582 2583 static int linenr_to_row(Terminal *term, int linenr) 2584 { 2585 return linenr - (int)term->sb_current - 1; 2586 } 2587 2588 static bool is_focused(Terminal *term) 2589 { 2590 return State & MODE_TERMINAL && curbuf->terminal == term; 2591 } 2592 2593 static char *get_config_string(buf_T *buf, char *key) 2594 { 2595 Error err = ERROR_INIT; 2596 Object obj = dict_get_value(buf->b_vars, cstr_as_string(key), NULL, &err); 2597 api_clear_error(&err); 2598 if (obj.type == kObjectTypeNil) { 2599 obj = dict_get_value(get_globvar_dict(), cstr_as_string(key), NULL, &err); 2600 api_clear_error(&err); 2601 } 2602 if (obj.type == kObjectTypeString) { 2603 return obj.data.string.data; 2604 } 2605 api_free_object(obj); 2606 return NULL; 2607 } 2608 2609 // }}} 2610 2611 // vim: foldmethod=marker