neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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, &regtype, 1);
   1757 
   1758  tv_list_append_string(args, &regname, 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