8000 chore(style): make Debug output for Text/Line/Span/Style more concise by joshka · Pull Request #1383 · ratatui/ratatui · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

chore(style): make Debug output for Text/Line/Span/Style more concise #1383

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 26, 2024
62 changes: 61 additions & 1 deletion src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ use std::fmt;

use bitflags::bitflags;
pub use color::{Color, ParseColorError};
use stylize::ColorDebugKind;
pub use stylize::{Styled, Stylize};

mod color;
Expand Down Expand Up @@ -223,7 +224,7 @@ impl fmt::Debug for Modifier {
/// buffer[(0, 0)].style(),
/// );
/// ```
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Style {
pub fg: Option<Color>,
Expand All @@ -234,6 +235,55 @@ pub struct Style {
pub sub_modifier: Modifier,
}

/// A custom debug implementation that prints only the fields that are not the default, and unwraps
/// the `Option`s.
impl fmt::Debug for Style {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Style::new()")?;
if let Some(fg) = self.fg {
fg.stylize_debug(ColorDebugKind::Foreground).fmt(f)?;
}
if let Some(bg) = self.bg {
bg.stylize_debug(ColorDebugKind::Background).fmt(f)?;
}
#[cfg(feature = "underline-color")]
if let Some(underline_color) = self.underline_color {
underline_color
.stylize_debug(ColorDebugKind::Underline)
.fmt(f)?;
}
for modifier in self.add_modifier.iter() {
match modifier {
Modifier::BOLD => f.write_str(".bold()")?,
Modifier::DIM => f.write_str(".dim()")?,
Modifier::ITALIC => f.write_str(".italic()")?,
Modifier::UNDERLINED => f.write_str(".underlined()")?,
Modifier::SLOW_BLINK => f.write_str(".slow_blink()")?,
Modifier::RAPID_BLINK => f.write_str(".rapid_blink()")?,
Modifier::REVERSED => f.write_str(".reversed()")?,
Modifier::HIDDEN => f.write_str(".hidden()")?,
Modifier::CROSSED_OUT => f.write_str(".crossed_out()")?,
_ => f.write_fmt(format_args!(".add_modifier(Modifier::{modifier:?})"))?,
}
}
for modifier in self.sub_modifier.iter() {
match modifier {
Modifier::BOLD => f.write_str(".not_bold()")?,
Modifier::DIM => f.write_str(".not_dim()")?,
Modifier::ITALIC => f.write_str(".not_italic()")?,
Modifier::UNDERLINED => f.write_str(".not_underlined()")?,
Modifier::SLOW_BLINK => f.write_str(".not_slow_blink()")?,
Modifier::RAPID_BLINK => f.write_str(".not_rapid_blink()")?,
Modifier::REVERSED => f.write_str(".not_reversed()")?,
Modifier::HIDDEN => f.write_str(".not_hidden()")?,
Modifier::CROSSED_OUT => f.write_str(".not_crossed_out()")?,
_ => f.write_fmt(format_args!(".remove_modifier(Modifier::{modifier:?})"))?,
}
}
Ok(())
}
}

impl Styled for Style {
type Item = Self;

Expand Down Expand Up @@ -549,6 +599,16 @@ mod tests {

use super::*;

#[rstest]
#[case(Style::new(), "Style::new()")]
#[case(Style::new().red(), "Style::new().red()")]
#[case(Style::new().on_blue(), "Style::new().on_blue()")]
#[case(Style::new().bold(), "Style::new().bold()")]
#[case(Style::new().not_italic(), "Style::new().not_italic()")]
fn debug(#[case] style: Style, #[case] expected: &'static str) {
assert_eq!(format!("{style:?}"), expected);
}

#[test]
fn combined_patch_gives_same_result_as_individual_patch() {
let styles = [
Expand Down
6 changes: 6 additions & 0 deletions src/style/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use std::{fmt, str::FromStr};

use crate::style::stylize::{ColorDebug, ColorDebugKind};

/// ANSI Color
///
/// All colors from the [ANSI color table] are supported (though some names are not exactly the
Expand Down Expand Up @@ -361,6 +363,10 @@ impl fmt::Display for Color {
}

impl Color {
pub(crate) const fn stylize_debug(self, kind: ColorDebugKind) -> ColorDebug {
ColorDebug { kind, color: self }
}

/// Converts a HSL representation to a `Color::Rgb` instance.
///
/// The `from_hsl` function converts the Hue, Saturation and Lightness values to a
Expand Down
150 changes: 150 additions & 0 deletions src/style/stylize.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt;

use paste::paste;

use crate::{
Expand All @@ -23,6 +25,75 @@ pub trait Styled {
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item;
}

/// A helper struct to make it easy to debug using the `Stylize` method names
pub(crate) struct ColorDebug {
pub kind: ColorDebugKind,
pub color: Color,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub(crate) enum ColorDebugKind {
Foreground,
Background,
#[cfg(feature = "underline-color")]
Underline,
}

impl fmt::Debug for ColorDebug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "underline-color")]
let is_underline = self.kind == ColorDebugKind::Underline;
#[cfg(not(feature = "underline-color"))]
let is_underline = false;
if is_underline
|| matches!(
self.color,
Color::Reset | Color::Indexed(_) | Color::Rgb(_, _, _)
)
{
match self.kind {
ColorDebugKind::Foreground => write!(f, ".fg(")?,
ColorDebugKind::Background => write!(f, ".bg(")?,
#[cfg(feature = "underline-color")]
ColorDebugKind::Underline => write!(f, ".underline_color(")?,
}
write!(f, "Color::{:?}", self.color)?;
write!(f, ")")?;
return Ok(());
}

match self.kind {
ColorDebugKind::Foreground => write!(f, ".")?,
ColorDebugKind::Background => write!(f, ".on_")?,
// TODO: .underline_color_xxx is not implemented on Stylize yet, but it should be
#[cfg(feature = "underline-color")]
ColorDebugKind::Underline => {
unreachable!("covered by the first part of the if statement")
}
}
match self.color {
Color::Black => write!(f, "black")?,
Color::Red => write!(f, "red")?,
Color::Green => write!(f, "green")?,
Color::Yellow => write!(f, "yellow")?,
Color::Blue => write!(f, "blue")?,
Color::Magenta => write!(f, "magenta")?,
Color::Cyan => write!(f, "cyan")?,
Color::Gray => write!(f, "gray")?,
Color::DarkGray => write!(f, "dark_gray")?,
Color::LightRed => write!(f, "light_red")?,
Color::LightGreen => write!(f, "light_green")?,
Color::LightYellow => write!(f, "light_yellow")?,
Color::LightBlue => write!(f, "light_blue")?,
Color::LightMagenta => write!(f, "light_magenta")?,
Color::LightCyan => write!(f, "light_cyan")?,
Color::White => write!(f, "white")?,
_ => unreachable!("covered by the first part of the if statement"),
}
write!(f, "()")
}
}

/// Generates two methods for each color, one for setting the foreground color (`red()`, `blue()`,
/// etc) and one for setting the background color (`on_red()`, `on_blue()`, etc.). Each method sets
/// the color of the style to the corresponding color.
Expand Down Expand Up @@ -231,6 +302,7 @@ impl Styled for String {
#[cfg(test)]
mod tests {
use itertools::Itertools;
use rstest::rstest;

use super::*;

Expand Down Expand Up @@ -423,4 +495,82 @@ mod tests {
Span::styled("hello", all_modifier_black)
);
}

#[rstest]
#[case(ColorDebugKind::Foreground, Color::Black, ".black()")]
#[case(ColorDebugKind::Foreground, Color::Red, ".red()")]
#[case(ColorDebugKind::Foreground, Color::Green, ".green()")]
#[case(ColorDebugKind::Foreground, Color::Yellow, ".yellow()")]
#[case(ColorDebugKind::Foreground, Color::Blue, ".blue()")]
#[case(ColorDebugKind::Foreground, Color::Magenta, ".magenta()")]
#[case(ColorDebugKind::Foreground, Color::Cyan, ".cyan()")]
#[case(ColorDebugKind::Foreground, Color::Gray, ".gray()")]
#[case(ColorDebugKind::Foreground, Color::DarkGray, ".dark_gray()")]
#[case(ColorDebugKind::Foreground, Color::LightRed, ".light_red()")]
#[case(ColorDebugKind::Foreground, Color::LightGreen, ".light_green()")]
#[case(ColorDebugKind::Foreground, Color::LightYellow, ".light_yellow()")]
#[case(ColorDebugKind::Foreground, Color::LightBlue, ".light_blue()")]
#[case(ColorDebugKind::Foreground, Color::LightMagenta, ".light_magenta()")]
#[case(ColorDebugKind::Foreground, Color::LightCyan, ".light_cyan()")]
#[case(ColorDebugKind::Foreground, Color::White, ".white()")]
#[case(
ColorDebugKind::Foreground,
Color::Indexed(10),
".fg(Color::Indexed(10))"
)]
#[case(
ColorDebugKind::Foreground,
Color::Rgb(255, 0, 0),
".fg(Color::Rgb(255, 0, 0))"
)]
#[case(ColorDebugKind::Background, Color::Black, ".on_black()")]
#[case(ColorDebugKind::Background, Color::Red, ".on_red()")]
#[case(ColorDebugKind::Background, Color::Green, ".on_green()")]
#[case(ColorDebugKind::Background, Color::Yellow, ".on_yellow()")]
#[case(ColorDebugKind::Background, Color::Blue, ".on_blue()")]
#[case(ColorDebugKind::Background, Color::Magenta, ".on_magenta()")]
#[case(ColorDebugKind::Background, Color::Cyan, ".on_cyan()")]
#[case(ColorDebugKind::Background, Color::Gray, ".on_gray()")]
#[case(ColorDebugKind::Background, Color::DarkGray, ".on_dark_gray()")]
#[case(ColorDebugKind::Background, Color::LightRed, ".on_light_red()")]
#[case(ColorDebugKind::Background, Color::LightGreen, ".on_light_green()")]
#[case(ColorDebugKind::Background, Color::LightYellow, ".on_light_yellow()")]
#[case(ColorDebugKind::Background, Color::LightBlue, ".on_light_blue()")]
#[case(ColorDebugKind::Background, Color::LightMagenta, ".on_light_magenta()")]
#[case(ColorDebugKind::Background, Color::LightCyan, ".on_light_cyan()")]
#[case(ColorDebugKind::Background, Color::White, ".on_white()")]
#[case(
ColorDebugKind::Background,
Color::Indexed(10),
".bg(Color::Indexed(10))"
)]
#[case(
ColorDebugKind::Background,
Color::Rgb(255, 0, 0),
".bg(Color::Rgb(255, 0, 0))"
)]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Black,
".underline_color(Color::Black)"
)]
#[cfg(feature = "underline-color")]
#[case(ColorDebugKind::Underline, Color::Red, ".underline_color(Color::Red)")]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Green,
".underline_color(Color::Green)"
)]
#[cfg(feature = "underline-color")]
#[case(
ColorDebugKind::Underline,
Color::Yellow,
".underline_color(Color::Yellow)"
)]
fn stylize_debug(#[case] kind: ColorDebugKind, #[case] color: Color, #[case] expected: &str) {
let debug = color.stylize_debug(kind);
assert_eq!(format!("{debug:?}"), expected);
}
}
25 changes: 21 additions & 4 deletions src/text/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,33 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
/// ```
///
/// [`Paragraph`]: crate::widgets::Paragraph
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Line<'a> {
/// The spans that make up this line of text.
pub spans: Vec<Span<'a>>,

