1 use std::fmt::{self, Write};
2 
3 /// Left, Center, Right or Unspecified
4 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
5 pub enum Alignment {
6     Unspecified,
7     Left,
8     Center,
9     Right,
10 }
11 
12 impl Default for Alignment {
default() -> Self13     fn default() -> Self {
14         Alignment::Unspecified
15     }
16 }
17 
18 /// a Compound is a part of a line with a consistent styling.
19 /// It can be part of word, several words, some inline code, or even the whole line.
20 #[derive(Clone, PartialEq, Eq, Hash)]
21 pub struct Compound<'s> {
22     pub src: &'s str,
23     pub bold: bool,
24     pub italic: bool,
25     pub code: bool,
26     pub strikeout: bool,
27 }
28 
29 impl<'s> Compound<'s> {
30     /// make a raw unstyled compound
31     /// Involves no parsing
32     #[inline(always)]
raw_str(src: &'s str) -> Compound<'s>33     pub fn raw_str(src: &'s str) -> Compound<'s> {
34         Compound {
35             src,
36             bold: false,
37             italic: false,
38             code: false,
39             strikeout: false,
40         }
41     }
42     /// change the content but keeps the style arguments
set_str(&mut self, src: &'s str)43     pub fn set_str(&mut self, src: &'s str) {
44         self.src = src;
45     }
46     /// return a sub part of the compound, with the same styling
47     /// r_start is relative, that is 0 is the index of the first
48     /// byte of this compound.
49     #[inline(always)]
sub(&self, r_start: usize, r_end: usize) -> Compound<'s>50     pub fn sub(&self, r_start: usize, r_end: usize) -> Compound<'s> {
51         Compound {
52             src: &self.src[r_start..r_end],
53             bold: self.bold,
54             italic: self.italic,
55             code: self.code,
56             strikeout: self.strikeout,
57         }
58     }
59     /// return a sub part of the compound, with the same styling
60     /// r_start is relative, that is 0 is the index of the first
61     /// char of this compound.
62     ///
63     /// The difference with `sub` is that this method is unicode
64     /// aware and counts the chars instead of asking for the bytes
65     #[inline(always)]
sub_chars(&self, r_start: usize, r_end: usize) -> Compound<'s>66     pub fn sub_chars(&self, r_start: usize, r_end: usize) -> Compound<'s> {
67         let mut rb_start = 0;
68         let mut rb_end = 0;
69         for (char_idx, (byte_idx, _)) in self.as_str().char_indices().enumerate() {
70             if char_idx == r_start {
71                 rb_start = byte_idx;
72             } else if char_idx == r_end {
73                 rb_end = byte_idx;
74                 break;
75             }
76         }
77         if rb_end == 0 && rb_start != 0 {
78             self.tail(rb_start)
79         } else {
80             self.sub(rb_start, rb_end)
81         }
82     }
83     /// return a sub part at end of the compound, with the same styling
84     /// r_start is relative, that is if you give 0 you get a clone of
85     /// this compound
86     #[inline(always)]
tail(&self, r_start: usize) -> Compound<'s>87     pub fn tail(&self, r_start: usize) -> Compound<'s> {
88         Compound {
89             src: &self.src[r_start..],
90             bold: self.bold,
91             italic: self.italic,
92             code: self.code,
93             strikeout: self.strikeout,
94         }
95     }
96     /// return a sub part at end of the compound, with the same styling
97     /// r_start is relative, that is if you give 0 you get a clone of
98     /// this compound
99     ///
100     /// The difference with `tail` is that this method is unicode
101     /// aware and counts the chars instead of asking for the bytes
102     #[inline(always)]
tail_chars(&self, r_start: usize) -> Compound<'s>103     pub fn tail_chars(&self, r_start: usize) -> Compound<'s> {
104         let mut rb_start = 0;
105         for (char_idx, (byte_idx, _)) in self.as_str().char_indices().enumerate() {
106             rb_start = byte_idx;
107             if char_idx == r_start {
108                 break;
109             }
110         }
111         self.tail(rb_start)
112     }
113 
114     // shortens this compound by `tail_size` bytes and returns the tail
115     // as another compound
cut_tail(&mut self, tail_size: usize) -> Compound<'s>116     pub fn cut_tail(&mut self, tail_size: usize) -> Compound<'s> {
117         let cut = self.src.len() - tail_size;
118         let tail = Compound {
119             src: &self.src[cut..],
120             bold: self.bold,
121             italic: self.italic,
122             code: self.code,
123             strikeout: self.strikeout,
124         };
125         self.src = &self.src[0..cut];
126         tail
127     }
128 
129     // make a raw unstyled compound from part of a string
130     // Involves no parsing
131     #[inline(always)]
raw_part(src: &'s str, start: usize, end: usize) -> Compound<'s>132     pub fn raw_part(src: &'s str, start: usize, end: usize) -> Compound<'s> {
133         Compound {
134             src: &src[start..end],
135             bold: false,
136             italic: false,
137             code: false,
138             strikeout: false,
139         }
140     }
141     #[inline(always)]
new( src: &'s str, start: usize, end: usize, bold: bool, italic: bool, code: bool, strikeout: bool, ) -> Compound<'s>142     pub fn new(
143         src: &'s str, // the source string from which the compound is a part
144         start: usize, // start index in bytes
145         end: usize,
146         bold: bool,
147         italic: bool,
148         code: bool,
149         strikeout: bool,
150     ) -> Compound<'s> {
151         Compound {
152             src: &src[start..end],
153             italic,
154             bold,
155             code,
156             strikeout,
157         }
158     }
159     #[inline(always)]
bold(mut self) -> Compound<'s>160     pub fn bold(mut self) -> Compound<'s> {
161         self.bold = true;
162         self
163     }
164     #[inline(always)]
italic(mut self) -> Compound<'s>165     pub fn italic(mut self) -> Compound<'s> {
166         self.italic = true;
167         self
168     }
169     #[inline(always)]
code(mut self) -> Compound<'s>170     pub fn code(mut self) -> Compound<'s> {
171         self.code = true;
172         self
173     }
174     #[inline(always)]
strikeout(mut self) -> Compound<'s>175     pub fn strikeout(mut self) -> Compound<'s> {
176         self.strikeout = true;
177         self
178     }
179     #[inline(always)]
set_bold(&mut self, bold: bool)180     pub fn set_bold(&mut self, bold: bool) {
181         self.bold = bold;
182     }
183     #[inline(always)]
set_italic(&mut self, italic: bool)184     pub fn set_italic(&mut self, italic: bool) {
185         self.italic = italic;
186     }
187     #[inline(always)]
set_code(&mut self, code: bool)188     pub fn set_code(&mut self, code: bool) {
189         self.code = code;
190     }
191     #[inline(always)]
set_strikeout(&mut self, strikeout: bool)192     pub fn set_strikeout(&mut self, strikeout: bool) {
193         self.strikeout = strikeout;
194     }
195     #[inline(always)]
as_str(&self) -> &'s str196     pub fn as_str(&self) -> &'s str {
197         self.src
198     }
199     #[inline(always)]
char_length(&self) -> usize200     pub fn char_length(&self) -> usize {
201         self.as_str().chars().count()
202     }
203     #[inline(always)]
is_empty(&self) -> bool204     pub fn is_empty(&self) -> bool {
205         self.src.is_empty()
206     }
207 }
208 
209 impl fmt::Display for Compound<'_> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result210     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211         f.write_str(self.as_str())?;
212         Ok(())
213     }
214 }
215 
216 impl fmt::Debug for Compound<'_> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result217     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218         if self.bold {
219             f.write_char('B')?;
220         }
221         if self.italic {
222             f.write_char('I')?;
223         }
224         if self.code {
225             f.write_char('C')?;
226         }
227         if self.strikeout {
228             f.write_char('S')?;
229         }
230         f.write_char('"')?;
231         f.write_str(self.as_str())?;
232         f.write_char('"')?;
233         Ok(())
234     }
235 }
236