Skip to main content

shuck_format/
format_element.rs

1use std::fmt;
2use std::sync::Arc;
3
4use unicode_width::UnicodeWidthChar;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum LineMode {
8    Hard,
9    Soft,
10    SoftOrSpace,
11}
12
13#[derive(Clone, PartialEq, Eq)]
14pub enum FormatElement {
15    Token(&'static str),
16    Text(TextElement),
17    Space,
18    Line(LineMode),
19    Indent(InternedDocument),
20    Group(InternedDocument),
21    BestFit {
22        flat: InternedDocument,
23        expanded: InternedDocument,
24    },
25    Verbatim(VerbatimText),
26}
27
28impl fmt::Debug for FormatElement {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            Self::Token(text) => f.debug_tuple("Token").field(text).finish(),
32            Self::Text(text) => f.debug_tuple("Text").field(text).finish(),
33            Self::Space => f.write_str("Space"),
34            Self::Line(mode) => f.debug_tuple("Line").field(mode).finish(),
35            Self::Indent(document) => f.debug_tuple("Indent").field(document).finish(),
36            Self::Group(document) => f.debug_tuple("Group").field(document).finish(),
37            Self::BestFit { flat, expanded } => f
38                .debug_struct("BestFit")
39                .field("flat", flat)
40                .field("expanded", expanded)
41                .finish(),
42            Self::Verbatim(text) => f.debug_tuple("Verbatim").field(text).finish(),
43        }
44    }
45}
46
47#[derive(Debug, Clone, Default, PartialEq, Eq)]
48pub struct Document {
49    elements: Vec<FormatElement>,
50}
51
52impl Document {
53    #[must_use]
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    #[must_use]
59    pub fn with_capacity(capacity: usize) -> Self {
60        Self {
61            elements: Vec::with_capacity(capacity),
62        }
63    }
64
65    #[must_use]
66    pub fn from_element(element: FormatElement) -> Self {
67        Self {
68            elements: vec![element],
69        }
70    }
71
72    #[must_use]
73    pub fn from_elements(elements: Vec<FormatElement>) -> Self {
74        Self { elements }
75    }
76
77    pub fn push(&mut self, element: FormatElement) {
78        self.elements.push(element);
79    }
80
81    pub fn extend(&mut self, document: Document) {
82        self.elements.extend(document.elements);
83    }
84
85    #[must_use]
86    pub fn as_slice(&self) -> &[FormatElement] {
87        &self.elements
88    }
89
90    #[must_use]
91    pub fn into_vec(self) -> Vec<FormatElement> {
92        self.elements
93    }
94
95    #[must_use]
96    pub fn is_empty(&self) -> bool {
97        self.elements.is_empty()
98    }
99}
100
101#[derive(Clone, PartialEq, Eq)]
102pub struct InternedDocument {
103    elements: Arc<[FormatElement]>,
104}
105
106impl InternedDocument {
107    #[must_use]
108    pub fn as_slice(&self) -> &[FormatElement] {
109        &self.elements
110    }
111}
112
113impl From<Document> for InternedDocument {
114    fn from(document: Document) -> Self {
115        Self {
116            elements: Arc::from(document.elements),
117        }
118    }
119}
120
121impl fmt::Debug for InternedDocument {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        f.debug_list().entries(self.elements.iter()).finish()
124    }
125}
126
127#[derive(Clone, PartialEq, Eq)]
128pub struct TextElement {
129    text: Box<str>,
130    metrics: TextMetrics,
131}
132
133impl TextElement {
134    #[must_use]
135    pub fn new(text: impl Into<String>) -> Self {
136        let text = text.into().into_boxed_str();
137        let metrics = TextMetrics::from_text(&text, 4);
138        Self { text, metrics }
139    }
140
141    #[must_use]
142    pub fn as_str(&self) -> &str {
143        &self.text
144    }
145
146    #[must_use]
147    pub fn metrics(&self) -> TextMetrics {
148        self.metrics
149    }
150}
151
152impl fmt::Debug for TextElement {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        f.debug_struct("TextElement")
155            .field("text", &self.text)
156            .field("metrics", &self.metrics)
157            .finish()
158    }
159}
160
161#[derive(Clone, PartialEq, Eq)]
162pub struct VerbatimText {
163    text: Box<str>,
164    metrics: TextMetrics,
165}
166
167impl VerbatimText {
168    #[must_use]
169    pub fn new(text: impl Into<String>, indent_width: u8) -> Self {
170        let text = text.into().into_boxed_str();
171        let metrics = TextMetrics::from_text(&text, indent_width);
172        Self { text, metrics }
173    }
174
175    #[must_use]
176    pub fn as_str(&self) -> &str {
177        &self.text
178    }
179
180    #[must_use]
181    pub fn metrics(&self) -> TextMetrics {
182        self.metrics
183    }
184}
185
186impl fmt::Debug for VerbatimText {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        f.debug_struct("VerbatimText")
189            .field("text", &self.text)
190            .field("metrics", &self.metrics)
191            .finish()
192    }
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub struct TextMetrics {
197    first_line_width: usize,
198    last_line_width: usize,
199    single_line_width: Option<usize>,
200    has_newline: bool,
201    ends_with_newline: bool,
202}
203
204impl TextMetrics {
205    #[must_use]
206    pub fn from_text(text: &str, indent_width: u8) -> Self {
207        let mut current_width = 0usize;
208        let mut first_line_width = 0usize;
209        let mut saw_newline = false;
210        let mut ends_with_newline = false;
211
212        for ch in text.chars() {
213            match ch {
214                '\n' => {
215                    if !saw_newline {
216                        first_line_width = current_width;
217                    }
218                    current_width = 0;
219                    saw_newline = true;
220                    ends_with_newline = true;
221                }
222                '\t' => {
223                    current_width += usize::from(indent_width);
224                    ends_with_newline = false;
225                }
226                _ => {
227                    current_width += unicode_width(ch);
228                    ends_with_newline = false;
229                }
230            }
231        }
232
233        if !saw_newline {
234            first_line_width = current_width;
235        }
236
237        Self {
238            first_line_width,
239            last_line_width: current_width,
240            single_line_width: (!saw_newline).then_some(current_width),
241            has_newline: saw_newline,
242            ends_with_newline,
243        }
244    }
245
246    #[must_use]
247    pub fn first_line_width(self) -> usize {
248        self.first_line_width
249    }
250
251    #[must_use]
252    pub fn last_line_width(self) -> usize {
253        self.last_line_width
254    }
255
256    #[must_use]
257    pub fn single_line_width(self) -> Option<usize> {
258        self.single_line_width
259    }
260
261    #[must_use]
262    pub fn has_newline(self) -> bool {
263        self.has_newline
264    }
265
266    #[must_use]
267    pub fn ends_with_newline(self) -> bool {
268        self.ends_with_newline
269    }
270}
271
272fn unicode_width(ch: char) -> usize {
273    ch.width().unwrap_or(0)
274}
275
276#[must_use]
277pub const fn token(text: &'static str) -> FormatElement {
278    FormatElement::Token(text)
279}
280
281#[must_use]
282pub fn text(text: impl Into<String>) -> FormatElement {
283    FormatElement::Text(TextElement::new(text))
284}
285
286#[must_use]
287pub const fn space() -> FormatElement {
288    FormatElement::Space
289}
290
291#[must_use]
292pub const fn hard_line_break() -> FormatElement {
293    FormatElement::Line(LineMode::Hard)
294}
295
296#[must_use]
297pub const fn soft_line_break() -> FormatElement {
298    FormatElement::Line(LineMode::Soft)
299}
300
301#[must_use]
302pub const fn soft_line_break_or_space() -> FormatElement {
303    FormatElement::Line(LineMode::SoftOrSpace)
304}
305
306#[must_use]
307pub fn indent(document: Document) -> FormatElement {
308    FormatElement::Indent(InternedDocument::from(document))
309}
310
311#[must_use]
312pub fn group(document: Document) -> FormatElement {
313    FormatElement::Group(InternedDocument::from(document))
314}
315
316#[must_use]
317pub fn best_fit(flat: Document, expanded: Document) -> FormatElement {
318    FormatElement::BestFit {
319        flat: InternedDocument::from(flat),
320        expanded: InternedDocument::from(expanded),
321    }
322}
323
324#[must_use]
325pub fn verbatim(text: impl Into<String>) -> FormatElement {
326    FormatElement::Verbatim(VerbatimText::new(text, 4))
327}