git-file-history 0.1.0

TUI for browsing the Git history of a single file
use std::borrow::Cow;

pub(crate) fn escape_text(text: &str) -> Cow<'_, str> {
    let mut safe = None;

    for (idx, ch) in text.char_indices() {
        if ch.is_control() && ch != '\t' {
            // Lazily allocate only when escaping is needed. `idx` is a byte
            // offset from char_indices, so slicing to it is UTF-8 safe.
            let safe = safe.get_or_insert_with(|| {
                let mut safe = String::with_capacity(text.len());
                safe.push_str(&text[..idx]);
                safe
            });
            safe.extend(ch.escape_default());
        } else if let Some(safe) = &mut safe {
            safe.push(ch);
        }
    }

    safe.map_or(Cow::Borrowed(text), Cow::Owned)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn escape_text_escapes_control_characters() {
        assert_eq!(
            escape_text("ok\x1b]52;c;bad\x07"),
            "ok\\u{1b}]52;c;bad\\u{7}"
        );
        assert_eq!(escape_text("keeps\ttabs"), "keeps\ttabs");
    }

    #[test]
    fn escape_text_preserves_multibyte_text_around_control_characters() {
        assert_eq!(escape_text("å\x1b"), "å\\u{1b}中");
    }
}