use std::fmt;
use cfg_if::cfg_if;
use crate::{
moves::{move_down, move_right},
write_utils::{writeln_truncated, writeln_untruncated},
};
#[derive(Clone, Copy)]
pub struct Exhibit<'t, T: ?Sized> {
inner: &'t T,
x: Option<u16>,
y: Option<u16>,
width: Option<u16>,
height: Option<u16>,
redact: bool,
#[cfg(feature = "ansi")]
strip_ansi: bool,
#[cfg(feature = "unicode")]
shade_wide: bool,
}
impl<'t, T> Exhibit<'t, T> {
pub(super) fn new(inner: &'t T) -> Self {
Self {
inner,
x: None,
y: None,
width: None,
height: None,
redact: false,
#[cfg(feature = "ansi")]
strip_ansi: false,
#[cfg(feature = "unicode")]
shade_wide: false,
}
}
#[must_use]
pub fn x(&mut self, x: u16) -> &mut Self {
self.x = Some(x);
self
}
#[must_use]
pub fn y(&mut self, y: u16) -> &mut Self {
self.y = Some(y);
self
}
#[must_use]
pub fn pos(&mut self, x: u16, y: u16) -> &mut Self {
self.x(x).y(y)
}
#[must_use]
pub fn width(&mut self, width: u16) -> &mut Self {
self.width = Some(width);
self
}
#[must_use]
pub fn height(&mut self, height: u16) -> &mut Self {
self.height = Some(height);
self
}
#[must_use]
pub fn size(&mut self, width: u16, height: u16) -> &mut Self {
self.width(width).height(height)
}
#[must_use]
pub fn redact(&mut self, redact: bool) -> &mut Self {
self.redact = redact;
self
}
#[cfg(feature = "ansi")]
#[must_use]
pub fn strip_ansi(&mut self, strip_ansi: bool) -> &mut Self {
self.strip_ansi = strip_ansi;
self
}
#[cfg(feature = "unicode")]
#[must_use]
pub fn shade_wide(&mut self, shade_wide: bool) -> &mut Self {
self.shade_wide = shade_wide;
self
}
}
impl<T> fmt::Display for Exhibit<'_, T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(y) = self.y {
move_down(f, y)?;
}
cfg_if! {
if #[cfg(feature = "ansi")] {
let strip_ansi = self.strip_ansi;
} else {
let strip_ansi = false;
}
}
cfg_if! {
if #[cfg(feature = "unicode")] {
let shade_wide = self.shade_wide;
} else {
let shade_wide = false;
}
}
match (self.height, self.width) {
(None, None) if self.x.is_none() && !self.redact && !strip_ansi && !shade_wide => {
self.inner.fmt(f)
}
(None, None) => self.inner.to_string().lines().try_for_each(|line| {
if let Some(x) = self.x {
move_right(f, x)?;
}
writeln_untruncated(f, line, self.redact, strip_ansi, shade_wide)
}),
(Some(height), None) => self
.inner
.to_string()
.lines()
.take(height as usize)
.try_for_each(|line| {
if let Some(x) = self.x {
move_right(f, x)?;
}
writeln_untruncated(f, line, self.redact, strip_ansi, shade_wide)
}),
(None, Some(width)) => self.inner.to_string().lines().try_for_each(|line| {
if let Some(x) = self.x {
move_right(f, x)?;
}
writeln_truncated(f, line, self.redact, strip_ansi, shade_wide, width as usize)
}),
(Some(height), Some(width)) => self
.inner
.to_string()
.lines()
.take(height as usize)
.try_for_each(|line| {
if let Some(x) = self.x {
move_right(f, x)?;
}
writeln_truncated(f, line, self.redact, strip_ansi, shade_wide, width as usize)
}),
}
}
}
#[cfg(test)]
mod tests {
use cfg_if::cfg_if;
#[cfg(feature = "ansi")]
use console::style;
use insta::assert_display_snapshot;
use crate::ExhibitExt;
#[test]
fn ascii() {
let text = "Hello, world!\nThis is a test!";
assert_eq!(text.exhibit().to_string(), text);
assert_display_snapshot!(text.exhibit().height(1), @r###"
Hello, world!
"###);
assert_display_snapshot!(text.exhibit().width(4), @r###"
Hell
This
"###);
assert_display_snapshot!(text.exhibit().size(5, 1), @r###"
Hello
"###);
}
#[cfg(feature = "unicode")]
#[test]
fn redact() {
let text = "你好世界!\nThis is a test!";
assert_ne!(text.exhibit().redact(true).to_string(), text);
assert_display_snapshot!(text.exhibit().redact(true).height(1), @r###"
▇▇▇▇▇▇▇▇!
"###);
assert_display_snapshot!(text.exhibit().redact(true).width(4), @r###"
▇▇▇▇
▆▆▆▅
"###);
assert_display_snapshot!(text.exhibit().redact(true).size(5, 1), @r###"
▇▇▇▇
"###);
}
#[cfg(all(feature = "ansi", feature = "unicode"))]
#[test]
fn redact_ansi() {
let text = format!(
"{}\n{}",
style(format!("{}世界!", style("你好").bold())).cyan(),
style(format!("Th{} a test!", style("is is").italic())).blue()
);
assert_ne!(text.exhibit().redact(true).to_string(), text);
assert_display_snapshot!(text.exhibit().redact(true).height(1), @r###"
[36m[1m▇▇▇▇[0m▇▇▇▇![0m
"###);
assert_display_snapshot!(text.exhibit().redact(true).width(4), @r###"
[36m[1m▇▇▇▇[0m[0m
[34m▆▆[3m▆▅[0m[0m
"###);
assert_display_snapshot!(text.exhibit().redact(true).size(5, 1), @r###"
[36m[1m▇▇▇▇[0m[0m
"###);
}
#[cfg(feature = "ansi")]
#[test]
fn ansi() {
let text = format!(
"{}\n{}",
style(format!("{}, world!", style("Hello").bold())).cyan(),
style(format!("Th{} a test!", style("is is").italic())).blue()
);
assert_eq!(text.exhibit().to_string(), text);
assert_display_snapshot!(text.exhibit().height(1), @r###"
[36m[1mHello[0m, world![0m
"###);
assert_display_snapshot!(text.exhibit().width(4), @r###"
[36m[1mHell[0m[0m
[34mTh[3mis[0m[0m
"###);
assert_display_snapshot!(text.exhibit().size(5, 1), @r###"
[36m[1mHello[0m[0m
"###);
}
#[cfg(feature = "ansi")]
#[test]
fn strip_ansi() {
let text = format!(
"{}\n{}",
style(format!("{}, world!", style("Hello").bold())).cyan(),
style(format!("Th{} a test!", style("is is").italic())).blue()
);
assert_ne!(text.exhibit().strip_ansi(true).to_string(), text);
assert_display_snapshot!(text.exhibit().strip_ansi(true).height(1), @r###"
Hello, world!
"###);
assert_display_snapshot!(text.exhibit().strip_ansi(true).width(4), @r###"
Hell
This
"###);
assert_display_snapshot!(text.exhibit().strip_ansi(true).size(5, 1), @r###"
Hello
"###);
}
#[cfg(feature = "unicode")]
#[test]
fn unicode() {
let text = "你好世界!\nThis is a test!";
assert_eq!(text.exhibit().to_string(), text);
assert_display_snapshot!(text.exhibit().height(1), @r###"
你好世界!
"###);
assert_display_snapshot!(text.exhibit().width(11), @r###"
你好世界!
This i
"###);
assert_display_snapshot!(text.exhibit().size(11, 1), @r###"
你好世界!
"###);
}
#[cfg(feature = "unicode")]
#[test]
fn shade_wide() {
let text = "你好世界!\nThis is a test!";
assert_ne!(text.exhibit().shade_wide(true).to_string(), text);
assert_display_snapshot!(text.exhibit()
.shade_wide(true).height(1), @r###"
░░░░░░░░!
"###);
assert_display_snapshot!(text.exhibit()
.shade_wide(true).width(11), @r###"
░░░░░░░░!
░░░░░░░░ ░░
"###);
assert_display_snapshot!(text.exhibit()
.shade_wide(true).size(11, 1), @r###"
░░░░░░░░!
"###);
}
#[cfg(all(feature = "ansi", feature = "unicode"))]
#[test]
fn ansi_with_unicode() {
let text = format!(
"{}\n{}",
style(format!("{}, world!", style("Hello").bold())).cyan(),
style(format!("Th{} a test!", style("is is").italic())).blue()
);
assert_eq!(text.exhibit().to_string(), text);
assert_display_snapshot!(text.exhibit().height(1), @r###"
[36m[1mHello[0m, world![0m
"###);
assert_display_snapshot!(text.exhibit().width(9), @r###"
[36m[1mHell[0m[0m
[34mTh[3mis [0m[0m
"###);
assert_display_snapshot!(text.exhibit().size(10, 1), @r###"
[36m[1mHello[0m[0m
"###);
}
#[test]
fn position() {
let text = "Hello, world!\nThis is a test!";
cfg_if! {
if #[cfg(any(feature = "cursor-crossterm", feature = "cursor-termion"))] {
let expected = "[2B\r[1CHello, world!\n\r[1CThis is a test!\n";
} else {
let expected = r###"
Hello, world!
This is a test!
"###;
}
}
assert_eq!(text.exhibit().pos(1, 2).to_string(), expected);
}
}