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 }