termimad/
composite.rs

1use {
2    crate::*,
3    minimad::{
4        Composite,
5        Compound,
6    },
7    unicode_width::UnicodeWidthStr,
8};
9
10/// Wrap Minimad compounds with their style and
11/// termimad specific information
12#[derive(Debug, Clone)]
13pub struct FmtComposite<'s> {
14    pub kind: CompositeKind,
15
16    pub compounds: Vec<Compound<'s>>,
17
18    // cached visible length in cells, not counting margins, bullets, etc.
19    pub visible_length: usize,
20
21    pub spacing: Option<Spacing>,
22}
23
24impl<'s> FmtComposite<'s> {
25    pub fn new() -> Self {
26        FmtComposite {
27            kind: CompositeKind::Paragraph,
28            compounds: Vec::new(),
29            visible_length: 0,
30            spacing: None,
31        }
32    }
33    pub fn from(composite: Composite<'s>, skin: &MadSkin) -> Self {
34        let kind: CompositeKind = composite.style.into();
35        FmtComposite {
36            visible_length: skin.visible_composite_length(kind, &composite.compounds),
37            kind,
38            compounds: composite.compounds,
39            spacing: None,
40        }
41    }
42    pub fn from_compound(compound: Compound<'s>) -> Self {
43        let mut fc = Self::new();
44        fc.add_compound(compound);
45        fc
46    }
47    /// Return the number of characters (usually spaces) to insert both
48    /// sides of the composite
49    pub const fn completions(&self) -> (usize, usize) {
50        match &self.spacing {
51            Some(spacing) => spacing.completions_for(self.visible_length),
52            None => (0, 0),
53        }
54    }
55    /// Add a compound and modifies `visible_length` accordingly
56    pub fn add_compound(&mut self, compound: Compound<'s>) {
57        self.visible_length += compound.src.width();
58        self.compounds.push(compound);
59    }
60    /// Ensure the cached `visible_length` is correct.
61    ///
62    /// It's normally not necessary to call it, but
63    /// this must be called if compounds are added,
64    /// removed or modified without using the `FmtComposite` API
65    pub fn recompute_width(&mut self, skin: &MadSkin) {
66        self.visible_length = skin.visible_composite_length(self.kind, &self.compounds);
67    }
68    /// try to ensure the composite's width doesn't exceed the given
69    /// width.
70    ///
71    /// The alignment can be used, if necessary, to know which side it's better
72    /// to remove content (for example if the alignment is left then we remove at
73    /// right).
74    /// The fitter may remove a part in the core of the composite if it looks
75    /// good enough. In this specific case an ellipsis will replace the removed part.
76    pub fn fit_width(&mut self, width: usize, align: Alignment, skin: &MadSkin) {
77        Fitter::for_align(align).fit(self, width, skin);
78    }
79    /// if the composite is smaller than the given width, pad it
80    /// according to the alignment.
81    pub fn extend_width(&mut self, width: usize, align: Alignment) {
82        if let Some(ref mut spacing) = self.spacing {
83            if spacing.width < width {
84                spacing.width = width;
85            }
86            spacing.align = align;
87        } else if self.visible_length < width {
88            self.spacing = Some(Spacing { width, align });
89        }
90    }
91    /// try to make it so that the composite has exactly the given width,
92    /// either by shortening it or by adding space.
93    ///
94    /// This calls the `fit_width` and `extend_width` methods.
95    pub fn fill_width(&mut self, width: usize, align: Alignment, skin: &MadSkin) {
96        self.fit_width(width, align, skin);
97        self.extend_width(width, align);
98    }
99}
100
101impl Default for FmtComposite<'_> {
102    fn default() -> Self {
103        Self::new()
104    }
105}