use std::borrow::Cow;
use unicode_width::UnicodeWidthStr;
pub(crate) fn coerce_crlf(input: &str) -> Cow<'_, str> {
let mut result = Cow::Borrowed(input);
let mut cursor: usize = 0;
for (idx, _) in input.match_indices('\n') {
if !(idx > 0 && input.as_bytes()[idx - 1] == b'\r') {
if let Cow::Borrowed(_) = result {
let mut owned = String::with_capacity(input.len() + 1);
owned.push_str(&input[cursor..idx]);
result = Cow::Owned(owned);
} else {
result += &input[cursor..idx];
}
result += "\r\n";
cursor = idx + 1;
}
}
if let Cow::Owned(_) = result {
result += &input[cursor..input.len()];
}
result
}
pub(crate) fn strip_ansi(string: &str) -> String {
String::from_utf8(strip_ansi_escapes::strip(string))
.map_err(|_| ())
.unwrap_or_else(|_| string.to_owned())
}
pub(crate) fn estimate_required_lines(input: &str, screen_width: u16) -> usize {
input.lines().fold(0, |acc, line| {
let wrap = estimate_single_line_wraps(line, screen_width);
acc + 1 + wrap
})
}
pub(crate) fn estimate_single_line_wraps(line: &str, terminal_columns: u16) -> usize {
let estimated_width = line_width(line);
let terminal_columns: usize = terminal_columns.into();
let estimated_line_count = (estimated_width + terminal_columns - 1) / terminal_columns;
estimated_line_count.saturating_sub(1)
}
pub(crate) fn line_width(line: &str) -> usize {
strip_ansi(line).width()
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use rstest::rstest;
#[rstest]
#[case("sentence\nsentence", "sentence\r\nsentence")]
#[case("sentence\r\nsentence", "sentence\r\nsentence")]
#[case("sentence\nsentence\n", "sentence\r\nsentence\r\n")]
#[case("😇\nsentence", "😇\r\nsentence")]
#[case("sentence\n😇", "sentence\r\n😇")]
#[case("\n", "\r\n")]
#[case("", "")]
fn test_coerce_crlf(#[case] input: &str, #[case] expected: &str) {
let result = coerce_crlf(input);
assert_eq!(result, expected);
assert!(
input != expected || matches!(result, Cow::Borrowed(_)),
"Unnecessary allocation"
)
}
}