use std::borrow::Cow;
use crate::utils::ColumnDisplayInfo;
#[cfg(feature = "custom_styling")]
mod custom_styling;
#[cfg(not(feature = "custom_styling"))]
mod normal;
#[cfg(feature = "custom_styling")]
pub use custom_styling::*;
#[cfg(not(feature = "custom_styling"))]
pub use normal::*;
pub fn split_line(line: &str, info: &ColumnDisplayInfo, delimiter: char) -> Vec<String> {
let mut lines = Vec::new();
let content_width = usize::from(info.content_width);
let mut elements = split_line_by_delimiter(line, delimiter);
elements.reverse();
let delimiter_width = measure_text_width(delimiter.encode_utf8(&mut [0; 4]));
let mut current_line = String::new();
let mut current_length = 0;
while let Some(next) = elements.pop() {
let next_length = measure_text_width(&next);
let mut added_length = next_length + current_length;
if !current_line.is_empty() {
added_length += 1;
}
let mut remaining_width = content_width - current_length;
if !current_line.is_empty() {
remaining_width = remaining_width.saturating_sub(1);
}
if added_length <= content_width {
if !current_line.is_empty() {
current_line.push(delimiter);
current_length += delimiter_width;
}
current_line.push_str(&next);
current_length += next_length;
(current_line, current_length) =
check_if_full(&mut lines, content_width, current_line, current_length);
continue;
}
if !current_line.is_empty() && remaining_width <= MIN_FREE_CHARS {
elements.push(next);
lines.push(current_line);
current_line = String::new();
current_length = 0;
continue;
}
if next_length > content_width {
let new_line = current_line.is_empty();
if !new_line {
current_line.push(delimiter);
}
let (head, tail) = match next {
Cow::Borrowed(word) => split_long_word(remaining_width, word),
Cow::Owned(word) => {
let (head, tail) = split_long_word(remaining_width, &word);
(Cow::Owned(head.into_owned()), Cow::Owned(tail.into_owned()))
}
};
if new_line && head.is_empty() {
let mut chars = tail.chars();
current_line.push(chars.next().unwrap());
elements.push(Cow::Owned(chars.collect()));
} else {
current_line.push_str(&head);
elements.push(tail);
}
lines.push(current_line);
current_line = String::new();
current_length = 0;
continue;
}
lines.push(current_line);
current_line = next.to_string();
current_length = next_length;
(current_line, current_length) =
check_if_full(&mut lines, content_width, current_line, current_length);
}
if !current_line.is_empty() {
lines.push(current_line);
}
lines
}
const MIN_FREE_CHARS: usize = 2;
fn check_if_full(
lines: &mut Vec<String>,
content_width: usize,
current_line: String,
current_length: usize,
) -> (String, usize) {
if current_length > content_width.saturating_sub(MIN_FREE_CHARS) {
lines.push(current_line);
return (String::new(), 0);
}
(current_line, current_length)
}
#[cfg(test)]
mod tests {
use unicode_width::UnicodeWidthStr;
use super::*;
#[test]
fn test_split_long_word() {
let emoji = "🙂↕️"; assert_eq!(emoji.len(), 13);
assert_eq!(emoji.chars().count(), 4);
assert_eq!(emoji.width(), 2);
let (word, remaining) = split_long_word(emoji.width(), emoji);
assert_eq!(word, "\u{1F642}\u{200D}\u{2195}\u{FE0F}");
assert_eq!(word.len(), 13);
assert_eq!(word.chars().count(), 4);
assert_eq!(word.width(), 2);
assert!(remaining.is_empty());
}
}