1use super::*;
2use gpui::{ModelHandle, MutableAppContext};
3use std::rc::Rc;
4use unindent::Unindent as _;
5
6#[gpui::test]
7fn test_edit_events(cx: &mut gpui::MutableAppContext) {
8 let mut now = Instant::now();
9 let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
10 let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
11
12 let buffer1 = cx.add_model(|cx| Buffer::new(0, "abcdef", cx));
13 let buffer2 = cx.add_model(|cx| Buffer::new(1, "abcdef", cx));
14 let buffer_ops = buffer1.update(cx, |buffer, cx| {
15 let buffer_1_events = buffer_1_events.clone();
16 cx.subscribe(&buffer1, move |_, _, event, _| {
17 buffer_1_events.borrow_mut().push(event.clone())
18 })
19 .detach();
20 let buffer_2_events = buffer_2_events.clone();
21 cx.subscribe(&buffer2, move |_, _, event, _| {
22 buffer_2_events.borrow_mut().push(event.clone())
23 })
24 .detach();
25
26 // An edit emits an edited event, followed by a dirtied event,
27 // since the buffer was previously in a clean state.
28 buffer.edit(Some(2..4), "XYZ", cx);
29
30 // An empty transaction does not emit any events.
31 buffer.start_transaction(None).unwrap();
32 buffer.end_transaction(None, cx).unwrap();
33
34 // A transaction containing two edits emits one edited event.
35 now += Duration::from_secs(1);
36 buffer.start_transaction_at(None, now).unwrap();
37 buffer.edit(Some(5..5), "u", cx);
38 buffer.edit(Some(6..6), "w", cx);
39 buffer.end_transaction_at(None, now, cx).unwrap();
40
41 // Undoing a transaction emits one edited event.
42 buffer.undo(cx);
43
44 buffer.operations.clone()
45 });
46
47 // Incorporating a set of remote ops emits a single edited event,
48 // followed by a dirtied event.
49 buffer2.update(cx, |buffer, cx| {
50 buffer.apply_ops(buffer_ops, cx).unwrap();
51 });
52
53 let buffer_1_events = buffer_1_events.borrow();
54 assert_eq!(
55 *buffer_1_events,
56 vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
57 );
58
59 let buffer_2_events = buffer_2_events.borrow();
60 assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
61}
62
63#[gpui::test]
64async fn test_apply_diff(mut cx: gpui::TestAppContext) {
65 let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
66 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
67
68 let text = "a\nccc\ndddd\nffffff\n";
69 let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
70 buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
71 cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
72
73 let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
74 let diff = buffer.read_with(&cx, |b, cx| b.diff(text.into(), cx)).await;
75 buffer.update(&mut cx, |b, cx| b.apply_diff(diff, cx));
76 cx.read(|cx| assert_eq!(buffer.read(cx).text(), text));
77}
78
79#[gpui::test]
80async fn test_reparse(mut cx: gpui::TestAppContext) {
81 let text = "fn a() {}";
82 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(rust_lang(), None, cx));
83
84 // Wait for the initial text to parse
85 buffer
86 .condition(&cx, |buffer, _| !buffer.is_parsing())
87 .await;
88 assert_eq!(
89 get_tree_sexp(&buffer, &cx),
90 concat!(
91 "(source_file (function_item name: (identifier) ",
92 "parameters: (parameters) ",
93 "body: (block)))"
94 )
95 );
96
97 buffer.update(&mut cx, |buffer, _| {
98 buffer.set_sync_parse_timeout(Duration::ZERO)
99 });
100
101 // Perform some edits (add parameter and variable reference)
102 // Parsing doesn't begin until the transaction is complete
103 buffer.update(&mut cx, |buf, cx| {
104 buf.start_transaction(None).unwrap();
105
106 let offset = buf.text().find(")").unwrap();
107 buf.edit(vec![offset..offset], "b: C", cx);
108 assert!(!buf.is_parsing());
109
110 let offset = buf.text().find("}").unwrap();
111 buf.edit(vec![offset..offset], " d; ", cx);
112 assert!(!buf.is_parsing());
113
114 buf.end_transaction(None, cx).unwrap();
115 assert_eq!(buf.text(), "fn a(b: C) { d; }");
116 assert!(buf.is_parsing());
117 });
118 buffer
119 .condition(&cx, |buffer, _| !buffer.is_parsing())
120 .await;
121 assert_eq!(
122 get_tree_sexp(&buffer, &cx),
123 concat!(
124 "(source_file (function_item name: (identifier) ",
125 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
126 "body: (block (identifier))))"
127 )
128 );
129
130 // Perform a series of edits without waiting for the current parse to complete:
131 // * turn identifier into a field expression
132 // * turn field expression into a method call
133 // * add a turbofish to the method call
134 buffer.update(&mut cx, |buf, cx| {
135 let offset = buf.text().find(";").unwrap();
136 buf.edit(vec![offset..offset], ".e", cx);
137 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
138 assert!(buf.is_parsing());
139 });
140 buffer.update(&mut cx, |buf, cx| {
141 let offset = buf.text().find(";").unwrap();
142 buf.edit(vec![offset..offset], "(f)", cx);
143 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
144 assert!(buf.is_parsing());
145 });
146 buffer.update(&mut cx, |buf, cx| {
147 let offset = buf.text().find("(f)").unwrap();
148 buf.edit(vec![offset..offset], "::<G>", cx);
149 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
150 assert!(buf.is_parsing());
151 });
152 buffer
153 .condition(&cx, |buffer, _| !buffer.is_parsing())
154 .await;
155 assert_eq!(
156 get_tree_sexp(&buffer, &cx),
157 concat!(
158 "(source_file (function_item name: (identifier) ",
159 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
160 "body: (block (call_expression ",
161 "function: (generic_function ",
162 "function: (field_expression value: (identifier) field: (field_identifier)) ",
163 "type_arguments: (type_arguments (type_identifier))) ",
164 "arguments: (arguments (identifier))))))",
165 )
166 );
167
168 buffer.update(&mut cx, |buf, cx| {
169 buf.undo(cx);
170 assert_eq!(buf.text(), "fn a() {}");
171 assert!(buf.is_parsing());
172 });
173 buffer
174 .condition(&cx, |buffer, _| !buffer.is_parsing())
175 .await;
176 assert_eq!(
177 get_tree_sexp(&buffer, &cx),
178 concat!(
179 "(source_file (function_item name: (identifier) ",
180 "parameters: (parameters) ",
181 "body: (block)))"
182 )
183 );
184
185 buffer.update(&mut cx, |buf, cx| {
186 buf.redo(cx);
187 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
188 assert!(buf.is_parsing());
189 });
190 buffer
191 .condition(&cx, |buffer, _| !buffer.is_parsing())
192 .await;
193 assert_eq!(
194 get_tree_sexp(&buffer, &cx),
195 concat!(
196 "(source_file (function_item name: (identifier) ",
197 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
198 "body: (block (call_expression ",
199 "function: (generic_function ",
200 "function: (field_expression value: (identifier) field: (field_identifier)) ",
201 "type_arguments: (type_arguments (type_identifier))) ",
202 "arguments: (arguments (identifier))))))",
203 )
204 );
205
206 fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
207 buffer.read_with(cx, |buffer, _| {
208 buffer.syntax_tree().unwrap().root_node().to_sexp()
209 })
210 }
211}
212
213#[gpui::test]
214fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
215 let buffer = cx.add_model(|cx| {
216 let text = "
217 mod x {
218 mod y {
219
220 }
221 }
222 "
223 .unindent();
224 Buffer::new(0, text, cx).with_language(rust_lang(), None, cx)
225 });
226 let buffer = buffer.read(cx);
227 assert_eq!(
228 buffer.enclosing_bracket_point_ranges(Point::new(1, 6)..Point::new(1, 6)),
229 Some((
230 Point::new(0, 6)..Point::new(0, 7),
231 Point::new(4, 0)..Point::new(4, 1)
232 ))
233 );
234 assert_eq!(
235 buffer.enclosing_bracket_point_ranges(Point::new(1, 10)..Point::new(1, 10)),
236 Some((
237 Point::new(1, 10)..Point::new(1, 11),
238 Point::new(3, 4)..Point::new(3, 5)
239 ))
240 );
241 assert_eq!(
242 buffer.enclosing_bracket_point_ranges(Point::new(3, 5)..Point::new(3, 5)),
243 Some((
244 Point::new(1, 10)..Point::new(1, 11),
245 Point::new(3, 4)..Point::new(3, 5)
246 ))
247 );
248}
249
250#[gpui::test]
251fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
252 cx.add_model(|cx| {
253 let text = "fn a() {}";
254 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
255
256 buffer.edit_with_autoindent([8..8], "\n\n", cx);
257 assert_eq!(buffer.text(), "fn a() {\n \n}");
258
259 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 4)], "b()\n", cx);
260 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
261
262 buffer.edit_with_autoindent([Point::new(2, 4)..Point::new(2, 4)], ".c", cx);
263 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
264
265 buffer
266 });
267}
268
269#[gpui::test]
270fn test_autoindent_moves_selections(cx: &mut MutableAppContext) {
271 cx.add_model(|cx| {
272 let text = "fn a() {}";
273
274 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
275
276 let selection_set_id = buffer.add_selection_set::<usize>(&[], cx);
277 buffer.start_transaction(Some(selection_set_id)).unwrap();
278 buffer.edit_with_autoindent([5..5, 9..9], "\n\n", cx);
279 buffer
280 .update_selection_set(
281 selection_set_id,
282 &[
283 Selection {
284 id: 0,
285 start: Point::new(1, 0),
286 end: Point::new(1, 0),
287 reversed: false,
288 goal: SelectionGoal::None,
289 },
290 Selection {
291 id: 1,
292 start: Point::new(4, 0),
293 end: Point::new(4, 0),
294 reversed: false,
295 goal: SelectionGoal::None,
296 },
297 ],
298 cx,
299 )
300 .unwrap();
301 assert_eq!(buffer.text(), "fn a(\n\n) {}\n\n");
302
303 // Ending the transaction runs the auto-indent. The selection
304 // at the start of the auto-indented row is pushed to the right.
305 buffer.end_transaction(Some(selection_set_id), cx).unwrap();
306 assert_eq!(buffer.text(), "fn a(\n \n) {}\n\n");
307 let selection_ranges = buffer
308 .selection_set(selection_set_id)
309 .unwrap()
310 .point_selections(&buffer)
311 .map(|selection| selection.point_range(&buffer))
312 .collect::<Vec<_>>();
313
314 assert_eq!(selection_ranges[0], empty(Point::new(1, 4)));
315 assert_eq!(selection_ranges[1], empty(Point::new(4, 0)));
316
317 buffer
318 });
319}
320
321#[gpui::test]
322fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut MutableAppContext) {
323 cx.add_model(|cx| {
324 let text = "
325 fn a() {
326 c;
327 d;
328 }
329 "
330 .unindent();
331
332 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
333
334 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
335 // their indentation is not adjusted.
336 buffer.edit_with_autoindent([empty(Point::new(1, 1)), empty(Point::new(2, 1))], "()", cx);
337 assert_eq!(
338 buffer.text(),
339 "
340 fn a() {
341 c();
342 d();
343 }
344 "
345 .unindent()
346 );
347
348 // When appending new content after these lines, the indentation is based on the
349 // preceding lines' actual indentation.
350 buffer.edit_with_autoindent(
351 [empty(Point::new(1, 1)), empty(Point::new(2, 1))],
352 "\n.f\n.g",
353 cx,
354 );
355 assert_eq!(
356 buffer.text(),
357 "
358 fn a() {
359 c
360 .f
361 .g();
362 d
363 .f
364 .g();
365 }
366 "
367 .unindent()
368 );
369 buffer
370 });
371}
372
373#[gpui::test]
374fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppContext) {
375 cx.add_model(|cx| {
376 let text = "
377 fn a() {}
378 "
379 .unindent();
380
381 let mut buffer = Buffer::new(0, text, cx).with_language(rust_lang(), None, cx);
382
383 buffer.edit_with_autoindent([5..5], "\nb", cx);
384 assert_eq!(
385 buffer.text(),
386 "
387 fn a(
388 b) {}
389 "
390 .unindent()
391 );
392
393 // The indentation suggestion changed because `@end` node (a close paren)
394 // is now at the beginning of the line.
395 buffer.edit_with_autoindent([Point::new(1, 4)..Point::new(1, 5)], "", cx);
396 assert_eq!(
397 buffer.text(),
398 "
399 fn a(
400 ) {}
401 "
402 .unindent()
403 );
404
405 buffer
406 });
407}
408
409#[gpui::test]
410async fn test_diagnostics(mut cx: gpui::TestAppContext) {
411 let (language_server, mut fake) = lsp::LanguageServer::fake(cx.background()).await;
412
413 let text = "
414 fn a() { A }
415 fn b() { BB }
416 fn c() { CCC }
417 "
418 .unindent();
419
420 let buffer = cx.add_model(|cx| {
421 Buffer::new(0, text, cx).with_language(rust_lang(), Some(language_server), cx)
422 });
423
424 let open_notification = fake
425 .receive_notification::<lsp::notification::DidOpenTextDocument>()
426 .await;
427
428 // Edit the buffer, moving the content down
429 buffer.update(&mut cx, |buffer, cx| buffer.edit([0..0], "\n\n", cx));
430 let change_notification_1 = fake
431 .receive_notification::<lsp::notification::DidChangeTextDocument>()
432 .await;
433 assert!(change_notification_1.text_document.version > open_notification.text_document.version);
434
435 buffer.update(&mut cx, |buffer, cx| {
436 // Receive diagnostics for an earlier version of the buffer.
437 buffer
438 .update_diagnostics(
439 Some(open_notification.text_document.version),
440 vec![
441 lsp::Diagnostic {
442 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
443 severity: Some(lsp::DiagnosticSeverity::ERROR),
444 message: "undefined variable 'A'".to_string(),
445 ..Default::default()
446 },
447 lsp::Diagnostic {
448 range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
449 severity: Some(lsp::DiagnosticSeverity::ERROR),
450 message: "undefined variable 'BB'".to_string(),
451 ..Default::default()
452 },
453 lsp::Diagnostic {
454 range: lsp::Range::new(lsp::Position::new(2, 9), lsp::Position::new(2, 12)),
455 severity: Some(lsp::DiagnosticSeverity::ERROR),
456 message: "undefined variable 'CCC'".to_string(),
457 ..Default::default()
458 },
459 ],
460 cx,
461 )
462 .unwrap();
463
464 // The diagnostics have moved down since they were created.
465 assert_eq!(
466 buffer
467 .diagnostics_in_range(Point::new(3, 0)..Point::new(5, 0))
468 .collect::<Vec<_>>(),
469 &[
470 (
471 Point::new(3, 9)..Point::new(3, 11),
472 &Diagnostic {
473 severity: DiagnosticSeverity::ERROR,
474 message: "undefined variable 'BB'".to_string()
475 },
476 ),
477 (
478 Point::new(4, 9)..Point::new(4, 12),
479 &Diagnostic {
480 severity: DiagnosticSeverity::ERROR,
481 message: "undefined variable 'CCC'".to_string()
482 }
483 )
484 ]
485 );
486 assert_eq!(
487 chunks_with_diagnostics(buffer, 0..buffer.len()),
488 [
489 ("\n\nfn a() { ".to_string(), None),
490 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
491 (" }\nfn b() { ".to_string(), None),
492 ("BB".to_string(), Some(DiagnosticSeverity::ERROR)),
493 (" }\nfn c() { ".to_string(), None),
494 ("CCC".to_string(), Some(DiagnosticSeverity::ERROR)),
495 (" }\n".to_string(), None),
496 ]
497 );
498 assert_eq!(
499 chunks_with_diagnostics(buffer, Point::new(3, 10)..Point::new(4, 11)),
500 [
501 ("B".to_string(), Some(DiagnosticSeverity::ERROR)),
502 (" }\nfn c() { ".to_string(), None),
503 ("CC".to_string(), Some(DiagnosticSeverity::ERROR)),
504 ]
505 );
506
507 // Ensure overlapping diagnostics are highlighted correctly.
508 buffer
509 .update_diagnostics(
510 Some(open_notification.text_document.version),
511 vec![
512 lsp::Diagnostic {
513 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
514 severity: Some(lsp::DiagnosticSeverity::ERROR),
515 message: "undefined variable 'A'".to_string(),
516 ..Default::default()
517 },
518 lsp::Diagnostic {
519 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 12)),
520 severity: Some(lsp::DiagnosticSeverity::WARNING),
521 message: "unreachable statement".to_string(),
522 ..Default::default()
523 },
524 ],
525 cx,
526 )
527 .unwrap();
528 assert_eq!(
529 buffer
530 .diagnostics_in_range(Point::new(2, 0)..Point::new(3, 0))
531 .collect::<Vec<_>>(),
532 &[
533 (
534 Point::new(2, 9)..Point::new(2, 12),
535 &Diagnostic {
536 severity: DiagnosticSeverity::WARNING,
537 message: "unreachable statement".to_string()
538 }
539 ),
540 (
541 Point::new(2, 9)..Point::new(2, 10),
542 &Diagnostic {
543 severity: DiagnosticSeverity::ERROR,
544 message: "undefined variable 'A'".to_string()
545 },
546 )
547 ]
548 );
549 assert_eq!(
550 chunks_with_diagnostics(buffer, Point::new(2, 0)..Point::new(3, 0)),
551 [
552 ("fn a() { ".to_string(), None),
553 ("A".to_string(), Some(DiagnosticSeverity::ERROR)),
554 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
555 ("\n".to_string(), None),
556 ]
557 );
558 assert_eq!(
559 chunks_with_diagnostics(buffer, Point::new(2, 10)..Point::new(3, 0)),
560 [
561 (" }".to_string(), Some(DiagnosticSeverity::WARNING)),
562 ("\n".to_string(), None),
563 ]
564 );
565 });
566
567 // Keep editing the buffer and ensure disk-based diagnostics get translated according to the
568 // changes since the last save.
569 buffer.update(&mut cx, |buffer, cx| {
570 buffer.edit(Some(Point::new(2, 0)..Point::new(2, 0)), " ", cx);
571 buffer.edit(Some(Point::new(2, 8)..Point::new(2, 10)), "(x: usize)", cx);
572 });
573 let change_notification_2 = fake
574 .receive_notification::<lsp::notification::DidChangeTextDocument>()
575 .await;
576 assert!(
577 change_notification_2.text_document.version > change_notification_1.text_document.version
578 );
579
580 buffer.update(&mut cx, |buffer, cx| {
581 buffer
582 .update_diagnostics(
583 Some(change_notification_2.text_document.version),
584 vec![
585 lsp::Diagnostic {
586 range: lsp::Range::new(lsp::Position::new(1, 9), lsp::Position::new(1, 11)),
587 severity: Some(lsp::DiagnosticSeverity::ERROR),
588 message: "undefined variable 'BB'".to_string(),
589 source: Some("rustc".to_string()),
590 ..Default::default()
591 },
592 lsp::Diagnostic {
593 range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
594 severity: Some(lsp::DiagnosticSeverity::ERROR),
595 message: "undefined variable 'A'".to_string(),
596 source: Some("rustc".to_string()),
597 ..Default::default()
598 },
599 ],
600 cx,
601 )
602 .unwrap();
603 assert_eq!(
604 buffer
605 .diagnostics_in_range(0..buffer.len())
606 .collect::<Vec<_>>(),
607 &[
608 (
609 Point::new(2, 21)..Point::new(2, 22),
610 &Diagnostic {
611 severity: DiagnosticSeverity::ERROR,
612 message: "undefined variable 'A'".to_string()
613 }
614 ),
615 (
616 Point::new(3, 9)..Point::new(3, 11),
617 &Diagnostic {
618 severity: DiagnosticSeverity::ERROR,
619 message: "undefined variable 'BB'".to_string()
620 },
621 )
622 ]
623 );
624 });
625
626 fn chunks_with_diagnostics<T: ToOffset>(
627 buffer: &Buffer,
628 range: Range<T>,
629 ) -> Vec<(String, Option<DiagnosticSeverity>)> {
630 let mut chunks: Vec<(String, Option<DiagnosticSeverity>)> = Vec::new();
631 for chunk in buffer.snapshot().highlighted_text_for_range(range) {
632 if chunks
633 .last()
634 .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic)
635 {
636 chunks.last_mut().unwrap().0.push_str(chunk.text);
637 } else {
638 chunks.push((chunk.text.to_string(), chunk.diagnostic));
639 }
640 }
641 chunks
642 }
643}
644
645#[test]
646fn test_contiguous_ranges() {
647 assert_eq!(
648 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12], 100).collect::<Vec<_>>(),
649 &[1..4, 5..7, 9..13]
650 );
651
652 // Respects the `max_len` parameter
653 assert_eq!(
654 contiguous_ranges([2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31], 3).collect::<Vec<_>>(),
655 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
656 );
657}
658
659impl Buffer {
660 pub fn enclosing_bracket_point_ranges<T: ToOffset>(
661 &self,
662 range: Range<T>,
663 ) -> Option<(Range<Point>, Range<Point>)> {
664 self.enclosing_bracket_ranges(range).map(|(start, end)| {
665 let point_start = start.start.to_point(self)..start.end.to_point(self);
666 let point_end = end.start.to_point(self)..end.end.to_point(self);
667 (point_start, point_end)
668 })
669 }
670}
671
672fn rust_lang() -> Option<Arc<Language>> {
673 Some(Arc::new(
674 Language::new(
675 LanguageConfig {
676 name: "Rust".to_string(),
677 path_suffixes: vec!["rs".to_string()],
678 language_server: None,
679 ..Default::default()
680 },
681 tree_sitter_rust::language(),
682 )
683 .with_indents_query(
684 r#"
685 (call_expression) @indent
686 (field_expression) @indent
687 (_ "(" ")" @end) @indent
688 (_ "{" "}" @end) @indent
689 "#,
690 )
691 .unwrap()
692 .with_brackets_query(r#" ("{" @open "}" @close) "#)
693 .unwrap(),
694 ))
695}
696
697fn empty(point: Point) -> Range<Point> {
698 point..point
699}