shuck_format/
format_element.rs1use 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}