Skip to main content

rich/
styled.rs

1//! Applying a style across a whole renderable.
2//!
3//! Port of `rich/styled.py`. [`Styled`] renders its child and lays `style`
4//! underneath every segment (each segment's own style still wins on top), so a
5//! single style can tint an entire composite renderable.
6
7use crate::console::{Console, ConsoleOptions};
8use crate::measure::Measurement;
9use crate::protocol::Renderable;
10use crate::segment::Segment;
11use crate::style::Style;
12
13/// Apply a [`Style`] to an entire renderable. Mirrors `rich.styled.Styled`.
14pub struct Styled {
15    renderable: Box<dyn Renderable>,
16    style: Style,
17}
18
19impl Styled {
20    /// Wrap `renderable`, applying `style` beneath its own styling.
21    pub fn new(renderable: Box<dyn Renderable>, style: Style) -> Self {
22        Styled { renderable, style }
23    }
24}
25
26impl Renderable for Styled {
27    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
28        let segments = self.renderable.rich_render(console, options);
29        Segment::apply_style(&segments, &self.style)
30    }
31
32    fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
33        // Styling doesn't change size — defer to the child. Port of
34        // `Styled.__rich_measure__`.
35        self.renderable.measure(console, options)
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::color::ColorSystem;
43    use crate::text::Text;
44
45    fn console() -> Console {
46        Console::builder()
47            .force_terminal(true)
48            .color_system(Some(ColorSystem::Truecolor))
49            .width(20)
50            .build()
51    }
52
53    #[test]
54    fn applies_base_style() {
55        // "on red" under plain "hi" → background red. Captured from real rich.
56        let styled = Styled::new(Box::new(Text::new("hi")), Style::parse("on red").unwrap());
57        assert_eq!(console().render_to_string(&styled), "\x1b[41mhi\x1b[0m");
58    }
59
60    #[test]
61    fn child_style_wins_on_top() {
62        // The child's own bold survives; the applied green fills the fg.
63        let inner = Text::new("x");
64        let mut inner = inner;
65        inner.set_base_style(Style::parse("bold").unwrap());
66        let styled = Styled::new(Box::new(inner), Style::parse("green").unwrap());
67        assert_eq!(console().render_to_string(&styled), "\x1b[1;32mx\x1b[0m");
68    }
69}