Skip to main content

rich/
containers.rs

1//! Renderable containers.
2//!
3//! Port of upstream `rich/containers.py`: [`Renderables`] and [`Lines`].
4//! Upstream's `Text.wrap` returns `Lines`; this port wraps at the segment
5//! level, so `Lines` is here for callers that justify lines of text
6//! themselves.
7
8use std::sync::Arc;
9
10use crate::console::{Console, ConsoleOptions, Justify, Overflow};
11use crate::measure::Measurement;
12use crate::protocol::Renderable;
13use crate::segment::Segment;
14use crate::style::StyleType;
15use crate::text::Text;
16
17/// A list of renderables rendered one after another. Mirrors
18/// `rich.containers.Renderables`, which `LogRender` puts in its message column.
19#[derive(Clone, Default)]
20pub struct Renderables {
21    renderables: Vec<Arc<dyn Renderable + Send + Sync>>,
22}
23
24impl Renderables {
25    /// A container of `renderables`, rendered in order.
26    pub fn new(renderables: Vec<Arc<dyn Renderable + Send + Sync>>) -> Self {
27        Renderables { renderables }
28    }
29
30    /// Add a renderable to the end. Port of `Renderables.append`.
31    pub fn append(&mut self, renderable: Arc<dyn Renderable + Send + Sync>) {
32        self.renderables.push(renderable);
33    }
34
35    /// The contained renderables.
36    pub fn renderables(&self) -> &[Arc<dyn Renderable + Send + Sync>] {
37        &self.renderables
38    }
39}
40
41impl Renderable for Renderables {
42    /// `yield from self._renderables`, each through `Console.render`.
43    ///
44    /// Upstream's renderables each end their output with a newline; this
45    /// port's separate lines instead, so each child's output is joined to the
46    /// previous one with a newline. A child that renders nothing adds no line.
47    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
48        let options = options.reset_height();
49        let mut segments: Vec<Segment> = Vec::new();
50        let mut first = true;
51        for renderable in &self.renderables {
52            let rendered = console.render(renderable.as_ref(), Some(&options));
53            if rendered.is_empty() {
54                continue;
55            }
56            if !first {
57                segments.push(Segment::line());
58            }
59            first = false;
60            segments.extend(rendered);
61        }
62        segments
63    }
64
65    /// Port of `Renderables.__rich_measure__`: the widest minimum and maximum
66    /// of the children, or `(1, 1)` when there are none.
67    fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
68        let dimensions: Vec<Measurement> = self
69            .renderables
70            .iter()
71            .map(|renderable| Measurement::get(console, options, renderable.as_ref()))
72            .collect();
73        if dimensions.is_empty() {
74            return Measurement::new(1, 1);
75        }
76        Measurement::new(
77            dimensions.iter().map(|d| d.minimum).max().unwrap_or(0),
78            dimensions.iter().map(|d| d.maximum).max().unwrap_or(0),
79        )
80    }
81}
82
83/// A list of [`Text`] lines that renders one per line. Port of
84/// `rich.containers.Lines`.
85#[derive(Clone, Debug, Default)]
86pub struct Lines {
87    lines: Vec<Text>,
88}
89
90impl Lines {
91    /// Lines holding `lines`.
92    pub fn new(lines: Vec<Text>) -> Self {
93        Lines { lines }
94    }
95
96    /// The lines.
97    pub fn lines(&self) -> &[Text] {
98        &self.lines
99    }
100
101    /// The lines, mutably (upstream's `__setitem__`).
102    pub fn lines_mut(&mut self) -> &mut Vec<Text> {
103        &mut self.lines
104    }
105
106    /// How many lines there are.
107    pub fn len(&self) -> usize {
108        self.lines.len()
109    }
110
111    /// Whether there are no lines.
112    pub fn is_empty(&self) -> bool {
113        self.lines.is_empty()
114    }
115
116    /// Add a line. Port of `Lines.append`.
117    pub fn append(&mut self, line: Text) {
118        self.lines.push(line);
119    }
120
121    /// Add several lines. Port of `Lines.extend`.
122    pub fn extend(&mut self, lines: impl IntoIterator<Item = Text>) {
123        self.lines.extend(lines);
124    }
125
126    /// Remove and return the line at `index`, the last when `None`. Port of
127    /// `Lines.pop`.
128    pub fn pop(&mut self, index: Option<usize>) -> Option<Text> {
129        match index {
130            None => self.lines.pop(),
131            Some(index) if index < self.lines.len() => Some(self.lines.remove(index)),
132            Some(_) => None,
133        }
134    }
135
136    /// Justify and overflow every line to `width` cells. Port of
137    /// `Lines.justify`: `left` pads each line, `center` and `right` strip and
138    /// pad on the left (and right), and `full` spreads the spaces between the
139    /// words of every line but the last. Any other method leaves the lines
140    /// alone.
141    pub fn justify(
142        &mut self,
143        console: &Console,
144        width: usize,
145        justify: Justify,
146        overflow: Overflow,
147    ) {
148        let cells = |line: &Text| crate::cells::cell_len(line.plain());
149        match justify {
150            Justify::Left => {
151                for line in &mut self.lines {
152                    line.truncate(width, Some(overflow), true);
153                }
154            }
155            Justify::Center => {
156                for line in &mut self.lines {
157                    line.rstrip();
158                    line.truncate(width, Some(overflow), false);
159                    line.pad_left(width.saturating_sub(cells(line)) / 2, ' ');
160                    line.pad_right(width.saturating_sub(cells(line)), ' ');
161                }
162            }
163            Justify::Right => {
164                for line in &mut self.lines {
165                    line.rstrip();
166                    line.truncate(width, Some(overflow), false);
167                    line.pad_left(width.saturating_sub(cells(line)), ' ');
168                }
169            }
170            Justify::Full => {
171                let last = self.lines.len().saturating_sub(1);
172                for line_index in 0..last {
173                    let line = &self.lines[line_index];
174                    let words = line.split(" ", false, false);
175                    let words_size: usize = words.iter().map(cells).sum();
176                    let mut num_spaces = words.len().saturating_sub(1);
177                    let mut spaces = vec![1usize; num_spaces];
178                    let mut index = 0;
179                    if !spaces.is_empty() {
180                        while words_size + num_spaces < width {
181                            let slot = spaces.len() - index - 1;
182                            spaces[slot] += 1;
183                            num_spaces += 1;
184                            index = (index + 1) % spaces.len();
185                        }
186                    }
187                    let mut tokens: Vec<Text> = Vec::with_capacity(words.len() * 2);
188                    for (index, word) in words.iter().enumerate() {
189                        tokens.push(word.clone());
190                        if index < spaces.len() {
191                            let style = word.get_style_at_offset(console, -1);
192                            let next_style = words[index + 1].get_style_at_offset(console, 0);
193                            let space_style: StyleType = if style == next_style {
194                                style.into()
195                            } else {
196                                line.base_style().clone()
197                            };
198                            tokens.push(Text::styled(" ".repeat(spaces[index]), space_style));
199                        }
200                    }
201                    self.lines[line_index] = Text::new("").join(&tokens);
202                }
203            }
204            Justify::Default => {}
205        }
206    }
207}
208
209impl std::ops::Index<usize> for Lines {
210    type Output = Text;
211
212    fn index(&self, index: usize) -> &Text {
213        &self.lines[index]
214    }
215}
216
217impl std::ops::IndexMut<usize> for Lines {
218    fn index_mut(&mut self, index: usize) -> &mut Text {
219        &mut self.lines[index]
220    }
221}
222
223impl IntoIterator for Lines {
224    type Item = Text;
225    type IntoIter = std::vec::IntoIter<Text>;
226
227    fn into_iter(self) -> Self::IntoIter {
228        self.lines.into_iter()
229    }
230}
231
232impl<'a> IntoIterator for &'a Lines {
233    type Item = &'a Text;
234    type IntoIter = std::slice::Iter<'a, Text>;
235
236    fn into_iter(self) -> Self::IntoIter {
237        self.lines.iter()
238    }
239}
240
241impl Renderable for Lines {
242    /// `yield from self._lines`: each line rendered in turn.
243    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
244        let renderables: Vec<Arc<dyn Renderable + Send + Sync>> = self
245            .lines
246            .iter()
247            .map(|line| Arc::new(line.clone()) as Arc<dyn Renderable + Send + Sync>)
248            .collect();
249        Renderables::new(renderables).rich_render(console, options)
250    }
251}