use super::*;
pub fn str_width(s: &str) -> usize {
s.chars().map(char_width).sum()
}
pub fn char_width(c: char) -> usize {
UnicodeWidthChar::width(c).unwrap_or(0)
}
pub fn take_width(s: &str, max: usize) -> (String, usize) {
let mut out = String::new();
let mut w = 0;
for c in s.chars() {
let cw = char_width(c);
if w + cw > max {
break;
}
out.push(c);
w += cw;
}
(out, w)
}
pub(super) fn fmt_date(t: SystemTime) -> String {
let secs = t.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() as i64;
let days = secs.div_euclid(86_400);
let tod = secs.rem_euclid(86_400);
let (hh, mm) = (tod / 3600, (tod % 3600) / 60);
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = y + if m <= 2 { 1 } else { 0 };
format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}")
}
pub(super) fn wrap_text(s: &str, width: usize) -> Vec<String> {
let width = width.max(1);
let mut out = Vec::new();
let mut line = String::new();
let mut w = 0usize;
let push_overlong = |word: &str, out: &mut Vec<String>, line: &mut String, w: &mut usize| {
for ch in word.chars() {
let cw = char_width(ch);
if *w > 0 && *w + cw > width {
out.push(std::mem::take(line));
*w = 0;
}
line.push(ch);
*w += cw;
}
};
let push_word = |word: &str, out: &mut Vec<String>, line: &mut String, w: &mut usize| {
let ww = str_width(word);
if *w == 0 {
push_overlong(word, out, line, w);
} else if *w + 1 + ww <= width {
line.push(' ');
line.push_str(word);
*w += 1 + ww;
} else {
out.push(std::mem::take(line));
*w = 0;
push_overlong(word, out, line, w);
}
};
for word in s.split_whitespace() {
push_word(word, &mut out, &mut line, &mut w);
}
out.push(line);
out
}
pub(super) fn wrap_preserve(s: &str, width: usize) -> Vec<String> {
let width = width.max(1);
let mut out = Vec::new();
let mut line = String::new();
let mut w = 0usize;
for ch in s.chars() {
let cw = char_width(ch);
if w + cw > width && !line.is_empty() {
if ch != ' ' {
if let Some(brk) = line.rfind(' ') {
let tail: String = line[brk + 1..].to_string();
if !tail.is_empty() {
line.truncate(brk + 1);
out.push(std::mem::take(&mut line));
line = tail;
w = str_width(&line);
} else {
out.push(std::mem::take(&mut line));
w = 0;
}
} else {
out.push(std::mem::take(&mut line));
w = 0;
}
} else {
out.push(std::mem::take(&mut line));
w = 0;
}
}
line.push(ch);
w += cw;
}
out.push(line);
out
}
pub fn sanitize_line(s: &str) -> String {
let mut out = String::with_capacity(s.len());
sanitize_into(&mut out, s);
out
}
#[inline]
pub(super) fn is_clean_ascii(s: &str) -> bool {
s.bytes().all(|b| b.wrapping_sub(0x20) < 0x5f)
}
pub(super) fn sanitize_into(out: &mut String, s: &str) {
if is_clean_ascii(s) {
out.push_str(s);
return;
}
let mut col = 0usize;
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
match c {
'\t' => {
let n = 4 - (col % 4);
out.extend(std::iter::repeat_n(' ', n));
col += n;
}
'\r' | '\n' => {}
'\u{1b}' => match chars.peek() {
Some('[') => {
chars.next();
while let Some(&p) = chars.peek() {
chars.next();
if ('@'..='~').contains(&p) {
break;
}
}
}
Some(']') => {
chars.next();
while let Some(&p) = chars.peek() {
chars.next();
if p == '\u{7}' {
break;
}
if p == '\u{1b}' {
if chars.peek() == Some(&'\\') {
chars.next();
}
break;
}
}
}
_ => {}
},
c if c.is_control() => {}
c => {
out.push(c);
col += 1;
}
}
}
}