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,
13 SingleOptionalNewline,
15 Newline,
17 None,
19 NoneOptionalNewline,
21 SingleOrNewline,
25 NoneOrNewline,
29 MultiSpace(u32),
31 MultiLF(u32),
33 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,
46 After,
48 Around,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
53pub(crate) struct Space {
54 pub(crate) value: SpaceValue,
56 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 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 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 Newline | NoneOrNewline | NoneOptionalNewline => {
201 matches!(self.space_before.value, Newline | Indent { .. })
202 }
203 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 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 } 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 } 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
272fn 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}