/// The style of this line of text.
pub style: Style,

/// The alignment of this line of text.
pub alignment: Option<Alignment>,

/// The spans that make up this line of text.
pub spans: Vec<Span<'a>>,
}

impl fmt::Debug for Line<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.style == Style::default() && self.alignment.is_none() {
f.write_str("Line ")?;
return f.debug_list().entries(&self.spans).finish();
}
let mut debug = f.debug_struct("Line");
if self.style != Style::default() {
debug.field("style", &self.style);
}
if let Some(alignment) = self.alignment {
debug.field("alignment", &format!("Alignment::{alignment}"));
}
debug.field("spans", &self.spans).finish()
}
}

fn cow_to_spans<'a>(content: impl Into<Cow<'a, str>>) -> Vec<Span<'a>> {
Expand Down
18 changes: 15 additions & 3 deletions src/text/span.rs
9048
Original file line number Diff line numberDiff line change
Expand Up @@ -88,12 +88,24 @@ use crate::{prelude::*, style::Styled, text::StyledGrapheme};
/// [`Paragraph`]: crate::widgets::Paragraph
/// [`Stylize`]: crate::style::Stylize
/// [`Cow<str>`]: std::borrow::Cow
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Span<'a> {
/// The content of the span as a Clone-on-write string.
pub content: Cow<'a, str>,
/// The style of the span.
pub style: Style,
/// The content of the span as a Clone-on-write string.
pub content: Cow<'a, str>,
}

impl fmt::Debug for Span<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.style == Style::default() {
return write!(f, "Span({:?})", self.content);
}
f.debug_struct("Span")
.field("style", &self.style)
.field("content", &self.content)
.finish()
}
}

impl<'a> Span<'a> {
Expand Down
Loading
0