toml_parse/toml_fmt/
ws.rs

1use std::fmt;
2
3use super::tkn_tree::{SyntaxToken, TomlKind};
4use super::Block;
5
6pub(crate) const USER_INDENT_SIZE: u32 = 4;
7
8#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub(crate) enum SpaceValue {
11    /// Single whitespace char, like `' '`
12    Single,
13    /// Single whitespace char, like `' '`, but preserve existing line break.
14    SingleOptionalNewline,
15    /// A single newline (`\n`) char
16    Newline,
17    /// No whitespace at all.
18    None,
19    /// No space, but preserve existing line break.
20    NoneOptionalNewline,
21    /// If the parent element fits into a single line, a single space.
22    /// Otherwise, at least one newline.
23    /// Existing newlines are preserved.
24    SingleOrNewline,
25    /// If the parent element fits into a single line, no space.
26    /// Otherwise, at least one newline.
27    /// Existing newlines are preserved.
28    NoneOrNewline,
29    /// Number of spaces this is only for `Whitespace` held by `Block`.
30    MultiSpace(u32),
31    /// Number of new lines this is only for `Whitespace` held by `Block`.
32    MultiLF(u32),
33    /// Number of spaces that indent is made of `'\n\s\s\s\s'`.
34    Indent {
35        level: u32,
36        alignment: u32,
37        is_tab: bool,
38    },
39}
40
41#[allow(dead_code)]
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
43pub(crate) enum SpaceLoc {
44    /// Before the element.
45    Before,
46    /// After the element.
47    After,
48    /// On the both sides of the element.
49    Around,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
53pub(crate) struct Space {
54    /// How much space to add.
55    pub(crate) value: SpaceValue,
56    /// Should the space be added before, after or around the element?
57    pub(crate) loc: SpaceLoc,
58}
59
60impl Space {
61    fn empty_before() -> Space {
62        Self {
63            loc: SpaceLoc::Before,
64            value: SpaceValue::None,
65        }
66    }
67    fn empty_after() -> Space {
68        Self {
69            loc: SpaceLoc::After,
70            value: SpaceValue::None,
71        }
72    }
73    fn before(token: SyntaxToken) -> Space {
74        if !is_ws(&token) {
75            return Self::empty_before();
76        }
77        let value = calc_space_value(&token);
78        Self {
79            loc: SpaceLoc::Before,
80            value,
81        }
82    }
83    fn after(token: SyntaxToken) -> Space {
84        if !is_ws(&token) {
85            return Self::empty_after();
86        }
87        let value = calc_space_value(&token);
88        Self {
89            loc: SpaceLoc::After,
90            value,
91        }
92    }
93}
94
95impl fmt::Display for Space {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self.value {
98            SpaceValue::Single => write!(f, " "),
99            SpaceValue::Newline => writeln!(f),
100            SpaceValue::Indent {
101                level,
102                alignment,
103                is_tab: false,
104            } => write!(
105                f,
106                "\n{}",
107                " ".repeat((level * USER_INDENT_SIZE + alignment) as usize)
108            ),
109            SpaceValue::Indent {
110                level,
111                is_tab: true,
112                ..
113            } => write!(f, "\n{}", "\t".repeat(level as usize)),
114            SpaceValue::MultiLF(count) => write!(f, "{}", "\n".repeat(count as usize)),
115            SpaceValue::MultiSpace(count) => write!(f, "{}", " ".repeat(count as usize)),
116            SpaceValue::None => write!(f, ""),
117            _ => {
118                // unreachable!("no other writable variants")
119                write!(f, " {:?} ", self.value)
120            }
121        }
122    }
123}
124
125#[derive(Clone, Copy, Debug)]
126pub struct WhiteSpace {
127    pub(crate) space_before: Space,
128    pub(crate) space_after: Space,
129}
130
131impl AsRef<WhiteSpace> for WhiteSpace {
132    fn as_ref(&self) -> &Self {
133        self
134    }
135}
136
137impl WhiteSpace {
138    pub(crate) fn new(token: &SyntaxToken) -> WhiteSpace {
139        let (space_before, space_after) = match (token.prev_token(), token.next_token()) {
140            (Some(pre), Some(post)) => (Space::before(pre), Space::after(post)),
141            (Some(pre), _) => (Space::before(pre), Space::empty_after()),
142            (_, Some(post)) => (Space::empty_before(), Space::after(post)),
143            (_, _) => unreachable!("next or previous token returned a node"),
144        };
145
146        Self {
147            space_before,
148            space_after,
149        }
150    }
151
152    pub(crate) fn from_rule(space: &Space, l_blk: &Block, r_blk: &Block) -> WhiteSpace {
153        match space.loc {
154            SpaceLoc::Before => {
155                let space_before = Space {
156                    loc: space.loc,
157                    value: process_space_value(r_blk, space),
158                };
159
160                Self {
161                    space_before,
162                    space_after: Space::empty_after(),
163                }
164            }
165            SpaceLoc::After => {
166                let space_after = Space {
167                    loc: SpaceLoc::Before,
168                    value: process_space_value(l_blk, space),
169                };
170                Self {
171                    space_before: space_after,
172                    space_after: Space::empty_after(),
173                }
174            }
175            SpaceLoc::Around => {
176                let space_before = Space {
177                    loc: SpaceLoc::Before,
178                    value: process_space_value(r_blk, space),
179                };
180                Self {
181                    space_before,
182                    space_after: r_blk.whitespace().space_after,
183                }
184            }
185        }
186    }
187
188    /// Returns true if `space` a `SpaceRule` matches `Whitespace` value.
189    pub(crate) fn match_space_before(&self, space: Space) -> bool {
190        use SpaceValue::*;
191        if self.space_before.value == space.value {
192            return true;
193        }
194        match space.value {
195            Single => matches!(self.space_before.value, Single),
196            SingleOrNewline | SingleOptionalNewline => {
197                matches!(self.space_before.value, Single | Newline | Indent { .. })
198            }
199            // TODO make sure valid
200            Newline | NoneOrNewline | NoneOptionalNewline => {
201                matches!(self.space_before.value, Newline | Indent { .. })
202            }
203            // TODO from here on the rules never set these they will
204            // never be checked.
205            MultiSpace(len) => match self.space_before.value {
206                Single => len == 1,
207                MultiSpace(num) => len == num,
208                _ => false,
209            },
210            MultiLF(len) => match self.space_before.value {
211                Newline | Indent { .. } => len == 1,
212                MultiLF(num) => len == num,
213                _ => false,
214            },
215            Indent { .. } => match self.space_before.value {
216                Newline | Indent { .. } => true,
217                MultiLF(len) => len == 1,
218                _ => false,
219            },
220            None => None == self.space_before.value,
221        }
222    }
223}
224
225fn is_ws(token: &SyntaxToken) -> bool {
226    token.kind() == TomlKind::Whitespace
227}
228
229pub(crate) fn calc_indent(ws: &str) -> (u32, u32) {
230    let len = if ws.contains("\n ") {
231        ws.matches(' ').count() as u32
232    } else if ws.contains('\t') {
233        ws.matches('\t').count() as u32 * USER_INDENT_SIZE
234    } else {
235        0
236    };
237    let level = len / USER_INDENT_SIZE;
238    let alignment = len % USER_INDENT_SIZE;
239    (level, alignment)
240}
241
242fn calc_space_value(tkn: &SyntaxToken) -> SpaceValue {
243    let orig = tkn.text().as_str();
244    let tkn_len = orig.chars().count();
245    // indent is `\n\s\s\s\s` or some variation
246    if orig.contains('\n') && (orig.contains(' ') || orig.contains('\t')) {
247        let (level, alignment) = calc_indent(orig);
248        SpaceValue::Indent {
249            level,
250            alignment,
251            is_tab: orig.contains('\t'),
252        }
253    // just new line
254    } else if orig.contains('\n') {
255        if tkn_len == 1 {
256            SpaceValue::Newline
257        } else {
258            SpaceValue::MultiLF((orig.matches('\n').count()) as u32)
259        }
260    // just spaces
261    } else if orig.contains(' ') {
262        if tkn_len == 1 {
263            SpaceValue::Single
264        } else {
265            SpaceValue::MultiSpace((orig.matches(' ').count()) as u32)
266        }
267    } else {
268        SpaceValue::None
269    }
270}
271
272/// TODO some left block parents may have diff contains, check when SpaceLoc::After
273fn process_space_value(blk: &Block, space: &Space) -> SpaceValue {
274    use SpaceValue::*;
275    match space.value {
276        Newline => Newline,
277        MultiLF(len) => MultiLF(len),
278        Single => Single,
279        MultiSpace(len) => MultiSpace(len),
280        NoneOptionalNewline | NoneOrNewline => {
281            if blk.parents_contain("\n") {
282                Newline
283            } else {
284                SpaceValue::None
285            }
286        }
287        SingleOptionalNewline | SingleOrNewline => {
288            if blk.parents_contain("\n") {
289                Newline
290            } else {
291                Single
292            }
293        }
294        Indent {
295            level,
296            alignment,
297            is_tab,
298        } => Indent {
299            level,
300            alignment,
301            is_tab,
302        },
303        None => space.value,
304    }
305}