Files
galaxy/app/src/ai/control_code_parser.rs
T

102 lines
3.7 KiB
Rust

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., "<ctrl-c>" instead of 0x03).
pub display: String,
/// Ranges in `display` where control codes appear (for styling).
pub control_code_ranges: Vec<Range<usize>>,
}
/// 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<Keystroke> {
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,
})
}