use std::fmt;
use cfg_if::cfg_if;
#[cfg(feature = "unicode")]
use unicode_width::UnicodeWidthChar;
#[inline]
pub(super) fn writeln_untruncated(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
strip_ansi: bool,
shade_wide: bool,
) -> fmt::Result {
write_untruncated(f, line, redact, strip_ansi, shade_wide)?;
writeln!(f)
}
#[inline]
pub(super) fn writeln_truncated(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
strip_ansi: bool,
shade_wide: bool,
mut width: usize,
) -> fmt::Result {
write_truncated(f, line, redact, strip_ansi, shade_wide, &mut width)?;
writeln!(f)
}
cfg_if! {
if #[cfg(feature = "ansi")] {
#[inline]
fn try_map_chunks(
f: &mut fmt::Formatter,
line: &str,
strip_ansi: bool,
mut ignore_ansi_fmt: impl FnMut(&mut fmt::Formatter, &str) -> fmt::Result,
mut ansi_fmt: impl FnMut(&mut fmt::Formatter, &str) -> fmt::Result,
) -> fmt::Result {
let stripped_line_bytes = strip_ansi_escapes::strip(line).map_err(|_| fmt::Error)?;
let stripped_line = std::str::from_utf8(&stripped_line_bytes).map_err(|_| fmt::Error)?;
if strip_ansi {
ignore_ansi_fmt(f, stripped_line)
} else {
let diff = similar::TextDiff::configure()
.algorithm(similar::Algorithm::Myers)
.diff_chars(line, stripped_line);
let remapper = similar::utils::TextDiffRemapper::from_text_diff(&diff, line, stripped_line);
let mut iter = diff.ops().iter().flat_map(|x| remapper.iter_slices(x));
iter.try_for_each(|(tag, chunk)| match tag {
similar::ChangeTag::Delete => ansi_fmt(f, chunk),
similar::ChangeTag::Equal => ignore_ansi_fmt(f, chunk),
similar::ChangeTag::Insert => unreachable!("ChangeTag::Insert should not be possible by construction"),
})
}
}
#[inline]
fn write_untruncated(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
strip_ansi: bool,
shade_wide: bool,
) -> fmt::Result {
if redact || strip_ansi {
try_map_chunks(f, line, strip_ansi, |f, chunk| write_untruncated_ignore_ansi(f, chunk, redact, shade_wide), |f, chunk| write!(f, "{chunk}"))
} else {
write_untruncated_ignore_ansi(f, line, redact, shade_wide)
}
}
#[inline]
fn write_truncated(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
strip_ansi: bool,
shade_wide: bool,
width: &mut usize,
) -> fmt::Result {
try_map_chunks(f, line, strip_ansi, |f, chunk| write_truncated_ignore_ansi(f, chunk, redact, shade_wide, width), |f, chunk| write!(f, "{chunk}"))
}
} else {
#[inline]
fn write_untruncated(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
_: bool,
shade_wide: bool,
) -> fmt::Result {
write_untruncated_ignore_ansi(f, line, redact, shade_wide)
}
#[inline]
fn write_truncated(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
_: bool,
shade_wide: bool,
width: &mut usize,
) -> fmt::Result {
write_truncated_ignore_ansi(f, line, redact, shade_wide, width)
}
}
}
#[allow(unused_variables)]
#[inline]
fn write_untruncated_ignore_ansi(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
shade_wide: bool,
) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "unicode")] {
let shade_wide = shade_wide;
} else {
let shade_wide = false;
}
}
if redact || shade_wide {
line.chars()
.try_for_each(|c| write_char(f, c, redact, shade_wide))
} else {
write!(f, "{line}")
}
}
#[inline]
fn write_truncated_ignore_ansi(
f: &mut fmt::Formatter,
line: &str,
redact: bool,
shade_wide: bool,
width: &mut usize,
) -> fmt::Result {
line.chars()
.take(*width)
.take_while(|c| should_write_char(*c, width))
.try_for_each(|c| write_char(f, c, redact, shade_wide))
}
#[allow(unused_variables)]
#[inline]
fn should_write_char(c: char, width: &mut usize) -> bool {
if *width == 0 {
return false;
}
cfg_if! {
if #[cfg(feature = "unicode")] {
let cw = c.width().unwrap_or(1);
if *width < cw {
return false;
}
*width -= cw;
} else {
*width -= 1;
}
}
true
}
#[inline]
fn should_redact_char(c: char) -> bool {
!c.is_whitespace() && !c.is_ascii_punctuation()
}
#[allow(unused_variables)]
#[inline]
fn write_char(f: &mut fmt::Formatter, c: char, redact: bool, shade_wide: bool) -> fmt::Result {
cfg_if! {
if #[cfg(feature = "unicode")] {
if redact || shade_wide {
let cw = c.width().unwrap_or(1);
if cw > 1 {
return (0..cw).try_for_each(|_| if redact {
write!(f, "{}", '▇')
} else {
write!(f, "{}", '░')
});
}
}
}
}
if redact && should_redact_char(c) {
if c == 'Q' {
return write!(f, "▇");
}
if c.is_uppercase() || "bdfhijklt".contains(c) {
return write!(f, "▆");
}
return write!(f, "▅");
}
write!(f, "{c}")
}