102 lines
3.7 KiB
Rust
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,
|
|
})
|
|
}
|