Skip to main content

broot/display/
matched_string.rs

1use {
2    super::{
3        CropWriter,
4        SPACE_FILLING,
5    },
6    crate::pattern::NameMatch,
7    termimad::{
8        CompoundStyle,
9        StrFit,
10        minimad::Alignment,
11    },
12    unicode_width::{
13        UnicodeWidthChar,
14        UnicodeWidthStr,
15    },
16};
17
18pub struct MatchedString<'a> {
19    pub name_match: Option<NameMatch>,
20    pub string: &'a str,
21    pub base_style: &'a CompoundStyle,
22    pub match_style: &'a CompoundStyle,
23    pub display_width: Option<usize>,
24    pub align: Alignment,
25}
26
27impl<'a> MatchedString<'a> {
28    pub fn new(
29        name_match: Option<NameMatch>,
30        string: &'a str,
31        base_style: &'a CompoundStyle,
32        match_style: &'a CompoundStyle,
33    ) -> Self {
34        Self {
35            name_match,
36            string,
37            base_style,
38            match_style,
39            display_width: None,
40            align: Alignment::Left,
41        }
42    }
43    /// If the string contains sep, then cut the tail of this matched
44    /// string and return it.
45    /// Note: a non none `display_width` currently prevents splitting
46    /// (i.e. it's not yet implemented and would involve compute width)
47    pub fn split_on_last(
48        &mut self,
49        sep: char,
50    ) -> Option<Self> {
51        if self.display_width.is_some() {
52            // the proper algo would need measuring the left part I guess
53            None
54        } else {
55            self.string.rfind(sep).map(|sep_idx| {
56                let right = &self.string[sep_idx + 1..];
57                self.string = &self.string[..=sep_idx];
58                let left_chars_count = self.string.chars().count();
59                let right_name_match = self
60                    .name_match
61                    .as_mut()
62                    .map(|nm| nm.cut_after(left_chars_count));
63                MatchedString {
64                    name_match: right_name_match,
65                    string: right,
66                    base_style: self.base_style,
67                    match_style: self.match_style,
68                    display_width: None,
69                    align: self.align,
70                }
71            })
72        }
73    }
74    pub fn fill(
75        &mut self,
76        width: usize,
77        align: Alignment,
78    ) {
79        self.display_width = Some(width);
80        self.align = align;
81    }
82    pub fn width(&self) -> usize {
83        UnicodeWidthStr::width(self.string)
84    }
85    /// Remove characters left so that the visible width is equal or
86    /// less to the required width
87    pub fn cut_left_to_fit(
88        &mut self,
89        max_width: usize,
90    ) -> usize {
91        let mut removed_char_count = 0;
92        let mut break_idx = 0;
93        let mut width = self.width();
94        for (idx, c) in self.string.char_indices() {
95            if width <= max_width {
96                break;
97            }
98            break_idx = idx + c.len_utf8();
99            let char_width = c.width().unwrap_or(0);
100            if char_width > width {
101                warn!("inconsistent char/str widths");
102                break;
103            }
104            width -= char_width;
105            removed_char_count += 1;
106        }
107        if removed_char_count > 0 {
108            self.string = &self.string[break_idx..];
109            self.name_match = self
110                .name_match
111                .take()
112                .map(|mut nm| nm.cut_after(removed_char_count - 1));
113        }
114        removed_char_count
115    }
116    pub fn queue_on<W>(
117        &self,
118        cw: &mut CropWriter<'_, W>,
119    ) -> Result<(), termimad::Error>
120    where
121        W: std::io::Write,
122    {
123        if let Some(m) = &self.name_match {
124            let mut pos_idx: usize = 0;
125            let mut combined_style = self.base_style.clone();
126            combined_style.overwrite_with(self.match_style);
127            let mut right_filling = 0;
128            let mut s = self.string;
129            if let Some(dw) = self.display_width {
130                let w = unicode_width::UnicodeWidthStr::width(s);
131                #[allow(clippy::comparison_chain)]
132                if w > dw {
133                    let (count_bytes, _) = StrFit::count_fitting(s, dw);
134                    s = &s[0..count_bytes];
135                } else if w < dw {
136                    match self.align {
137                        Alignment::Right => {
138                            cw.repeat(self.base_style, &SPACE_FILLING, dw - w)?;
139                        }
140                        Alignment::Center => {
141                            right_filling = (dw - w) / 2;
142                            cw.repeat(self.base_style, &SPACE_FILLING, dw - w - right_filling)?;
143                        }
144                        _ => {
145                            right_filling = dw - w;
146                        }
147                    }
148                }
149            }
150            // we might call queue_char more than allowed but that's okay
151            // because the cropwriter will crop them
152            for (cand_idx, cand_char) in s.chars().enumerate() {
153                if pos_idx < m.pos.len() && m.pos[pos_idx] == cand_idx {
154                    cw.queue_char(&combined_style, cand_char)?;
155                    pos_idx += 1;
156                } else {
157                    cw.queue_char(self.base_style, cand_char)?;
158                }
159            }
160            if right_filling > 0 {
161                cw.repeat(self.base_style, &SPACE_FILLING, right_filling)?;
162            }
163        } else if let Some(w) = self.display_width {
164            match self.align {
165                Alignment::Center => {
166                    cw.queue_str(self.base_style, &format!("{:^w$}", self.string, w = w))?;
167                }
168                Alignment::Right => {
169                    cw.queue_str(self.base_style, &format!("{:>w$}", self.string, w = w))?;
170                }
171                _ => {
172                    cw.queue_str(self.base_style, &format!("{:<w$}", self.string, w = w))?;
173                }
174            }
175        } else {
176            cw.queue_str(self.base_style, self.string)?;
177        }
178        Ok(())
179    }
180}