use rich_ext::sanitize_terminal_controls;
pub(crate) const VIEW_LIMIT: u64 = 8 * 1024 * 1024;
pub(crate) const LINE_LIMIT: usize = 20_000;
pub(crate) const BYTES_LIMIT: u64 = 64 * 1024;
pub(crate) const INSPECT_LIMIT: u64 = 64 * 1024 * 1024;
pub(crate) const CAPTURE_LIMIT: usize = 1024 * 1024;
pub(crate) const THEME_FILE_LIMIT: u64 = 1024 * 1024;
pub(crate) fn size(bytes: u64) -> String {
const MIB: u64 = 1024 * 1024;
if bytes >= MIB && bytes.is_multiple_of(MIB) {
format!("{} MiB", bytes / MIB)
} else if bytes >= 1024 && bytes.is_multiple_of(1024) {
format!("{} KiB", bytes / 1024)
} else {
format!("{bytes} bytes")
}
}
pub(crate) fn neutralize_for_decoder(input: &str) -> String {
let chars: Vec<char> = input.chars().collect();
let mut out = String::with_capacity(input.len());
let mut i = 0;
while i < chars.len() {
let ch = chars[i];
if ch != '\u{1b}' {
match ch {
'\n' | '\t' | '\r' => out.push(ch),
'\0'..='\u{1f}' | '\u{7f}'..='\u{9f}' => {
out.push_str(&sanitize_terminal_controls(ch.encode_utf8(&mut [0; 4])))
}
_ => out.push(ch),
}
i += 1;
continue;
}
match chars.get(i + 1).copied() {
Some(']') => {
let mut j = i + 2;
let mut end = None;
while j < chars.len() && chars[j] != '\n' {
if chars[j] == '\u{7}' {
end = Some(j + 1);
break;
}
if chars[j] == '\u{1b}' && chars.get(j + 1) == Some(&'\\') {
end = Some(j + 2);
break;
}
j += 1;
}
i = end.unwrap_or(i + 2);
}
Some('[') => {
let mut j = i + 2;
while j < chars.len() && ('\u{30}'..='\u{3f}').contains(&chars[j]) {
j += 1;
}
while j < chars.len() && ('\u{20}'..='\u{2f}').contains(&chars[j]) {
j += 1;
}
if j < chars.len() && ('\u{40}'..='\u{7e}').contains(&chars[j]) {
out.extend(&chars[i..=j]);
i = j + 1;
} else {
out.push('␛');
i += 1;
}
}
Some(c) if c == '(' || ('@'..='Z').contains(&c) || ('\\'..='_').contains(&c) => {
i += 2;
}
_ => {
out.push('␛');
i += 1;
}
}
}
out
}
pub(crate) fn shown(text: &str) -> String {
sanitize_terminal_controls(text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn styles_survive_and_other_controls_do_not() {
assert_eq!(
neutralize_for_decoder("\u{1b}[31mred\u{1b}[0m"),
"\u{1b}[31mred\u{1b}[0m"
);
assert_eq!(neutralize_for_decoder("a\u{1b}]0;T\u{7}b"), "ab");
assert_eq!(
neutralize_for_decoder("a\u{1b}]8;;http://x\u{1b}\\l\u{1b}]8;;\u{1b}\\"),
"al"
);
assert_eq!(neutralize_for_decoder("a\u{9b}2J"), "a\\u{009B}2J");
assert_eq!(neutralize_for_decoder("a\u{1b}7b\u{7}"), "a␛7b␇");
assert_eq!(neutralize_for_decoder("x\u{1b}[é"), "x␛[é");
assert_eq!(
neutralize_for_decoder("x\u{1b}]0;no end\ny"),
"x0;no end\ny"
);
assert_eq!(neutralize_for_decoder("a\r\n\tb"), "a\r\n\tb");
}
#[test]
fn sizes_read_naturally() {
assert_eq!(size(64 * 1024 * 1024), "64 MiB");
assert_eq!(size(8192), "8 KiB");
assert_eq!(size(10), "10 bytes");
}
}