mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	Merge pull request #26096 from gpanders/tui-input-refactor
TUI input refactors
This commit is contained in:
		| @@ -122,7 +122,7 @@ void tinput_init(TermInput *input, Loop *loop) | ||||
|   input->loop = loop; | ||||
|   input->paste = 0; | ||||
|   input->in_fd = STDIN_FILENO; | ||||
|   input->extkeys_type = kExtkeysNone; | ||||
|   input->key_encoding = kKeyEncodingLegacy; | ||||
|   input->ttimeout = (bool)p_ttimeout; | ||||
|   input->ttimeoutlen = p_ttm; | ||||
|   input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); | ||||
| @@ -444,38 +444,7 @@ static void tk_getkeys(TermInput *input, bool force) | ||||
|     } else if (key.type == TERMKEY_TYPE_MOUSE) { | ||||
|       forward_mouse_event(input, &key); | ||||
|     } else if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) { | ||||
|       // There is no specified limit on the number of parameters a CSI sequence can contain, so just | ||||
|       // allocate enough space for a large upper bound | ||||
|       long args[16]; | ||||
|       size_t nargs = 16; | ||||
|       unsigned long cmd; | ||||
|       if (termkey_interpret_csi(input->tk, &key, args, &nargs, &cmd) == TERMKEY_RES_KEY) { | ||||
|         uint8_t intermediate = (cmd >> 16) & 0xFF; | ||||
|         uint8_t initial = (cmd >> 8) & 0xFF; | ||||
|         uint8_t command = cmd & 0xFF; | ||||
|  | ||||
|         // Currently unused | ||||
|         (void)intermediate; | ||||
|  | ||||
|         if (input->waiting_for_csiu_response > 0) { | ||||
|           if (initial == '?' && command == 'u') { | ||||
|             // The first (and only) argument contains the current progressive | ||||
|             // enhancement flags. Only enable CSI u mode if the first bit | ||||
|             // (disambiguate escape codes) is not already set | ||||
|             if (nargs > 0 && (args[0] & 0x1) == 0) { | ||||
|               input->extkeys_type = kExtkeysCSIu; | ||||
|             } else { | ||||
|               input->extkeys_type = kExtkeysNone; | ||||
|             } | ||||
|           } else if (initial == '?' && command == 'c') { | ||||
|             // Received Primary Device Attributes response | ||||
|             input->waiting_for_csiu_response = 0; | ||||
|             tui_enable_extkeys(input->tui_data); | ||||
|           } else { | ||||
|             input->waiting_for_csiu_response--; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       handle_unknown_csi(input, &key); | ||||
|     } else if (key.type == TERMKEY_TYPE_OSC || key.type == TERMKEY_TYPE_DCS) { | ||||
|       handle_term_response(input, &key); | ||||
|     } else if (key.type == TERMKEY_TYPE_MODEREPORT) { | ||||
| @@ -578,6 +547,7 @@ static HandleState handle_bracketed_paste(TermInput *input) | ||||
|   return kNotApplicable; | ||||
| } | ||||
|  | ||||
| /// Handle an OSC or DCS response sequence from the terminal. | ||||
| static void handle_term_response(TermInput *input, const TermKeyKey *key) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
| @@ -612,14 +582,70 @@ static void handle_term_response(TermInput *input, const TermKeyKey *key) | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Handle a mode report (DECRPM) sequence from the terminal. | ||||
| static void handle_modereport(TermInput *input, const TermKeyKey *key) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   // termkey_interpret_modereport incorrectly sign extends the mode so we parse the response | ||||
|   // ourselves | ||||
|   int mode = (uint8_t)key->code.mouse[1] << 8 | (uint8_t)key->code.mouse[2]; | ||||
|   TerminalModeState value = (uint8_t)key->code.mouse[3]; | ||||
|   tui_dec_report_mode(input->tui_data, (TerminalDecMode)mode, value); | ||||
|   int initial; | ||||
|   int mode; | ||||
|   int value; | ||||
|   if (termkey_interpret_modereport(input->tk, key, &initial, &mode, &value) == TERMKEY_RES_KEY) { | ||||
|     (void)initial;  // Unused | ||||
|     tui_handle_term_mode(input->tui_data, (TermMode)mode, (TermModeState)value); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Handle a CSI sequence from the terminal that is unrecognized by libtermkey. | ||||
| static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   // There is no specified limit on the number of parameters a CSI sequence can | ||||
|   // contain, so just allocate enough space for a large upper bound | ||||
|   long args[16]; | ||||
|   size_t nargs = 16; | ||||
|   unsigned long cmd; | ||||
|   if (termkey_interpret_csi(input->tk, key, args, &nargs, &cmd) != TERMKEY_RES_KEY) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   uint8_t intermediate = (cmd >> 16) & 0xFF; | ||||
|   uint8_t initial = (cmd >> 8) & 0xFF; | ||||
|   uint8_t command = cmd & 0xFF; | ||||
|  | ||||
|   // Currently unused | ||||
|   (void)intermediate; | ||||
|  | ||||
|   switch (command) { | ||||
|   case 'u': | ||||
|     switch (initial) { | ||||
|     case '?': | ||||
|       // Kitty keyboard protocol query response. | ||||
|       if (input->waiting_for_kkp_response) { | ||||
|         input->waiting_for_kkp_response = false; | ||||
|         input->key_encoding = kKeyEncodingKitty; | ||||
|         tui_set_key_encoding(input->tui_data); | ||||
|       } | ||||
|  | ||||
|       break; | ||||
|     } | ||||
|     break; | ||||
|   case 'c': | ||||
|     switch (initial) { | ||||
|     case '?': | ||||
|       // Primary Device Attributes response | ||||
|       if (input->waiting_for_kkp_response) { | ||||
|         input->waiting_for_kkp_response = false; | ||||
|  | ||||
|         // Enable the fallback key encoding (if any) | ||||
|         tui_set_key_encoding(input->tui_data); | ||||
|       } | ||||
|  | ||||
|       break; | ||||
|     } | ||||
|     break; | ||||
|   default: | ||||
|     break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| static void handle_raw_buffer(TermInput *input, bool force) | ||||
|   | ||||
| @@ -14,18 +14,22 @@ | ||||
| #include "nvim/types.h" | ||||
|  | ||||
| typedef enum { | ||||
|   kExtkeysNone, | ||||
|   kExtkeysCSIu, | ||||
|   kExtkeysXterm, | ||||
| } ExtkeysType; | ||||
|   kKeyEncodingLegacy,  ///< Legacy key encoding | ||||
|   kKeyEncodingKitty,   ///< Kitty keyboard protocol encoding | ||||
|   kKeyEncodingXterm,   ///< Xterm's modifyOtherKeys encoding (XTMODKEYS) | ||||
| } KeyEncoding; | ||||
|  | ||||
| typedef struct term_input { | ||||
| typedef struct { | ||||
|   int in_fd; | ||||
|   // Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk | ||||
|   int8_t paste; | ||||
|   bool ttimeout; | ||||
|   int8_t waiting_for_csiu_response; | ||||
|   ExtkeysType extkeys_type; | ||||
|  | ||||
|   bool waiting_for_kkp_response;  ///< True if we are expecting to receive a response to a query for | ||||
|                                   ///< Kitty keyboard protocol support | ||||
|  | ||||
|   KeyEncoding key_encoding;       ///< The key encoding used by the terminal emulator | ||||
|  | ||||
|   OptInt ttimeoutlen; | ||||
|   TermKey *tk; | ||||
|   TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn;  ///< libtermkey terminfo hook | ||||
|   | ||||
| @@ -134,8 +134,6 @@ struct TUIData { | ||||
|     int save_title, restore_title; | ||||
|     int set_underline_style; | ||||
|     int set_underline_color; | ||||
|     int enable_extended_keys, disable_extended_keys; | ||||
|     int get_extkeys; | ||||
|     int sync; | ||||
|   } unibi_ext; | ||||
|   char *space_buf; | ||||
| @@ -183,35 +181,40 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term) | ||||
|   *term = tui->term; | ||||
| } | ||||
|  | ||||
| void tui_enable_extkeys(TUIData *tui) | ||||
| void tui_set_key_encoding(TUIData *tui) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   TermInput input = tui->input; | ||||
|   unibi_term *ut = tui->ut; | ||||
|  | ||||
|   switch (input.extkeys_type) { | ||||
|   case kExtkeysCSIu: | ||||
|     tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", | ||||
|                                                                  "\x1b[>1u"); | ||||
|     tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", | ||||
|                                                                   "\x1b[<1u"); | ||||
|   switch (tui->input.key_encoding) { | ||||
|   case kKeyEncodingKitty: | ||||
|     out(tui, S_LEN("\x1b[>1u")); | ||||
|     break; | ||||
|   case kExtkeysXterm: | ||||
|     tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", | ||||
|                                                                  "\x1b[>4;2m"); | ||||
|     tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", | ||||
|                                                                   "\x1b[>4;0m"); | ||||
|   case kKeyEncodingXterm: | ||||
|     out(tui, S_LEN("\x1b[>4;2m")); | ||||
|     break; | ||||
|   default: | ||||
|   case kKeyEncodingLegacy: | ||||
|     break; | ||||
|   } | ||||
|  | ||||
|   unibi_out_ext(tui, tui->unibi_ext.enable_extended_keys); | ||||
| } | ||||
|  | ||||
| /// Request the terminal's DEC mode (DECRQM). | ||||
| static void tui_reset_key_encoding(TUIData *tui) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   switch (tui->input.key_encoding) { | ||||
|   case kKeyEncodingKitty: | ||||
|     out(tui, S_LEN("\x1b[<1u")); | ||||
|     break; | ||||
|   case kKeyEncodingXterm: | ||||
|     out(tui, S_LEN("\x1b[>4;0m")); | ||||
|     break; | ||||
|   case kKeyEncodingLegacy: | ||||
|     break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Request the terminal's mode (DECRQM). | ||||
| /// | ||||
| /// @see handle_modereport | ||||
| static void tui_dec_request_mode(TUIData *tui, TerminalDecMode mode) | ||||
| static void tui_request_term_mode(TUIData *tui, TermMode mode) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   // 5 bytes for \x1b[?$p, 1 byte for null terminator, 6 bytes for mode digits (more than enough) | ||||
| @@ -221,22 +224,22 @@ static void tui_dec_request_mode(TUIData *tui, TerminalDecMode mode) | ||||
|   out(tui, buf, (size_t)len); | ||||
| } | ||||
|  | ||||
| /// Handle a DECRPM response from the terminal. | ||||
| void tui_dec_report_mode(TUIData *tui, TerminalDecMode mode, TerminalModeState state) | ||||
| /// Handle a mode report (DECRPM) from the terminal. | ||||
| void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   switch (state) { | ||||
|   case kTerminalModeNotRecognized: | ||||
|   case kTerminalModePermanentlySet: | ||||
|   case kTerminalModePermanentlyReset: | ||||
|   case kTermModeNotRecognized: | ||||
|   case kTermModePermanentlySet: | ||||
|   case kTermModePermanentlyReset: | ||||
|     // If the mode is not recognized, or if the terminal emulator does not allow it to be changed, | ||||
|     // then there is nothing to do | ||||
|     break; | ||||
|   case kTerminalModeSet: | ||||
|   case kTerminalModeReset: | ||||
|   case kTermModeSet: | ||||
|   case kTermModeReset: | ||||
|     // The terminal supports changing the given mode | ||||
|     switch (mode) { | ||||
|     case kDecModeSynchronizedOutput: | ||||
|     case kTermModeSynchronizedOutput: | ||||
|       // Ref: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 | ||||
|       tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync", | ||||
|                                                    "\x1b[?2026%?%p1%{1}%-%tl%eh%;"); | ||||
| @@ -244,6 +247,21 @@ void tui_dec_report_mode(TUIData *tui, TerminalDecMode mode, TerminalModeState s | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Query the terminal emulator to see if it supports Kitty's keyboard protocol. | ||||
| /// | ||||
| /// Write CSI ? u followed by a primary device attributes request (CSI c). If | ||||
| /// a primary device attributes response is received without first receiving an | ||||
| /// answer to the progressive enhancement query (CSI u), then the terminal does | ||||
| /// not support the Kitty keyboard protocol. | ||||
| /// | ||||
| /// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol | ||||
| static void tui_query_kitty_keyboard(TUIData *tui) | ||||
|   FUNC_ATTR_NONNULL_ALL | ||||
| { | ||||
|   tui->input.waiting_for_kkp_response = true; | ||||
|   out(tui, S_LEN("\x1b[?u\x1b[c")); | ||||
| } | ||||
|  | ||||
| static void terminfo_start(TUIData *tui) | ||||
| { | ||||
|   tui->scroll_region_is_full_screen = true; | ||||
| @@ -277,9 +295,6 @@ static void terminfo_start(TUIData *tui) | ||||
|   tui->unibi_ext.set_cursor_style = -1; | ||||
|   tui->unibi_ext.reset_cursor_style = -1; | ||||
|   tui->unibi_ext.set_underline_color = -1; | ||||
|   tui->unibi_ext.enable_extended_keys = -1; | ||||
|   tui->unibi_ext.disable_extended_keys = -1; | ||||
|   tui->unibi_ext.get_extkeys = -1; | ||||
|   tui->unibi_ext.sync = -1; | ||||
|   tui->out_fd = STDOUT_FILENO; | ||||
|   tui->out_isatty = os_isatty(tui->out_fd); | ||||
| @@ -356,11 +371,10 @@ static void terminfo_start(TUIData *tui) | ||||
|   // Query support for mode 2026 (Synchronized Output). Some terminals also | ||||
|   // support an older DCS sequence for synchronized output, but we will only use | ||||
|   // mode 2026 | ||||
|   tui_dec_request_mode(tui, kDecModeSynchronizedOutput); | ||||
|   tui_request_term_mode(tui, kTermModeSynchronizedOutput); | ||||
|  | ||||
|   // Query the terminal to see if it supports CSI u | ||||
|   tui->input.waiting_for_csiu_response = 5; | ||||
|   unibi_out_ext(tui, tui->unibi_ext.get_extkeys); | ||||
|   // Query the terminal to see if it supports Kitty's keyboard protocol | ||||
|   tui_query_kitty_keyboard(tui); | ||||
|  | ||||
|   int ret; | ||||
|   uv_loop_init(&tui->write_loop); | ||||
| @@ -403,8 +417,10 @@ static void terminfo_stop(TUIData *tui) | ||||
|   // Reset cursor to normal before exiting alternate screen. | ||||
|   unibi_out(tui, unibi_cursor_normal); | ||||
|   unibi_out(tui, unibi_keypad_local); | ||||
|   // Disable extended keys before exiting alternate screen. | ||||
|   unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys); | ||||
|  | ||||
|   // Reset the key encoding | ||||
|   tui_reset_key_encoding(tui); | ||||
|  | ||||
|   // May restore old title before exiting alternate screen. | ||||
|   tui_set_title(tui, (String)STRING_INIT); | ||||
|   if (ui_client_exit_status == 0) { | ||||
| @@ -1949,12 +1965,6 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo | ||||
| #define XTERM_SETAB_16 \ | ||||
|   "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" | ||||
|  | ||||
|   // Query the terminal to see if it supports CSI u key encoding by writing CSI | ||||
|   // ? u followed by a request for the primary device attributes (CSI c) | ||||
|   // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol | ||||
|   tui->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys", | ||||
|                                                       "\x1b[?u\x1b[c"); | ||||
|  | ||||
|   // Terminals with 256-colour SGR support despite what terminfo says. | ||||
|   if (unibi_get_num(ut, unibi_max_colors) < 256) { | ||||
|     // See http://fedoraproject.org/wiki/Features/256_Color_Terminals | ||||
| @@ -2247,8 +2257,9 @@ static void augment_terminfo(TUIData *tui, const char *term, int vte_version, in | ||||
|   } | ||||
|  | ||||
|   if (!kitty && (vte_version == 0 || vte_version >= 5400)) { | ||||
|     // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u | ||||
|     tui->input.extkeys_type = kExtkeysXterm; | ||||
|     // Fallback to Xterm's modifyOtherKeys if terminal does not support the | ||||
|     // Kitty keyboard protocol | ||||
|     tui->input.key_encoding = kKeyEncodingXterm; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -6,16 +6,16 @@ | ||||
| typedef struct TUIData TUIData; | ||||
|  | ||||
| typedef enum { | ||||
|   kDecModeSynchronizedOutput = 2026, | ||||
| } TerminalDecMode; | ||||
|   kTermModeSynchronizedOutput = 2026, | ||||
| } TermMode; | ||||
|  | ||||
| typedef enum { | ||||
|   kTerminalModeNotRecognized = 0, | ||||
|   kTerminalModeSet = 1, | ||||
|   kTerminalModeReset = 2, | ||||
|   kTerminalModePermanentlySet = 3, | ||||
|   kTerminalModePermanentlyReset = 4, | ||||
| } TerminalModeState; | ||||
|   kTermModeNotRecognized = 0, | ||||
|   kTermModeSet = 1, | ||||
|   kTermModeReset = 2, | ||||
|   kTermModePermanentlySet = 3, | ||||
|   kTermModePermanentlyReset = 4, | ||||
| } TermModeState; | ||||
|  | ||||
| #ifdef INCLUDE_GENERATED_DECLARATIONS | ||||
| # include "tui/tui.h.generated.h" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user