1use taplo::{
4 dom::{
5 node::{DomNode, Key},
6 FromSyntax, KeyOrIndex, Keys, Node,
7 },
8 rowan::{Direction, TextRange, TextSize},
9 syntax::{SyntaxKind::*, SyntaxNode, SyntaxToken},
10 util::join_ranges,
11};
12
13#[derive(Debug, Default)]
14pub struct Query {
15 pub offset: TextSize,
17 pub before: Option<PositionInfo>,
19 pub after: Option<PositionInfo>,
21}
22
23impl Query {
24 #[must_use]
34 pub fn at(root: &Node, offset: TextSize) -> Self {
35 let syntax = root.syntax().cloned().unwrap().into_node().unwrap();
36
37 Query {
38 offset,
39 before: offset
40 .checked_sub(TextSize::from(1))
41 .and_then(|offset| Self::position_info_at(root, &syntax, offset)),
42 after: if offset >= syntax.text_range().end() {
43 None
44 } else {
45 Self::position_info_at(root, &syntax, offset)
46 },
47 }
48 }
49
50 fn position_info_at(
51 root: &Node,
52 syntax: &SyntaxNode,
53 offset: TextSize,
54 ) -> Option<PositionInfo> {
55 let syntax = match syntax.token_at_offset(offset) {
56 taplo::rowan::TokenAtOffset::None => return None,
57 taplo::rowan::TokenAtOffset::Single(s) => s,
58 taplo::rowan::TokenAtOffset::Between(_, right) => right,
59 };
60
61 Some(PositionInfo {
62 syntax,
63 dom_node: root
64 .flat_iter()
65 .filter(|(k, n)| full_range(k, n).contains(offset))
66 .max_by_key(|(k, _)| k.len()),
67 })
68 }
69}
70
71impl Query {
72 #[must_use]
73 pub fn in_table_header(&self) -> bool {
74 match (&self.before, &self.after) {
75 (Some(before), Some(after)) => {
76 let Some(header_syntax) = before
77 .syntax
78 .parent_ancestors()
79 .find(|s| s.kind() == TABLE_HEADER)
80 else {
81 return false;
82 };
83
84 if !after.syntax.parent_ancestors().any(|a| a == header_syntax) {
85 return false;
86 }
87
88 let Some(bracket_start) = header_syntax.children_with_tokens().find_map(|t| {
89 if t.kind() == BRACKET_START {
90 t.into_token()
91 } else {
92 None
93 }
94 }) else {
95 return false;
96 };
97
98 let Some(bracket_end) = header_syntax.children_with_tokens().find_map(|t| {
99 if t.kind() == BRACKET_END {
100 t.into_token()
101 } else {
102 None
103 }
104 }) else {
105 return false;
106 };
107
108 (before.syntax == bracket_start
109 || before.syntax.text_range().start() >= bracket_start.text_range().end())
110 && (after.syntax == bracket_end
111 || after.syntax.text_range().end() <= bracket_end.text_range().start())
112 }
113 _ => false,
114 }
115 }
116
117 #[must_use]
118 pub fn in_table_array_header(&self) -> bool {
119 match (&self.before, &self.after) {
120 (Some(before), Some(after)) => {
121 let Some(header_syntax) = before
122 .syntax
123 .parent_ancestors()
124 .find(|s| s.kind() == TABLE_ARRAY_HEADER)
125 else {
126 return false;
127 };
128
129 if !after.syntax.parent_ancestors().any(|a| a == header_syntax) {
130 return false;
131 }
132
133 let Some(bracket_start) = header_syntax
134 .children_with_tokens()
135 .filter_map(|t| {
136 if t.kind() == BRACKET_START {
137 t.into_token()
138 } else {
139 None
140 }
141 })
142 .nth(1)
143 else {
144 return false;
145 };
146
147 let Some(bracket_end) = header_syntax.children_with_tokens().find_map(|t| {
148 if t.kind() == BRACKET_END {
149 t.into_token()
150 } else {
151 None
152 }
153 }) else {
154 return false;
155 };
156
157 (before.syntax == bracket_start
158 || before.syntax.text_range().start() >= bracket_start.text_range().end())
159 && (after.syntax == bracket_end
160 || after.syntax.text_range().end() <= bracket_end.text_range().start())
161 }
162 _ => false,
163 }
164 }
165
166 #[must_use]
167 pub fn header_key(&self) -> Option<SyntaxNode> {
168 match (&self.before, &self.after) {
169 (Some(before), _) => {
170 let header_syntax = before
171 .syntax
172 .parent_ancestors()
173 .find(|s| matches!(s.kind(), TABLE_ARRAY_HEADER | TABLE_HEADER))?;
174
175 header_syntax.descendants().find(|n| n.kind() == KEY)
176 }
177 _ => None,
178 }
179 }
180
181 #[must_use]
182 pub fn entry_key(&self) -> Option<SyntaxNode> {
183 let syntax = match self.before.as_ref().or(self.after.as_ref()) {
184 Some(p) => &p.syntax,
185 None => return None,
186 };
187
188 let keys = syntax
189 .parent_ancestors()
190 .find(|n| n.kind() == ENTRY)
191 .and_then(|entry| entry.children().find(|c| c.kind() == KEY))?;
192
193 Some(keys)
194 }
195
196 #[must_use]
197 pub fn entry_value(&self) -> Option<SyntaxNode> {
198 let syntax = match self.before.as_ref().or(self.after.as_ref()) {
199 Some(p) => &p.syntax,
200 None => return None,
201 };
202
203 let value = syntax
204 .parent_ancestors()
205 .find(|n| n.kind() == ENTRY)
206 .and_then(|entry| entry.children().find(|c| c.kind() == VALUE))?;
207
208 Some(value)
209 }
210
211 #[must_use]
212 pub fn parent_table_or_array_table(&self, root: &Node) -> (Keys, Node) {
213 let syntax = match self.before.as_ref().or(self.after.as_ref()) {
214 Some(s) => s.syntax.clone(),
215 None => return (Keys::empty(), root.clone()),
216 };
217
218 let last_header = root
219 .syntax()
220 .unwrap()
221 .as_node()
222 .unwrap()
223 .descendants()
224 .skip(1)
225 .filter(|n| matches!(n.kind(), TABLE_HEADER | TABLE_ARRAY_HEADER))
226 .take_while(|n| n.text_range().end() <= syntax.text_range().end())
227 .last();
228
229 let Some(last_header) = last_header else {
230 return (Keys::empty(), root.clone());
231 };
232
233 let keys = Keys::from_syntax(
234 last_header
235 .descendants()
236 .find(|n| n.kind() == KEY)
237 .unwrap()
238 .into(),
239 );
240 let node = root.path(&keys).unwrap();
241
242 (keys, node)
243 }
244
245 #[must_use]
246 pub fn empty_line(&self) -> bool {
247 let before_syntax = match self.before.as_ref() {
248 Some(s) => &s.syntax,
249 None => return true,
250 };
251
252 match &self.after {
253 Some(after) => {
254 if matches!(after.syntax.kind(), WHITESPACE | NEWLINE) {
255 let new_line_after = after
256 .syntax
257 .siblings_with_tokens(Direction::Next)
258 .find_map(|s| match s.kind() {
259 NEWLINE => Some(true),
260 WHITESPACE | COMMENT => None,
261 _ => Some(false),
262 })
263 .unwrap_or(true);
264
265 if !new_line_after {
266 return false;
267 }
268 } else {
269 return false;
270 }
271 }
272 None => {}
273 }
274
275 before_syntax
276 .siblings_with_tokens(Direction::Prev)
277 .find_map(|s| match s.kind() {
278 NEWLINE => Some(true),
279 WHITESPACE | COMMENT => None,
280 _ => Some(false),
281 })
282 .unwrap_or(true)
283 }
284
285 #[must_use]
286 pub fn in_entry_keys(&self) -> bool {
287 self.entry_key()
288 .is_some_and(|k| k.text_range().contains(self.offset))
289 }
290
291 #[must_use]
292 pub fn entry_has_eq(&self) -> bool {
293 let Some(key_syntax) = self.entry_key() else {
294 return false;
295 };
296
297 key_syntax
298 .siblings(Direction::Next)
299 .find_map(|s| match s.kind() {
300 EQ => Some(true),
301 WHITESPACE => None,
302 _ => Some(false),
303 })
304 .unwrap_or(false)
305 }
306
307 #[must_use]
308 pub fn in_entry_value(&self) -> bool {
309 let in_value = self
310 .entry_value()
311 .is_some_and(|k| k.text_range().contains_inclusive(self.offset));
313
314 if in_value {
315 return true;
316 }
317
318 let syntax = match self.before.as_ref().or(self.after.as_ref()) {
319 Some(p) => &p.syntax,
320 None => return false,
321 };
322
323 syntax
324 .siblings_with_tokens(Direction::Prev)
325 .find_map(|s| match s.kind() {
326 EQ => Some(true),
327 WHITESPACE | COMMENT | NEWLINE => None,
328 _ => Some(false),
329 })
330 .unwrap_or(false)
331 }
332
333 #[must_use]
334 pub fn is_single_quote_value(&self) -> bool {
335 self.entry_value().is_some_and(|v| {
336 v.descendants_with_tokens()
337 .any(|t| matches!(t.kind(), STRING_LITERAL | MULTI_LINE_STRING_LITERAL))
338 })
339 }
340
341 #[must_use]
342 pub fn is_inline(&self) -> bool {
343 let syntax = match self.before.as_ref().or(self.after.as_ref()) {
344 Some(p) => &p.syntax,
345 None => return false,
346 };
347
348 syntax
349 .parent_ancestors()
350 .any(|a| matches!(a.kind(), INLINE_TABLE | ARRAY))
351 }
352
353 #[must_use]
354 pub fn in_inline_table(&self) -> bool {
355 let syntax = match self.before.as_ref().or(self.after.as_ref()) {
356 Some(p) => &p.syntax,
357 None => return false,
358 };
359
360 match syntax.parent() {
361 Some(parent) => {
362 if parent.kind() != INLINE_TABLE {
363 return false;
364 }
365
366 parent
367 .children_with_tokens()
368 .find_map(|t| {
369 if t.kind() == BRACE_END {
370 Some(self.offset <= t.text_range().start())
371 } else {
372 None
373 }
374 })
375 .unwrap_or(true)
376 }
377 None => false,
378 }
379 }
380
381 #[must_use]
382 pub fn in_array(&self) -> bool {
383 let syntax = match self.before.as_ref().or(self.after.as_ref()) {
384 Some(p) => &p.syntax,
385 None => return false,
386 };
387
388 match syntax.parent() {
389 Some(parent) => {
390 if parent.kind() != ARRAY {
391 return false;
392 }
393
394 parent
395 .children_with_tokens()
396 .find_map(|t| {
397 if t.kind() == BRACKET_END {
398 Some(self.offset <= t.text_range().start())
399 } else {
400 None
401 }
402 })
403 .unwrap_or(true)
404 }
405 None => false,
406 }
407 }
408
409 pub fn entry_keys(&self) -> Keys {
410 self.entry_key()
411 .map_or_else(Keys::empty, |keys| Keys::from_syntax(keys.into()))
412 }
413
414 pub fn header_keys(&self) -> Keys {
415 self.header_key()
416 .map_or_else(Keys::empty, |keys| Keys::from_syntax(keys.into()))
417 }
418
419 #[must_use]
420 pub fn dom_node(&self) -> Option<&(Keys, Node)> {
421 self.before
422 .as_ref()
423 .and_then(|p| p.dom_node.as_ref())
424 .or_else(|| self.after.as_ref().and_then(|p| p.dom_node.as_ref()))
425 }
426}
427
428#[must_use]
433pub fn lookup_keys(root: Node, keys: &Keys) -> Keys {
434 let mut node = root;
435 let mut new_keys = Keys::empty();
436
437 for key in keys.iter().cloned() {
438 node = node.get(&key);
439 new_keys = new_keys.join(key);
440 if let Some(arr) = node.as_array() {
441 new_keys = new_keys.join(arr.items().read().len().saturating_sub(1));
442 }
443 }
444
445 new_keys
446}
447
448#[derive(Debug, Clone)]
449pub struct PositionInfo {
450 pub syntax: SyntaxToken,
452 pub dom_node: Option<(Keys, Node)>,
454}
455
456fn full_range(keys: &Keys, node: &Node) -> TextRange {
457 let Some(last_key) = keys
458 .iter()
459 .filter_map(KeyOrIndex::as_key)
460 .next_back()
461 .map(Key::text_ranges)
462 else {
463 return join_ranges(node.text_ranges(true));
464 };
465
466 join_ranges(last_key.chain(node.text_ranges(true)))
467}