use galaxyui::keymap::Keystroke; use regex::Regex; use std::ops::Range; use crate::terminal::model::escape_sequences::C0; lazy_static::lazy_static! { /// Matches control code values specified in hex format delimited by angle brackets, e.g. <0x1B>. static ref CONTROL_CODE_REGEX: Regex = Regex::new(r"<0x([0-9a-fA-F]{2})>").expect("Regex is valid."); /// Matches an incomplete control code at the end of the response stream. static ref INCOMPLETE_CONTROL_CODE_REGEX: Regex = Regex::new(r"<(0|0x|0x[0-9a-fA-F]|0x[0-9a-fA-F]{2})$").expect("Regex is valid."); } /// Parsed output that separates raw bytes from display-friendly text. #[derive(Debug, Clone, Default)] pub struct ParsedControlCodeOutput { /// Human-readable display text (e.g., "" instead of 0x03). pub display: String, /// Ranges in `display` where control codes appear (for styling). pub control_code_ranges: Vec>, } /// Parses raw bytes containing control codes like `\x1B`. pub fn parse_control_codes_from_bytes(bytes: &[u8]) -> ParsedControlCodeOutput { let mut parsed_output = String::new(); let mut control_code_ranges = vec![]; let mut output_bytes = Vec::with_capacity(bytes.len()); for &byte in bytes { output_bytes.push(byte); if let Some(keystroke) = byte_value_to_keystroke(byte) { let keystroke_string = format!(" <{}>", keystroke.normalized()); control_code_ranges .push(parsed_output.len() + 1..parsed_output.len() + keystroke_string.len()); parsed_output.push_str(&keystroke_string); } else if byte.is_ascii_graphic() || byte == b' ' { parsed_output.push(byte as char); } else { // For non-control, non-printable bytes, show as hex notation let hex_string = format!("<0x{byte:02X}>"); parsed_output.push_str(&hex_string); } } ParsedControlCodeOutput { display: parsed_output, control_code_ranges, } } /// Returns the corresponding `Keystroke` for the given byte value if it represents a C0 control code. /// /// For example, given the byte value for `C0::CR` (carriage return), returns the "enter" `Keystroke`. fn byte_value_to_keystroke(value: u8) -> Option { let (ctrl, key) = match value { C0::SOH => (true, "a".into()), C0::STX => (true, "b".into()), C0::ETX => (true, "c".into()), C0::EOT => (true, "d".into()), C0::ENQ => (true, "e".into()), C0::ACK => (true, "f".into()), C0::BEL => (true, "g".into()), C0::VT => (true, "k".into()), C0::FF => (true, "l".into()), C0::SO => (true, "n".into()), C0::SI => (true, "o".into()), C0::DLE => (true, "p".into()), C0::XON => (true, "q".into()), C0::DC2 => (true, "r".into()), C0::XOFF => (true, "s".into()), C0::DC4 => (true, "t".into()), C0::NAK => (true, "u".into()), C0::SYN => (true, "v".into()), C0::ETB => (true, "w".into()), C0::CAN => (true, "x".into()), C0::EM => (true, "y".into()), C0::SUB => (true, "z".into()), C0::FS => (true, "\\".into()), C0::GS => (true, "]".into()), C0::RS => (true, "^".into()), C0::US => (true, "_".into()), C0::HT => (false, "tab".into()), C0::ESC => (false, "escape".into()), C0::DEL => (false, "delete".into()), C0::BS => (false, "backspace".into()), C0::LF | C0::CR => (false, "enter".into()), _ => return None, }; Some(Keystroke { ctrl, alt: false, shift: false, cmd: false, meta: false, key, }) }