neovim

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

input.c (29734B)


      1 #include <assert.h>
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 
      6 #include "klib/kvec.h"
      7 #include "nvim/api/private/defs.h"
      8 #include "nvim/api/private/helpers.h"
      9 #include "nvim/event/loop.h"
     10 #include "nvim/event/rstream.h"
     11 #include "nvim/macros_defs.h"
     12 #include "nvim/main.h"
     13 #include "nvim/map_defs.h"
     14 #include "nvim/memory.h"
     15 #include "nvim/msgpack_rpc/channel.h"
     16 #include "nvim/option_vars.h"
     17 #include "nvim/os/os.h"
     18 #include "nvim/os/os_defs.h"
     19 #include "nvim/strings.h"
     20 #include "nvim/tui/input.h"
     21 #include "nvim/tui/input_defs.h"
     22 #include "nvim/tui/termkey/driver-csi.h"
     23 #include "nvim/tui/termkey/termkey.h"
     24 #include "nvim/tui/termkey/termkey_defs.h"
     25 #include "nvim/tui/tui.h"
     26 #include "nvim/ui_client.h"
     27 
     28 #ifdef MSWIN
     29 # include "nvim/os/os_win_console.h"
     30 #endif
     31 
     32 #define READ_STREAM_SIZE 0xfff
     33 
     34 /// Size of libtermkey's internal input buffer. The buffer may grow larger than
     35 /// this when processing very long escape sequences, but will shrink back to
     36 /// this size afterward
     37 #define INPUT_BUFFER_SIZE 256
     38 
     39 static const struct kitty_key_map_entry {
     40  int key;
     41  const char *name;
     42 } kitty_key_map_entry[] = {
     43  { KITTY_KEY_ESCAPE,              "Esc" },
     44  { KITTY_KEY_ENTER,               "CR" },
     45  { KITTY_KEY_TAB,                 "Tab" },
     46  { KITTY_KEY_BACKSPACE,           "BS" },
     47  { KITTY_KEY_INSERT,              "Insert" },
     48  { KITTY_KEY_DELETE,              "Del" },
     49  { KITTY_KEY_LEFT,                "Left" },
     50  { KITTY_KEY_RIGHT,               "Right" },
     51  { KITTY_KEY_UP,                  "Up" },
     52  { KITTY_KEY_DOWN,                "Down" },
     53  { KITTY_KEY_PAGE_UP,             "PageUp" },
     54  { KITTY_KEY_PAGE_DOWN,           "PageDown" },
     55  { KITTY_KEY_HOME,                "Home" },
     56  { KITTY_KEY_END,                 "End" },
     57  { KITTY_KEY_F1,                  "F1" },
     58  { KITTY_KEY_F2,                  "F2" },
     59  { KITTY_KEY_F3,                  "F3" },
     60  { KITTY_KEY_F4,                  "F4" },
     61  { KITTY_KEY_F5,                  "F5" },
     62  { KITTY_KEY_F6,                  "F6" },
     63  { KITTY_KEY_F7,                  "F7" },
     64  { KITTY_KEY_F8,                  "F8" },
     65  { KITTY_KEY_F9,                  "F9" },
     66  { KITTY_KEY_F10,                 "F10" },
     67  { KITTY_KEY_F11,                 "F11" },
     68  { KITTY_KEY_F12,                 "F12" },
     69  { KITTY_KEY_F13,                 "F13" },
     70  { KITTY_KEY_F14,                 "F14" },
     71  { KITTY_KEY_F15,                 "F15" },
     72  { KITTY_KEY_F16,                 "F16" },
     73  { KITTY_KEY_F17,                 "F17" },
     74  { KITTY_KEY_F18,                 "F18" },
     75  { KITTY_KEY_F19,                 "F19" },
     76  { KITTY_KEY_F20,                 "F20" },
     77  { KITTY_KEY_F21,                 "F21" },
     78  { KITTY_KEY_F22,                 "F22" },
     79  { KITTY_KEY_F23,                 "F23" },
     80  { KITTY_KEY_F24,                 "F24" },
     81  { KITTY_KEY_F25,                 "F25" },
     82  { KITTY_KEY_F26,                 "F26" },
     83  { KITTY_KEY_F27,                 "F27" },
     84  { KITTY_KEY_F28,                 "F28" },
     85  { KITTY_KEY_F29,                 "F29" },
     86  { KITTY_KEY_F30,                 "F30" },
     87  { KITTY_KEY_F31,                 "F31" },
     88  { KITTY_KEY_F32,                 "F32" },
     89  { KITTY_KEY_F33,                 "F33" },
     90  { KITTY_KEY_F34,                 "F34" },
     91  { KITTY_KEY_F35,                 "F35" },
     92  { KITTY_KEY_KP_0,                "k0" },
     93  { KITTY_KEY_KP_1,                "k1" },
     94  { KITTY_KEY_KP_2,                "k2" },
     95  { KITTY_KEY_KP_3,                "k3" },
     96  { KITTY_KEY_KP_4,                "k4" },
     97  { KITTY_KEY_KP_5,                "k5" },
     98  { KITTY_KEY_KP_6,                "k6" },
     99  { KITTY_KEY_KP_7,                "k7" },
    100  { KITTY_KEY_KP_8,                "k8" },
    101  { KITTY_KEY_KP_9,                "k9" },
    102  { KITTY_KEY_KP_DECIMAL,          "kPoint" },
    103  { KITTY_KEY_KP_DIVIDE,           "kDivide" },
    104  { KITTY_KEY_KP_MULTIPLY,         "kMultiply" },
    105  { KITTY_KEY_KP_SUBTRACT,         "kMinus" },
    106  { KITTY_KEY_KP_ADD,              "kPlus" },
    107  { KITTY_KEY_KP_ENTER,            "kEnter" },
    108  { KITTY_KEY_KP_EQUAL,            "kEqual" },
    109  { KITTY_KEY_KP_LEFT,             "kLeft" },
    110  { KITTY_KEY_KP_RIGHT,            "kRight" },
    111  { KITTY_KEY_KP_UP,               "kUp" },
    112  { KITTY_KEY_KP_DOWN,             "kDown" },
    113  { KITTY_KEY_KP_PAGE_UP,          "kPageUp" },
    114  { KITTY_KEY_KP_PAGE_DOWN,        "kPageDown" },
    115  { KITTY_KEY_KP_HOME,             "kHome" },
    116  { KITTY_KEY_KP_END,              "kEnd" },
    117  { KITTY_KEY_KP_INSERT,           "kInsert" },
    118  { KITTY_KEY_KP_DELETE,           "kDel" },
    119  { KITTY_KEY_KP_BEGIN,            "kOrigin" },
    120 };
    121 
    122 static PMap(int) kitty_key_map = MAP_INIT;
    123 
    124 #include "tui/input.c.generated.h"
    125 
    126 void tinput_init(TermInput *input, Loop *loop, TerminfoEntry *ti)
    127 {
    128  assert(input->loop == NULL);
    129  input->loop = loop;
    130  input->paste = 0;
    131  input->in_fd = STDIN_FILENO;
    132  input->ttimeout = (bool)p_ttimeout;
    133  input->ttimeoutlen = p_ttm;
    134 
    135  // setup input handle
    136  rstream_init_fd(loop, &input->read_stream, input->in_fd);
    137 
    138  for (size_t i = 0; i < ARRAY_SIZE(kitty_key_map_entry); i++) {
    139    pmap_put(int)(&kitty_key_map, kitty_key_map_entry[i].key, (ptr_t)kitty_key_map_entry[i].name);
    140  }
    141 
    142  input->tk = termkey_new_abstract(ti, (TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART
    143                                        | TERMKEY_FLAG_KEEPC0));
    144  termkey_set_buffer_size(input->tk, INPUT_BUFFER_SIZE);
    145  termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, input);
    146  termkey_start(input->tk);
    147 
    148  int curflags = termkey_get_canonflags(input->tk);
    149  termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
    150 
    151  // initialize a timer handle for handling ESC with libtermkey
    152  uv_timer_init(&loop->uv, &input->timer_handle);
    153  input->timer_handle.data = input;
    154 
    155  uv_timer_init(&loop->uv, &input->bg_query_timer);
    156  input->bg_query_timer.data = input;
    157 }
    158 
    159 void tinput_destroy(TermInput *input)
    160 {
    161  map_destroy(int, &kitty_key_map);
    162  uv_close((uv_handle_t *)&input->timer_handle, NULL);
    163  uv_close((uv_handle_t *)&input->bg_query_timer, NULL);
    164  rstream_may_close(&input->read_stream);
    165  termkey_destroy(input->tk);
    166  input->loop = NULL;
    167 }
    168 
    169 void tinput_start(TermInput *input)
    170 {
    171  rstream_start(&input->read_stream, tinput_read_cb, input);
    172 }
    173 
    174 void tinput_stop(TermInput *input)
    175 {
    176  rstream_stop(&input->read_stream);
    177  uv_timer_stop(&input->timer_handle);
    178  uv_timer_stop(&input->bg_query_timer);
    179 }
    180 
    181 static void tinput_done_event(void **argv)
    182  FUNC_ATTR_NORETURN
    183 {
    184  os_exit(1);
    185 }
    186 
    187 /// Send all pending input in key buffer to Nvim server.
    188 static void tinput_flush(TermInput *input)
    189 {
    190  String keys = { .data = input->key_buffer, .size = input->key_buffer_len };
    191  if (input->paste) {  // produce exactly one paste event
    192    MAXSIZE_TEMP_ARRAY(args, 3);
    193    ADD_C(args, STRING_OBJ(keys));  // 'data'
    194    ADD_C(args, BOOLEAN_OBJ(true));  // 'crlf'
    195    ADD_C(args, INTEGER_OBJ(input->paste));  // 'phase'
    196    rpc_send_event(ui_client_channel_id, "nvim_paste", args);
    197    if (input->paste == 1) {
    198      // Paste phase: "continue"
    199      input->paste = 2;
    200    }
    201  } else {  // enqueue input
    202    if (input->key_buffer_len > 0) {
    203      MAXSIZE_TEMP_ARRAY(args, 1);
    204      ADD_C(args, STRING_OBJ(keys));
    205      // NOTE: This is non-blocking and won't check partially processed input,
    206      // but should be fine as all big sends are handled with nvim_paste, not nvim_input
    207      rpc_send_event(ui_client_channel_id, "nvim_input", args);
    208    }
    209  }
    210  input->key_buffer_len = 0;
    211 }
    212 
    213 static void tinput_enqueue(TermInput *input, const char *buf, size_t size)
    214 {
    215  if (input->key_buffer_len > KEY_BUFFER_SIZE - size) {
    216    // don't ever let the buffer get too full or we risk putting incomplete keys into it
    217    tinput_flush(input);
    218  }
    219  size_t to_copy = MIN(size, KEY_BUFFER_SIZE - input->key_buffer_len);
    220  memcpy(input->key_buffer + input->key_buffer_len, buf, to_copy);
    221  input->key_buffer_len += to_copy;
    222 }
    223 
    224 /// Handle TERMKEY_KEYMOD_* modifiers, i.e. Shift, Alt and Ctrl.
    225 ///
    226 /// @return  The number of bytes written into "buf", excluding the final NUL.
    227 static size_t handle_termkey_modifiers(TermKeyKey *key, char *buf, size_t buflen)
    228  FUNC_ATTR_WARN_UNUSED_RESULT
    229 {
    230  size_t len = 0;
    231  if (key->modifiers & TERMKEY_KEYMOD_SHIFT) {  // Shift
    232    len += (size_t)snprintf(buf + len, buflen - len, "S-");
    233  }
    234  if (key->modifiers & TERMKEY_KEYMOD_ALT) {  // Alt
    235    len += (size_t)snprintf(buf + len, buflen - len, "A-");
    236  }
    237  if (key->modifiers & TERMKEY_KEYMOD_CTRL) {  // Ctrl
    238    len += (size_t)snprintf(buf + len, buflen - len, "C-");
    239  }
    240  assert(len < buflen);
    241  return len;
    242 }
    243 
    244 enum {
    245  KEYMOD_SUPER      = 1 << 3,
    246  KEYMOD_META       = 1 << 5,
    247 #ifdef _MSC_VER
    248 # pragma warning(push)
    249 # pragma warning(disable : 5287)
    250 #endif
    251  KEYMOD_RECOGNIZED = (TERMKEY_KEYMOD_SHIFT | TERMKEY_KEYMOD_ALT | TERMKEY_KEYMOD_CTRL
    252                       | KEYMOD_SUPER | KEYMOD_META),
    253 #ifdef _MSC_VER
    254 # pragma warning(pop)
    255 #endif
    256 };
    257 
    258 /// Handle modifiers not handled by libtermkey.
    259 /// Currently only Super ("D-") and Meta ("T-") are supported in Nvim.
    260 ///
    261 /// @return  The number of bytes written into "buf", excluding the final NUL.
    262 static size_t handle_more_modifiers(TermKeyKey *key, char *buf, size_t buflen)
    263  FUNC_ATTR_WARN_UNUSED_RESULT
    264 {
    265  size_t len = 0;
    266  if (key->modifiers & KEYMOD_SUPER) {
    267    len += (size_t)snprintf(buf + len, buflen - len, "D-");
    268  }
    269  if (key->modifiers & KEYMOD_META) {
    270    len += (size_t)snprintf(buf + len, buflen - len, "T-");
    271  }
    272  assert(len < buflen);
    273  return len;
    274 }
    275 
    276 static void handle_kitty_key_protocol(TermInput *input, TermKeyKey *key)
    277 {
    278  const char *name = pmap_get(int)(&kitty_key_map, key->code.codepoint);
    279  if (name) {
    280    char buf[64];
    281    size_t len = 0;
    282    buf[len++] = '<';
    283    len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len);
    284    len += handle_more_modifiers(key, buf + len, sizeof(buf) - len);
    285    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "%s>", name);
    286    assert(len < sizeof(buf));
    287    tinput_enqueue(input, buf, len);
    288  }
    289 }
    290 
    291 static void forward_simple_utf8(TermInput *input, TermKeyKey *key)
    292 {
    293  size_t len = 0;
    294  char buf[64];
    295  char *ptr = key->utf8;
    296 
    297  if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF
    298      && map_has(int, &kitty_key_map, (int)key->code.codepoint)) {
    299    handle_kitty_key_protocol(input, key);
    300    return;
    301  }
    302  while (*ptr) {
    303    if (*ptr == '<') {
    304      len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>");
    305    } else {
    306      buf[len++] = *ptr;
    307    }
    308    assert(len < sizeof(buf));
    309    ptr++;
    310  }
    311 
    312  tinput_enqueue(input, buf, len);
    313 }
    314 
    315 static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
    316 {
    317  size_t len;
    318  char buf[64];
    319 
    320  if (key->type == TERMKEY_TYPE_KEYSYM
    321      && key->code.sym == TERMKEY_SYM_SUSPEND) {
    322    len = (size_t)snprintf(buf, sizeof(buf), "<C-Z>");
    323  } else if (key->type != TERMKEY_TYPE_UNICODE) {
    324    len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
    325  } else {
    326    assert(key->modifiers);
    327    if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF
    328        && map_has(int, &kitty_key_map, (int)key->code.codepoint)) {
    329      handle_kitty_key_protocol(input, key);
    330      return;
    331    }
    332    // Termkey doesn't include the S- modifier for ASCII characters (e.g.,
    333    // ctrl-shift-l is <C-L> instead of <C-S-L>.  Vim, on the other hand,
    334    // treats <C-L> and <C-l> the same, requiring the S- modifier.
    335    len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM);
    336    if ((key->modifiers & TERMKEY_KEYMOD_CTRL)
    337        && !(key->modifiers & TERMKEY_KEYMOD_SHIFT)
    338        && ASCII_ISUPPER(key->code.codepoint)) {
    339      assert(len + 2 < sizeof(buf));
    340      // Make room for the S-
    341      memmove(buf + 3, buf + 1, len - 1);
    342      buf[1] = 'S';
    343      buf[2] = '-';
    344      len += 2;
    345    }
    346  }
    347 
    348  char more_buf[25];
    349  size_t more_len = handle_more_modifiers(key, more_buf, sizeof(more_buf));
    350  if (more_len > 0) {
    351    assert(len + more_len < sizeof(buf));
    352    memmove(buf + 1 + more_len, buf + 1, len - 1);
    353    memcpy(buf + 1, more_buf, more_len);
    354    len += more_len;
    355  }
    356 
    357  assert(len < sizeof(buf));
    358  tinput_enqueue(input, buf, len);
    359 }
    360 
    361 static void forward_mouse_event(TermInput *input, TermKeyKey *key)
    362 {
    363  char buf[64];
    364  size_t len = 0;
    365  int button, row, col;
    366  static int last_pressed_button = 0;
    367  TermKeyMouseEvent ev;
    368  termkey_interpret_mouse(input->tk, key, &ev, &button, &row, &col);
    369 
    370  if ((ev == TERMKEY_MOUSE_RELEASE || ev == TERMKEY_MOUSE_DRAG)
    371      && button == 0) {
    372    // Some terminals (like urxvt) don't report which button was released.
    373    // libtermkey reports button 0 in this case.
    374    // For drag and release, we can reasonably infer the button to be the last
    375    // pressed one.
    376    button = last_pressed_button;
    377  }
    378 
    379  if ((button == 0 && ev != TERMKEY_MOUSE_RELEASE)
    380      || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG && ev != TERMKEY_MOUSE_RELEASE)) {
    381    return;
    382  }
    383 
    384  row--; col--;  // Termkey uses 1-based coordinates
    385  buf[len++] = '<';
    386 
    387  len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len);
    388  // Doesn't actually work because there are only 3 bits (0x1c) for modifiers.
    389  // len += handle_more_modifiers(key, buf + len, sizeof(buf) - len);
    390 
    391  if (button == 1) {
    392    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Left");
    393  } else if (button == 2) {
    394    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Middle");
    395  } else if (button == 3) {
    396    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Right");
    397  } else if (button == 8) {
    398    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "X1");
    399  } else if (button == 9) {
    400    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "X2");
    401  }
    402 
    403  switch (ev) {
    404  case TERMKEY_MOUSE_PRESS:
    405    if (button == 4) {
    406      len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp");
    407    } else if (button == 5) {
    408      len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown");
    409    } else if (button == 6) {
    410      len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelLeft");
    411    } else if (button == 7) {
    412      len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelRight");
    413    } else {
    414      len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse");
    415      last_pressed_button = button;
    416    }
    417    break;
    418  case TERMKEY_MOUSE_DRAG:
    419    len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Drag");
    420    break;
    421  case TERMKEY_MOUSE_RELEASE:
    422    len += (size_t)snprintf(buf + len, sizeof(buf) - len, button ? "Release" : "MouseMove");
    423    last_pressed_button = 0;
    424    break;
    425  case TERMKEY_MOUSE_UNKNOWN:
    426    abort();
    427  }
    428 
    429  len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
    430  assert(len < sizeof(buf));
    431  tinput_enqueue(input, buf, len);
    432 }
    433 
    434 static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
    435 {
    436  return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key);
    437 }
    438 
    439 static void tk_getkeys(TermInput *input, bool force)
    440 {
    441  TermKeyKey key;
    442  TermKeyResult result;
    443 
    444  while ((result = tk_getkey(input->tk, &key, force)) == TERMKEY_RES_KEY) {
    445    // Only press and repeat events are handled for now
    446    switch (key.event) {
    447    case TERMKEY_EVENT_PRESS:
    448    case TERMKEY_EVENT_REPEAT:
    449      break;
    450    default:
    451      continue;
    452    }
    453 
    454    if (key.type == TERMKEY_TYPE_UNICODE && !(key.modifiers & KEYMOD_RECOGNIZED)) {
    455      forward_simple_utf8(input, &key);
    456    } else if (key.type == TERMKEY_TYPE_UNICODE
    457               || key.type == TERMKEY_TYPE_FUNCTION
    458               || key.type == TERMKEY_TYPE_KEYSYM) {
    459      forward_modified_utf8(input, &key);
    460    } else if (key.type == TERMKEY_TYPE_MOUSE) {
    461      forward_mouse_event(input, &key);
    462    } else if (key.type == TERMKEY_TYPE_MODEREPORT) {
    463      handle_modereport(input, &key);
    464    } else if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) {
    465      handle_unknown_csi(input, &key);
    466    } else if (key.type == TERMKEY_TYPE_OSC || key.type == TERMKEY_TYPE_DCS
    467               || key.type == TERMKEY_TYPE_APC) {
    468      handle_term_response(input, &key);
    469    }
    470  }
    471 
    472  if (result != TERMKEY_RES_AGAIN) {
    473    return;
    474  }
    475  // else: Partial keypress event was found in the buffer, but it does not
    476  // yet contain all the bytes required. `key` structure indicates what
    477  // termkey_getkey_force() would return.
    478 
    479  if (input->ttimeout && input->ttimeoutlen >= 0) {
    480    // Stop the current timer if already running
    481    uv_timer_stop(&input->timer_handle);
    482    uv_timer_start(&input->timer_handle, tinput_timer_cb, (uint64_t)input->ttimeoutlen, 0);
    483  } else {
    484    tk_getkeys(input, true);
    485  }
    486 }
    487 
    488 static void tinput_timer_cb(uv_timer_t *handle)
    489 {
    490  TermInput *input = handle->data;
    491  // If the raw buffer is not empty, process the raw buffer first because it is
    492  // processing an incomplete bracketed paste sequence.
    493  size_t size = rstream_available(&input->read_stream);
    494  if (size) {
    495    size_t consumed = handle_raw_buffer(input, true, input->read_stream.read_pos, size);
    496    rstream_consume(&input->read_stream, consumed);
    497  }
    498  tk_getkeys(input, true);
    499  tinput_flush(input);
    500 }
    501 
    502 static void bg_query_timer_cb(uv_timer_t *handle)
    503  FUNC_ATTR_NONNULL_ALL
    504 {
    505  TermInput *input = handle->data;
    506  tui_query_bg_color(input->tui_data);
    507 }
    508 
    509 /// Handle focus events.
    510 ///
    511 /// If the upcoming sequence of bytes in the input stream matches the termcode
    512 /// for "focus gained" or "focus lost", consume that sequence and send an event
    513 /// to Nvim server.
    514 ///
    515 /// @param input the input stream
    516 /// @return true iff handle_focus_event consumed some input
    517 static size_t handle_focus_event(TermInput *input, const char *ptr, size_t size)
    518 {
    519  if (size >= 3
    520      && (!memcmp(ptr, "\x1b[I", 3)
    521          || !memcmp(ptr, "\x1b[O", 3))) {
    522    bool focus_gained = ptr[2] == 'I';
    523 
    524    MAXSIZE_TEMP_ARRAY(args, 1);
    525    ADD_C(args, BOOLEAN_OBJ(focus_gained));
    526    rpc_send_event(ui_client_channel_id, "nvim_ui_set_focus", args);
    527    return 3;  // Advance past the sequence
    528  }
    529  return 0;
    530 }
    531 
    532 #define START_PASTE "\x1b[200~"
    533 #define END_PASTE   "\x1b[201~"
    534 static size_t handle_bracketed_paste(TermInput *input, const char *ptr, size_t size,
    535                                     bool *incomplete)
    536 {
    537  if (size >= 6
    538      && (!memcmp(ptr, START_PASTE, 6)
    539          || !memcmp(ptr, END_PASTE, 6))) {
    540    bool enable = ptr[4] == '0';
    541    if (input->paste && enable) {
    542      return 0;  // Pasting "start paste" code literally.
    543    }
    544 
    545    // Advance past the sequence
    546    if (!!input->paste == enable) {
    547      return 6;  // Spurious "disable paste" code.
    548    }
    549 
    550    if (enable) {
    551      // Flush before starting paste.
    552      tinput_flush(input);
    553      // Paste phase: "first-chunk".
    554      input->paste = 1;
    555    } else if (input->paste) {
    556      // Paste phase: "last-chunk".
    557      input->paste = input->paste == 2 ? 3 : -1;
    558      tinput_flush(input);
    559      // Paste phase: "disabled".
    560      input->paste = 0;
    561    }
    562    return 6;
    563  } else if (size < 6
    564             && (!memcmp(ptr, START_PASTE, size)
    565                 || !memcmp(ptr, END_PASTE, size))) {
    566    // Wait for further input, as the sequence may be split.
    567    *incomplete = true;
    568    return 0;
    569  }
    570  return 0;
    571 }
    572 
    573 /// Handle an OSC, DCS, or APC response sequence from the terminal.
    574 static void handle_term_response(TermInput *input, const TermKeyKey *key)
    575  FUNC_ATTR_NONNULL_ALL
    576 {
    577  const char *str = NULL;
    578  if (termkey_interpret_string(input->tk, key, &str) == TERMKEY_RES_KEY) {
    579    assert(str != NULL);
    580 
    581    // Handle DECRQSS SGR response for the query from tui_query_extended_underline().
    582    // Some terminals include "0" in the attribute list unconditionally; others don't.
    583    if (key->type == TERMKEY_TYPE_DCS
    584        && (strnequal(str, S_LEN("1$r4:3m")) || strnequal(str, S_LEN("1$r0;4:3m")))) {
    585      tui_enable_extended_underline(input->tui_data);
    586    }
    587 
    588    // Send an event to nvim core. This will update the v:termresponse variable
    589    // and fire the TermResponse event
    590    MAXSIZE_TEMP_ARRAY(args, 2);
    591    ADD_C(args, STATIC_CSTR_AS_OBJ("termresponse"));
    592 
    593    // libtermkey strips the OSC/DCS bytes from the response. We add it back in
    594    // so that downstream consumers of v:termresponse can differentiate between
    595    // the two.
    596    StringBuilder response = KV_INITIAL_VALUE;
    597    switch (key->type) {
    598    case TERMKEY_TYPE_OSC:
    599      kv_printf(response, "\x1b]%s", str);
    600      break;
    601    case TERMKEY_TYPE_DCS:
    602      kv_printf(response, "\x1bP%s", str);
    603      break;
    604    case TERMKEY_TYPE_APC:
    605      kv_printf(response, "\x1b_%s", str);
    606      break;
    607    default:
    608      // Key type already checked for OSC/DCS in termkey_interpret_string
    609      UNREACHABLE;
    610    }
    611 
    612    ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size)));
    613    rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args);
    614    kv_destroy(response);
    615  }
    616 }
    617 
    618 /// Handle a Primary Device Attributes (DA1) response from the terminal.
    619 static void handle_primary_device_attr(TermInput *input, TermKeyCsiParam *params, size_t nparams)
    620  FUNC_ATTR_NONNULL_ALL
    621 {
    622  if (input->callbacks.primary_device_attr) {
    623    void (*cb_save)(TUIData *) = input->callbacks.primary_device_attr;
    624    // Clear the callback before invoking it, as it may set a new callback. #34031
    625    input->callbacks.primary_device_attr = NULL;
    626    cb_save(input->tui_data);
    627  }
    628 
    629  if (nparams == 0) {
    630    return;
    631  }
    632 
    633  MAXSIZE_TEMP_ARRAY(args, 2);
    634  ADD_C(args, STATIC_CSTR_AS_OBJ("termresponse"));
    635 
    636  StringBuilder response = KV_INITIAL_VALUE;
    637  kv_concat(response, "\x1b[?");
    638 
    639  for (size_t i = 0; i < nparams; i++) {
    640    int arg;
    641    if (termkey_interpret_csi_param(params[i], &arg, NULL, NULL) != TERMKEY_RES_KEY) {
    642      goto out;
    643    }
    644 
    645    kv_printf(response, "%d", arg);
    646    if (i < nparams - 1) {
    647      kv_push(response, ';');
    648    }
    649  }
    650 
    651  kv_push(response, 'c');
    652 
    653  ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size)));
    654  rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args);
    655 out:
    656  kv_destroy(response);
    657 }
    658 
    659 /// Handle a mode report (DECRPM) sequence from the terminal.
    660 static void handle_modereport(TermInput *input, const TermKeyKey *key)
    661  FUNC_ATTR_NONNULL_ALL
    662 {
    663  int initial;
    664  int mode;
    665  int value;
    666  if (termkey_interpret_modereport(input->tk, key, &initial, &mode, &value) == TERMKEY_RES_KEY) {
    667    (void)initial;  // Unused
    668    tui_handle_term_mode(input->tui_data, (TermMode)mode, (TermModeState)value);
    669  }
    670 }
    671 
    672 /// Handle a CSI sequence from the terminal that is unrecognized by libtermkey.
    673 static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
    674  FUNC_ATTR_NONNULL_ALL
    675 {
    676  // There is no specified limit on the number of parameters a CSI sequence can
    677  // contain, so just allocate enough space for a large upper bound
    678  TermKeyCsiParam params[16];
    679  size_t nparams = 16;
    680  unsigned cmd;
    681  if (termkey_interpret_csi(input->tk, key, params, &nparams, &cmd) != TERMKEY_RES_KEY) {
    682    return;
    683  }
    684 
    685  uint8_t intermediate = (cmd >> 16) & 0xFF;
    686  uint8_t initial = (cmd >> 8) & 0xFF;
    687  uint8_t command = cmd & 0xFF;
    688 
    689  // Currently unused
    690  (void)intermediate;
    691 
    692  switch (command) {
    693  case 'u':
    694    switch (initial) {
    695    case '?':
    696      // Kitty keyboard protocol query response.
    697      input->key_encoding = kKeyEncodingKitty;
    698      break;
    699    }
    700    break;
    701  case 'c':
    702    switch (initial) {
    703    case '?':
    704      // Primary Device Attributes (DA1) response
    705      handle_primary_device_attr(input, params, nparams);
    706      break;
    707    }
    708    break;
    709  case 't':
    710    if (nparams == 5) {
    711      // We only care about the first 3 parameters, and we ignore subparameters
    712      int args[3];
    713      for (size_t i = 0; i < ARRAY_SIZE(args); i++) {
    714        if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) {
    715          return;
    716        }
    717      }
    718 
    719      if (args[0] == 48) {
    720        // In-band resize event (DEC private mode 2048)
    721        int height_chars = args[1];
    722        int width_chars = args[2];
    723        tui_set_size(input->tui_data, width_chars, height_chars);
    724      }
    725    }
    726    break;
    727  case 'n':
    728    // Device Status Report (DSR)
    729    if (nparams == 1) {
    730      // ECMA-48 DSR
    731      // https://ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
    732      int arg;
    733      if (termkey_interpret_csi_param(params[0], &arg, NULL, NULL) != TERMKEY_RES_KEY) {
    734        return;
    735      }
    736 
    737      MAXSIZE_TEMP_ARRAY(args, 2);
    738      ADD_C(args, STATIC_CSTR_AS_OBJ("termresponse"));
    739 
    740      StringBuilder response = KV_INITIAL_VALUE;
    741      kv_printf(response, "\x1b[%dn", arg);
    742      ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size)));
    743 
    744      rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args);
    745      kv_destroy(response);
    746    } else if (nparams == 2) {
    747      // Hard to find comprehensive docs on these responses. Some can be found at https://www.xfree86.org/current/ctlseqs.html
    748      // under "Device Status Report (DSR, DEC-specific)"
    749      // - Report Printer status
    750      // - Report User Defined Key status
    751      // - Report Locator status
    752      // When the first parameter is 997, it's a theme update response based on
    753      // contour terminal VT extensions, as described below.
    754      int args[2];
    755      for (size_t i = 0; i < ARRAY_SIZE(args); i++) {
    756        if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) {
    757          return;
    758        }
    759      }
    760 
    761      if (args[0] == 997) {
    762        // Theme update notification
    763        // https://github.com/contour-terminal/contour/blob/master/docs/vt-extensions/color-palette-update-notifications.md
    764        // The second argument tells us whether the OS theme is set to light
    765        // mode or dark mode, but all we care about is the background color of
    766        // the terminal emulator. We query for that with OSC 11 and the response
    767        // is handled by the autocommand created in _core/defaults.lua. The terminal
    768        // may send us multiple notifications all at once so we use a timer to
    769        // coalesce the queries.
    770        if (uv_timer_get_due_in(&input->bg_query_timer) > 0) {
    771          return;
    772        }
    773 
    774        uv_timer_start(&input->bg_query_timer, bg_query_timer_cb, 100, 0);
    775      }
    776    }
    777    break;
    778  default:
    779    break;
    780  }
    781 }
    782 
    783 static size_t handle_raw_buffer(TermInput *input, bool force, const char *data, size_t size)
    784 {
    785  const char *ptr = data;
    786 
    787  do {
    788    if (!force) {
    789      size_t consumed = handle_focus_event(input, ptr, size);
    790      if (consumed) {
    791        ptr += consumed;
    792        size -= consumed;
    793        continue;
    794      }
    795 
    796      bool incomplete = false;
    797      consumed = handle_bracketed_paste(input, ptr, size, &incomplete);
    798      if (incomplete) {
    799        assert(consumed == 0);
    800        // Wait for the next input, leaving it in the raw buffer due to an
    801        // incomplete sequence.
    802        return (size_t)(ptr - data);
    803      } else if (consumed) {
    804        ptr += consumed;
    805        size -= consumed;
    806        continue;
    807      }
    808    }
    809 
    810    //
    811    // Find the next ESC and push everything up to it (excluding), so it will
    812    // be the first thing encountered on the next iteration. The `handle_*`
    813    // calls (above) depend on this.
    814    //
    815    size_t count = 0;
    816    for (size_t i = 0; i < size; i++) {
    817      count = i + 1;
    818      if (ptr[i] == '\x1b' && count > 1) {
    819        count--;
    820        break;
    821      }
    822    }
    823    // Push bytes directly (paste).
    824    if (input->paste) {
    825      tinput_enqueue(input, ptr, count);
    826      ptr += count;
    827      size -= count;
    828      continue;
    829    }
    830 
    831    // Push through libtermkey (translates to "<keycode>" strings, etc.).
    832    {
    833      const size_t to_use = MIN(count, size);
    834      if (to_use > termkey_get_buffer_remaining(input->tk)) {
    835        // We are processing a very long escape sequence. Increase termkey's
    836        // internal buffer size. We don't handle out of memory situations so
    837        // abort if it fails
    838        const size_t delta = to_use - termkey_get_buffer_remaining(input->tk);
    839        const size_t bufsize = termkey_get_buffer_size(input->tk);
    840        if (!termkey_set_buffer_size(input->tk, MAX(bufsize + delta, bufsize * 2))) {
    841          abort();
    842        }
    843      }
    844 
    845      size_t consumed = termkey_push_bytes(input->tk, ptr, to_use);
    846 
    847      // We resize termkey's buffer when it runs out of space, so this should
    848      // never happen
    849      assert(consumed <= to_use);
    850      ptr += consumed;
    851      size -= consumed;
    852 
    853      // Process the input buffer now for any keys
    854      tk_getkeys(input, false);
    855    }
    856  } while (size);
    857 
    858  const size_t tk_size = termkey_get_buffer_size(input->tk);
    859  const size_t tk_remaining = termkey_get_buffer_remaining(input->tk);
    860  const size_t tk_count = tk_size - tk_remaining;
    861  if (tk_count < INPUT_BUFFER_SIZE && tk_size > INPUT_BUFFER_SIZE) {
    862    // If the termkey buffer was resized to handle a large input sequence then
    863    // shrink it back down to its original size.
    864    if (!termkey_set_buffer_size(input->tk, INPUT_BUFFER_SIZE)) {
    865      abort();
    866    }
    867  }
    868 
    869  return (size_t)(ptr - data);
    870 }
    871 
    872 static size_t tinput_read_cb(RStream *stream, const char *buf, size_t count_, void *data, bool eof)
    873 {
    874  TermInput *input = data;
    875 
    876  size_t consumed = handle_raw_buffer(input, false, buf, count_);
    877  tinput_flush(input);
    878 
    879  if (eof) {
    880    loop_schedule_fast(&main_loop, event_create(tinput_done_event, NULL));
    881    return consumed;
    882  }
    883 
    884  // An incomplete sequence was found. Leave it in the raw buffer and wait for
    885  // the next input.
    886  if (consumed < count_) {
    887    // If 'ttimeout' is not set, start the timer with a timeout of 0 to process
    888    // the next input.
    889    int64_t ms = input->ttimeout
    890                 ? (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0;
    891    // Stop the current timer if already running
    892    uv_timer_stop(&input->timer_handle);
    893    uv_timer_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0);
    894  }
    895 
    896  return consumed;
    897 }