1use crate::language_settings::{
2 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
3};
4
5use super::*;
6use clock::ReplicaId;
7use collections::BTreeMap;
8use gpui::{AppContext, ModelHandle};
9use indoc::indoc;
10use proto::deserialize_operation;
11use rand::prelude::*;
12use regex::RegexBuilder;
13use settings::SettingsStore;
14use std::{
15 cell::RefCell,
16 env,
17 ops::Range,
18 rc::Rc,
19 time::{Duration, Instant},
20};
21use text::network::Network;
22use text::LineEnding;
23use unindent::Unindent as _;
24use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
25
26lazy_static! {
27 static ref TRAILING_WHITESPACE_REGEX: Regex = RegexBuilder::new("[ \t]+$")
28 .multi_line(true)
29 .build()
30 .unwrap();
31}
32
33#[cfg(test)]
34#[ctor::ctor]
35fn init_logger() {
36 if std::env::var("RUST_LOG").is_ok() {
37 env_logger::init();
38 }
39}
40
41#[gpui::test]
42fn test_line_endings(cx: &mut gpui::AppContext) {
43 init_settings(cx, |_| {});
44
45 cx.add_model(|cx| {
46 let mut buffer = Buffer::new(0, cx.model_id() as u64, "one\r\ntwo\rthree")
47 .with_language(Arc::new(rust_lang()), cx);
48 assert_eq!(buffer.text(), "one\ntwo\nthree");
49 assert_eq!(buffer.line_ending(), LineEnding::Windows);
50
51 buffer.check_invariants();
52 buffer.edit(
53 [(buffer.len()..buffer.len(), "\r\nfour")],
54 Some(AutoindentMode::EachLine),
55 cx,
56 );
57 buffer.edit([(0..0, "zero\r\n")], None, cx);
58 assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
59 assert_eq!(buffer.line_ending(), LineEnding::Windows);
60 buffer.check_invariants();
61
62 buffer
63 });
64}
65
66#[gpui::test]
67fn test_select_language() {
68 let registry = Arc::new(LanguageRegistry::test());
69 registry.add(Arc::new(Language::new(
70 LanguageConfig {
71 name: "Rust".into(),
72 path_suffixes: vec!["rs".to_string()],
73 ..Default::default()
74 },
75 Some(tree_sitter_rust::language()),
76 )));
77 registry.add(Arc::new(Language::new(
78 LanguageConfig {
79 name: "Make".into(),
80 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
81 ..Default::default()
82 },
83 Some(tree_sitter_rust::language()),
84 )));
85
86 // matching file extension
87 assert_eq!(
88 registry
89 .language_for_file("zed/lib.rs", None)
90 .now_or_never()
91 .and_then(|l| Some(l.ok()?.name())),
92 Some("Rust".into())
93 );
94 assert_eq!(
95 registry
96 .language_for_file("zed/lib.mk", None)
97 .now_or_never()
98 .and_then(|l| Some(l.ok()?.name())),
99 Some("Make".into())
100 );
101
102 // matching filename
103 assert_eq!(
104 registry
105 .language_for_file("zed/Makefile", None)
106 .now_or_never()
107 .and_then(|l| Some(l.ok()?.name())),
108 Some("Make".into())
109 );
110
111 // matching suffix that is not the full file extension or filename
112 assert_eq!(
113 registry
114 .language_for_file("zed/cars", None)
115 .now_or_never()
116 .and_then(|l| Some(l.ok()?.name())),
117 None
118 );
119 assert_eq!(
120 registry
121 .language_for_file("zed/a.cars", None)
122 .now_or_never()
123 .and_then(|l| Some(l.ok()?.name())),
124 None
125 );
126 assert_eq!(
127 registry
128 .language_for_file("zed/sumk", None)
129 .now_or_never()
130 .and_then(|l| Some(l.ok()?.name())),
131 None
132 );
133}
134
135#[gpui::test]
136fn test_edit_events(cx: &mut gpui::AppContext) {
137 let mut now = Instant::now();
138 let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
139 let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
140
141 let buffer1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abcdef"));
142 let buffer2 = cx.add_model(|cx| Buffer::new(1, cx.model_id() as u64, "abcdef"));
143 let buffer1_ops = Rc::new(RefCell::new(Vec::new()));
144 buffer1.update(cx, {
145 let buffer1_ops = buffer1_ops.clone();
146 |buffer, cx| {
147 let buffer_1_events = buffer_1_events.clone();
148 cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
149 Event::Operation(op) => buffer1_ops.borrow_mut().push(op),
150 event => buffer_1_events.borrow_mut().push(event),
151 })
152 .detach();
153 let buffer_2_events = buffer_2_events.clone();
154 cx.subscribe(&buffer2, move |_, _, event, _| {
155 buffer_2_events.borrow_mut().push(event.clone())
156 })
157 .detach();
158
159 // An edit emits an edited event, followed by a dirty changed event,
160 // since the buffer was previously in a clean state.
161 buffer.edit([(2..4, "XYZ")], None, cx);
162
163 // An empty transaction does not emit any events.
164 buffer.start_transaction();
165 buffer.end_transaction(cx);
166
167 // A transaction containing two edits emits one edited event.
168 now += Duration::from_secs(1);
169 buffer.start_transaction_at(now);
170 buffer.edit([(5..5, "u")], None, cx);
171 buffer.edit([(6..6, "w")], None, cx);
172 buffer.end_transaction_at(now, cx);
173
174 // Undoing a transaction emits one edited event.
175 buffer.undo(cx);
176 }
177 });
178
179 // Incorporating a set of remote ops emits a single edited event,
180 // followed by a dirty changed event.
181 buffer2.update(cx, |buffer, cx| {
182 buffer
183 .apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
184 .unwrap();
185 });
186 assert_eq!(
187 mem::take(&mut *buffer_1_events.borrow_mut()),
188 vec![
189 Event::Edited,
190 Event::DirtyChanged,
191 Event::Edited,
192 Event::Edited,
193 ]
194 );
195 assert_eq!(
196 mem::take(&mut *buffer_2_events.borrow_mut()),
197 vec![Event::Edited, Event::DirtyChanged]
198 );
199
200 buffer1.update(cx, |buffer, cx| {
201 // Undoing the first transaction emits edited event, followed by a
202 // dirty changed event, since the buffer is again in a clean state.
203 buffer.undo(cx);
204 });
205 // Incorporating the remote ops again emits a single edited event,
206 // followed by a dirty changed event.
207 buffer2.update(cx, |buffer, cx| {
208 buffer
209 .apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
210 .unwrap();
211 });
212 assert_eq!(
213 mem::take(&mut *buffer_1_events.borrow_mut()),
214 vec![Event::Edited, Event::DirtyChanged,]
215 );
216 assert_eq!(
217 mem::take(&mut *buffer_2_events.borrow_mut()),
218 vec![Event::Edited, Event::DirtyChanged]
219 );
220}
221
222#[gpui::test]
223async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
224 let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
225 let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
226 let anchor = buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
227
228 let text = "a\nccc\ndddd\nffffff\n";
229 let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
230 buffer.update(cx, |buffer, cx| {
231 buffer.apply_diff(diff, cx).unwrap();
232 assert_eq!(buffer.text(), text);
233 assert_eq!(anchor.to_point(buffer), Point::new(2, 3));
234 });
235
236 let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
237 let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
238 buffer.update(cx, |buffer, cx| {
239 buffer.apply_diff(diff, cx).unwrap();
240 assert_eq!(buffer.text(), text);
241 assert_eq!(anchor.to_point(buffer), Point::new(4, 4));
242 });
243}
244
245#[gpui::test(iterations = 10)]
246async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
247 let text = [
248 "zero", //
249 "one ", // 2 trailing spaces
250 "two", //
251 "three ", // 3 trailing spaces
252 "four", //
253 "five ", // 4 trailing spaces
254 ]
255 .join("\n");
256
257 let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
258
259 // Spawn a task to format the buffer's whitespace.
260 // Pause so that the foratting task starts running.
261 let format = buffer.read_with(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
262 smol::future::yield_now().await;
263
264 // Edit the buffer while the normalization task is running.
265 let version_before_edit = buffer.read_with(cx, |buffer, _| buffer.version());
266 buffer.update(cx, |buffer, cx| {
267 buffer.edit(
268 [
269 (Point::new(0, 1)..Point::new(0, 1), "EE"),
270 (Point::new(3, 5)..Point::new(3, 5), "EEE"),
271 ],
272 None,
273 cx,
274 );
275 });
276
277 let format_diff = format.await;
278 buffer.update(cx, |buffer, cx| {
279 let version_before_format = format_diff.base_version.clone();
280 buffer.apply_diff(format_diff, cx);
281
282 // The outcome depends on the order of concurrent taks.
283 //
284 // If the edit occurred while searching for trailing whitespace ranges,
285 // then the trailing whitespace region touched by the edit is left intact.
286 if version_before_format == version_before_edit {
287 assert_eq!(
288 buffer.text(),
289 [
290 "zEEero", //
291 "one", //
292 "two", //
293 "threeEEE ", //
294 "four", //
295 "five", //
296 ]
297 .join("\n")
298 );
299 }
300 // Otherwise, all trailing whitespace is removed.
301 else {
302 assert_eq!(
303 buffer.text(),
304 [
305 "zEEero", //
306 "one", //
307 "two", //
308 "threeEEE", //
309 "four", //
310 "five", //
311 ]
312 .join("\n")
313 );
314 }
315 });
316}
317
318#[gpui::test]
319async fn test_reparse(cx: &mut gpui::TestAppContext) {
320 let text = "fn a() {}";
321 let buffer = cx.add_model(|cx| {
322 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
323 });
324
325 // Wait for the initial text to parse
326 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
327 assert_eq!(
328 get_tree_sexp(&buffer, cx),
329 concat!(
330 "(source_file (function_item name: (identifier) ",
331 "parameters: (parameters) ",
332 "body: (block)))"
333 )
334 );
335
336 buffer.update(cx, |buffer, _| {
337 buffer.set_sync_parse_timeout(Duration::ZERO)
338 });
339
340 // Perform some edits (add parameter and variable reference)
341 // Parsing doesn't begin until the transaction is complete
342 buffer.update(cx, |buf, cx| {
343 buf.start_transaction();
344
345 let offset = buf.text().find(')').unwrap();
346 buf.edit([(offset..offset, "b: C")], None, cx);
347 assert!(!buf.is_parsing());
348
349 let offset = buf.text().find('}').unwrap();
350 buf.edit([(offset..offset, " d; ")], None, cx);
351 assert!(!buf.is_parsing());
352
353 buf.end_transaction(cx);
354 assert_eq!(buf.text(), "fn a(b: C) { d; }");
355 assert!(buf.is_parsing());
356 });
357 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
358 assert_eq!(
359 get_tree_sexp(&buffer, cx),
360 concat!(
361 "(source_file (function_item name: (identifier) ",
362 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
363 "body: (block (expression_statement (identifier)))))"
364 )
365 );
366
367 // Perform a series of edits without waiting for the current parse to complete:
368 // * turn identifier into a field expression
369 // * turn field expression into a method call
370 // * add a turbofish to the method call
371 buffer.update(cx, |buf, cx| {
372 let offset = buf.text().find(';').unwrap();
373 buf.edit([(offset..offset, ".e")], None, cx);
374 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
375 assert!(buf.is_parsing());
376 });
377 buffer.update(cx, |buf, cx| {
378 let offset = buf.text().find(';').unwrap();
379 buf.edit([(offset..offset, "(f)")], None, cx);
380 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
381 assert!(buf.is_parsing());
382 });
383 buffer.update(cx, |buf, cx| {
384 let offset = buf.text().find("(f)").unwrap();
385 buf.edit([(offset..offset, "::<G>")], None, cx);
386 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
387 assert!(buf.is_parsing());
388 });
389 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
390 assert_eq!(
391 get_tree_sexp(&buffer, cx),
392 concat!(
393 "(source_file (function_item name: (identifier) ",
394 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
395 "body: (block (expression_statement (call_expression ",
396 "function: (generic_function ",
397 "function: (field_expression value: (identifier) field: (field_identifier)) ",
398 "type_arguments: (type_arguments (type_identifier))) ",
399 "arguments: (arguments (identifier)))))))",
400 )
401 );
402
403 buffer.update(cx, |buf, cx| {
404 buf.undo(cx);
405 buf.undo(cx);
406 buf.undo(cx);
407 buf.undo(cx);
408 assert_eq!(buf.text(), "fn a() {}");
409 assert!(buf.is_parsing());
410 });
411 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
412 assert_eq!(
413 get_tree_sexp(&buffer, cx),
414 concat!(
415 "(source_file (function_item name: (identifier) ",
416 "parameters: (parameters) ",
417 "body: (block)))"
418 )
419 );
420
421 buffer.update(cx, |buf, cx| {
422 buf.redo(cx);
423 buf.redo(cx);
424 buf.redo(cx);
425 buf.redo(cx);
426 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
427 assert!(buf.is_parsing());
428 });
429 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
430 assert_eq!(
431 get_tree_sexp(&buffer, cx),
432 concat!(
433 "(source_file (function_item name: (identifier) ",
434 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
435 "body: (block (expression_statement (call_expression ",
436 "function: (generic_function ",
437 "function: (field_expression value: (identifier) field: (field_identifier)) ",
438 "type_arguments: (type_arguments (type_identifier))) ",
439 "arguments: (arguments (identifier)))))))",
440 )
441 );
442}
443
444#[gpui::test]
445async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
446 let buffer = cx.add_model(|cx| {
447 let mut buffer =
448 Buffer::new(0, cx.model_id() as u64, "{}").with_language(Arc::new(rust_lang()), cx);
449 buffer.set_sync_parse_timeout(Duration::ZERO);
450 buffer
451 });
452
453 // Wait for the initial text to parse
454 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
455 assert_eq!(
456 get_tree_sexp(&buffer, cx),
457 "(source_file (expression_statement (block)))"
458 );
459
460 buffer.update(cx, |buffer, cx| {
461 buffer.set_language(Some(Arc::new(json_lang())), cx)
462 });
463 buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
464 assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
465}
466
467#[gpui::test]
468async fn test_outline(cx: &mut gpui::TestAppContext) {
469 let text = r#"
470 struct Person {
471 name: String,
472 age: usize,
473 }
474
475 mod module {
476 enum LoginState {
477 LoggedOut,
478 LoggingOn,
479 LoggedIn {
480 person: Person,
481 time: Instant,
482 }
483 }
484 }
485
486 impl Eq for Person {}
487
488 impl Drop for Person {
489 fn drop(&mut self) {
490 println!("bye");
491 }
492 }
493 "#
494 .unindent();
495
496 let buffer = cx.add_model(|cx| {
497 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
498 });
499 let outline = buffer
500 .read_with(cx, |buffer, _| buffer.snapshot().outline(None))
501 .unwrap();
502
503 assert_eq!(
504 outline
505 .items
506 .iter()
507 .map(|item| (item.text.as_str(), item.depth))
508 .collect::<Vec<_>>(),
509 &[
510 ("struct Person", 0),
511 ("name", 1),
512 ("age", 1),
513 ("mod module", 0),
514 ("enum LoginState", 1),
515 ("LoggedOut", 2),
516 ("LoggingOn", 2),
517 ("LoggedIn", 2),
518 ("person", 3),
519 ("time", 3),
520 ("impl Eq for Person", 0),
521 ("impl Drop for Person", 0),
522 ("fn drop", 1),
523 ]
524 );
525
526 // Without space, we only match on names
527 assert_eq!(
528 search(&outline, "oon", cx).await,
529 &[
530 ("mod module", vec![]), // included as the parent of a match
531 ("enum LoginState", vec![]), // included as the parent of a match
532 ("LoggingOn", vec![1, 7, 8]), // matches
533 ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
534 ]
535 );
536
537 assert_eq!(
538 search(&outline, "dp p", cx).await,
539 &[
540 ("impl Drop for Person", vec![5, 8, 9, 14]),
541 ("fn drop", vec![]),
542 ]
543 );
544 assert_eq!(
545 search(&outline, "dpn", cx).await,
546 &[("impl Drop for Person", vec![5, 14, 19])]
547 );
548 assert_eq!(
549 search(&outline, "impl ", cx).await,
550 &[
551 ("impl Eq for Person", vec![0, 1, 2, 3, 4]),
552 ("impl Drop for Person", vec![0, 1, 2, 3, 4]),
553 ("fn drop", vec![]),
554 ]
555 );
556
557 async fn search<'a>(
558 outline: &'a Outline<Anchor>,
559 query: &'a str,
560 cx: &'a gpui::TestAppContext,
561 ) -> Vec<(&'a str, Vec<usize>)> {
562 let matches = cx
563 .read(|cx| outline.search(query, cx.background().clone()))
564 .await;
565 matches
566 .into_iter()
567 .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions))
568 .collect::<Vec<_>>()
569 }
570}
571
572#[gpui::test]
573async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
574 let text = r#"
575 impl A for B<
576 C
577 > {
578 };
579 "#
580 .unindent();
581
582 let buffer = cx.add_model(|cx| {
583 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
584 });
585 let outline = buffer
586 .read_with(cx, |buffer, _| buffer.snapshot().outline(None))
587 .unwrap();
588
589 assert_eq!(
590 outline
591 .items
592 .iter()
593 .map(|item| (item.text.as_str(), item.depth))
594 .collect::<Vec<_>>(),
595 &[("impl A for B<", 0)]
596 );
597}
598
599#[gpui::test]
600async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
601 let language = javascript_lang()
602 .with_outline_query(
603 r#"
604 (function_declaration
605 "function" @context
606 name: (_) @name
607 parameters: (formal_parameters
608 "(" @context.extra
609 ")" @context.extra)) @item
610 "#,
611 )
612 .unwrap();
613
614 let text = r#"
615 function a() {}
616 function b(c) {}
617 "#
618 .unindent();
619
620 let buffer = cx.add_model(|cx| {
621 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(language), cx)
622 });
623 let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
624
625 // extra context nodes are included in the outline.
626 let outline = snapshot.outline(None).unwrap();
627 assert_eq!(
628 outline
629 .items
630 .iter()
631 .map(|item| (item.text.as_str(), item.depth))
632 .collect::<Vec<_>>(),
633 &[("function a()", 0), ("function b( )", 0),]
634 );
635
636 // extra context nodes do not appear in breadcrumbs.
637 let symbols = snapshot.symbols_containing(3, None).unwrap();
638 assert_eq!(
639 symbols
640 .iter()
641 .map(|item| (item.text.as_str(), item.depth))
642 .collect::<Vec<_>>(),
643 &[("function a", 0)]
644 );
645}
646
647#[gpui::test]
648async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
649 let text = r#"
650 impl Person {
651 fn one() {
652 1
653 }
654
655 fn two() {
656 2
657 }fn three() {
658 3
659 }
660 }
661 "#
662 .unindent();
663
664 let buffer = cx.add_model(|cx| {
665 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
666 });
667 let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
668
669 // point is at the start of an item
670 assert_eq!(
671 symbols_containing(Point::new(1, 4), &snapshot),
672 vec![
673 (
674 "impl Person".to_string(),
675 Point::new(0, 0)..Point::new(10, 1)
676 ),
677 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
678 ]
679 );
680
681 // point is in the middle of an item
682 assert_eq!(
683 symbols_containing(Point::new(2, 8), &snapshot),
684 vec![
685 (
686 "impl Person".to_string(),
687 Point::new(0, 0)..Point::new(10, 1)
688 ),
689 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
690 ]
691 );
692
693 // point is at the end of an item
694 assert_eq!(
695 symbols_containing(Point::new(3, 5), &snapshot),
696 vec![
697 (
698 "impl Person".to_string(),
699 Point::new(0, 0)..Point::new(10, 1)
700 ),
701 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
702 ]
703 );
704
705 // point is in between two adjacent items
706 assert_eq!(
707 symbols_containing(Point::new(7, 5), &snapshot),
708 vec![
709 (
710 "impl Person".to_string(),
711 Point::new(0, 0)..Point::new(10, 1)
712 ),
713 ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5))
714 ]
715 );
716
717 fn symbols_containing(
718 position: Point,
719 snapshot: &BufferSnapshot,
720 ) -> Vec<(String, Range<Point>)> {
721 snapshot
722 .symbols_containing(position, None)
723 .unwrap()
724 .into_iter()
725 .map(|item| {
726 (
727 item.text,
728 item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot),
729 )
730 })
731 .collect()
732 }
733}
734
735#[gpui::test]
736fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
737 let mut assert = |selection_text, range_markers| {
738 assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
739 };
740
741 assert(
742 indoc! {"
743 mod x {
744 moˇd y {
745
746 }
747 }
748 let foo = 1;"},
749 vec![indoc! {"
750 mod x «{»
751 mod y {
752
753 }
754 «}»
755 let foo = 1;"}],
756 );
757
758 assert(
759 indoc! {"
760 mod x {
761 mod y ˇ{
762
763 }
764 }
765 let foo = 1;"},
766 vec![
767 indoc! {"
768 mod x «{»
769 mod y {
770
771 }
772 «}»
773 let foo = 1;"},
774 indoc! {"
775 mod x {
776 mod y «{»
777
778 «}»
779 }
780 let foo = 1;"},
781 ],
782 );
783
784 assert(
785 indoc! {"
786 mod x {
787 mod y {
788
789 }ˇ
790 }
791 let foo = 1;"},
792 vec![
793 indoc! {"
794 mod x «{»
795 mod y {
796
797 }
798 «}»
799 let foo = 1;"},
800 indoc! {"
801 mod x {
802 mod y «{»
803
804 «}»
805 }
806 let foo = 1;"},
807 ],
808 );
809
810 assert(
811 indoc! {"
812 mod x {
813 mod y {
814
815 }
816 ˇ}
817 let foo = 1;"},
818 vec![indoc! {"
819 mod x «{»
820 mod y {
821
822 }
823 «}»
824 let foo = 1;"}],
825 );
826
827 assert(
828 indoc! {"
829 mod x {
830 mod y {
831
832 }
833 }
834 let fˇoo = 1;"},
835 vec![],
836 );
837
838 // Regression test: avoid crash when querying at the end of the buffer.
839 assert(
840 indoc! {"
841 mod x {
842 mod y {
843
844 }
845 }
846 let foo = 1;ˇ"},
847 vec![],
848 );
849}
850
851#[gpui::test]
852fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) {
853 let mut assert = |selection_text, bracket_pair_texts| {
854 assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
855 };
856
857 assert(
858 indoc! {"
859 for (const a in b)ˇ {
860 // a comment that's longer than the for-loop header
861 }"},
862 vec![indoc! {"
863 for «(»const a in b«)» {
864 // a comment that's longer than the for-loop header
865 }"}],
866 );
867
868 // Regression test: even though the parent node of the parentheses (the for loop) does
869 // intersect the given range, the parentheses themselves do not contain the range, so
870 // they should not be returned. Only the curly braces contain the range.
871 assert(
872 indoc! {"
873 for (const a in b) {ˇ
874 // a comment that's longer than the for-loop header
875 }"},
876 vec![indoc! {"
877 for (const a in b) «{»
878 // a comment that's longer than the for-loop header
879 «}»"}],
880 );
881}
882
883#[gpui::test]
884fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
885 cx.add_model(|cx| {
886 let text = "fn a() { b(|c| {}) }";
887 let buffer =
888 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
889 let snapshot = buffer.snapshot();
890
891 assert_eq!(
892 snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")),
893 Some(range_of(text, "|"))
894 );
895 assert_eq!(
896 snapshot.range_for_syntax_ancestor(range_of(text, "|")),
897 Some(range_of(text, "|c|"))
898 );
899 assert_eq!(
900 snapshot.range_for_syntax_ancestor(range_of(text, "|c|")),
901 Some(range_of(text, "|c| {}"))
902 );
903 assert_eq!(
904 snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")),
905 Some(range_of(text, "(|c| {})"))
906 );
907
908 buffer
909 });
910
911 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
912 let start = text.find(part).unwrap();
913 start..start
914 }
915
916 fn range_of(text: &str, part: &str) -> Range<usize> {
917 let start = text.find(part).unwrap();
918 start..start + part.len()
919 }
920}
921
922#[gpui::test]
923fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
924 init_settings(cx, |_| {});
925
926 cx.add_model(|cx| {
927 let text = "fn a() {}";
928 let mut buffer =
929 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
930
931 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
932 assert_eq!(buffer.text(), "fn a() {\n \n}");
933
934 buffer.edit(
935 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
936 Some(AutoindentMode::EachLine),
937 cx,
938 );
939 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
940
941 // Create a field expression on a new line, causing that line
942 // to be indented.
943 buffer.edit(
944 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
945 Some(AutoindentMode::EachLine),
946 cx,
947 );
948 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
949
950 // Remove the dot so that the line is no longer a field expression,
951 // causing the line to be outdented.
952 buffer.edit(
953 [(Point::new(2, 8)..Point::new(2, 9), "")],
954 Some(AutoindentMode::EachLine),
955 cx,
956 );
957 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
958
959 buffer
960 });
961}
962
963#[gpui::test]
964fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
965 init_settings(cx, |settings| {
966 settings.defaults.hard_tabs = Some(true);
967 });
968
969 cx.add_model(|cx| {
970 let text = "fn a() {}";
971 let mut buffer =
972 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
973
974 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
975 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
976
977 buffer.edit(
978 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
979 Some(AutoindentMode::EachLine),
980 cx,
981 );
982 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
983
984 // Create a field expression on a new line, causing that line
985 // to be indented.
986 buffer.edit(
987 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
988 Some(AutoindentMode::EachLine),
989 cx,
990 );
991 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
992
993 // Remove the dot so that the line is no longer a field expression,
994 // causing the line to be outdented.
995 buffer.edit(
996 [(Point::new(2, 2)..Point::new(2, 3), "")],
997 Some(AutoindentMode::EachLine),
998 cx,
999 );
1000 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
1001
1002 buffer
1003 });
1004}
1005
1006#[gpui::test]
1007fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
1008 init_settings(cx, |_| {});
1009
1010 cx.add_model(|cx| {
1011 let mut buffer = Buffer::new(
1012 0,
1013 cx.model_id() as u64,
1014 "
1015 fn a() {
1016 c;
1017 d;
1018 }
1019 "
1020 .unindent(),
1021 )
1022 .with_language(Arc::new(rust_lang()), cx);
1023
1024 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
1025 // their indentation is not adjusted.
1026 buffer.edit_via_marked_text(
1027 &"
1028 fn a() {
1029 c«()»;
1030 d«()»;
1031 }
1032 "
1033 .unindent(),
1034 Some(AutoindentMode::EachLine),
1035 cx,
1036 );
1037 assert_eq!(
1038 buffer.text(),
1039 "
1040 fn a() {
1041 c();
1042 d();
1043 }
1044 "
1045 .unindent()
1046 );
1047
1048 // When appending new content after these lines, the indentation is based on the
1049 // preceding lines' actual indentation.
1050 buffer.edit_via_marked_text(
1051 &"
1052 fn a() {
1053 c«
1054 .f
1055 .g()»;
1056 d«
1057 .f
1058 .g()»;
1059 }
1060 "
1061 .unindent(),
1062 Some(AutoindentMode::EachLine),
1063 cx,
1064 );
1065
1066 assert_eq!(
1067 buffer.text(),
1068 "
1069 fn a() {
1070 c
1071 .f
1072 .g();
1073 d
1074 .f
1075 .g();
1076 }
1077 "
1078 .unindent()
1079 );
1080 buffer
1081 });
1082
1083 cx.add_model(|cx| {
1084 let mut buffer = Buffer::new(
1085 0,
1086 cx.model_id() as u64,
1087 "
1088 fn a() {
1089 b();
1090 |
1091 "
1092 .replace("|", "") // marker to preserve trailing whitespace
1093 .unindent(),
1094 )
1095 .with_language(Arc::new(rust_lang()), cx);
1096
1097 // Insert a closing brace. It is outdented.
1098 buffer.edit_via_marked_text(
1099 &"
1100 fn a() {
1101 b();
1102 «}»
1103 "
1104 .unindent(),
1105 Some(AutoindentMode::EachLine),
1106 cx,
1107 );
1108 assert_eq!(
1109 buffer.text(),
1110 "
1111 fn a() {
1112 b();
1113 }
1114 "
1115 .unindent()
1116 );
1117
1118 // Manually edit the leading whitespace. The edit is preserved.
1119 buffer.edit_via_marked_text(
1120 &"
1121 fn a() {
1122 b();
1123 « »}
1124 "
1125 .unindent(),
1126 Some(AutoindentMode::EachLine),
1127 cx,
1128 );
1129 assert_eq!(
1130 buffer.text(),
1131 "
1132 fn a() {
1133 b();
1134 }
1135 "
1136 .unindent()
1137 );
1138 buffer
1139 });
1140}
1141
1142#[gpui::test]
1143fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
1144 init_settings(cx, |_| {});
1145
1146 cx.add_model(|cx| {
1147 let mut buffer = Buffer::new(
1148 0,
1149 cx.model_id() as u64,
1150 "
1151 fn a() {
1152 i
1153 }
1154 "
1155 .unindent(),
1156 )
1157 .with_language(Arc::new(rust_lang()), cx);
1158
1159 // Regression test: line does not get outdented due to syntax error
1160 buffer.edit_via_marked_text(
1161 &"
1162 fn a() {
1163 i«f let Some(x) = y»
1164 }
1165 "
1166 .unindent(),
1167 Some(AutoindentMode::EachLine),
1168 cx,
1169 );
1170 assert_eq!(
1171 buffer.text(),
1172 "
1173 fn a() {
1174 if let Some(x) = y
1175 }
1176 "
1177 .unindent()
1178 );
1179
1180 buffer.edit_via_marked_text(
1181 &"
1182 fn a() {
1183 if let Some(x) = y« {»
1184 }
1185 "
1186 .unindent(),
1187 Some(AutoindentMode::EachLine),
1188 cx,
1189 );
1190 assert_eq!(
1191 buffer.text(),
1192 "
1193 fn a() {
1194 if let Some(x) = y {
1195 }
1196 "
1197 .unindent()
1198 );
1199
1200 buffer
1201 });
1202}
1203
1204#[gpui::test]
1205fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
1206 init_settings(cx, |_| {});
1207
1208 cx.add_model(|cx| {
1209 let mut buffer = Buffer::new(
1210 0,
1211 cx.model_id() as u64,
1212 "
1213 fn a() {}
1214 "
1215 .unindent(),
1216 )
1217 .with_language(Arc::new(rust_lang()), cx);
1218
1219 buffer.edit_via_marked_text(
1220 &"
1221 fn a(«
1222 b») {}
1223 "
1224 .unindent(),
1225 Some(AutoindentMode::EachLine),
1226 cx,
1227 );
1228 assert_eq!(
1229 buffer.text(),
1230 "
1231 fn a(
1232 b) {}
1233 "
1234 .unindent()
1235 );
1236
1237 // The indentation suggestion changed because `@end` node (a close paren)
1238 // is now at the beginning of the line.
1239 buffer.edit_via_marked_text(
1240 &"
1241 fn a(
1242 ˇ) {}
1243 "
1244 .unindent(),
1245 Some(AutoindentMode::EachLine),
1246 cx,
1247 );
1248 assert_eq!(
1249 buffer.text(),
1250 "
1251 fn a(
1252 ) {}
1253 "
1254 .unindent()
1255 );
1256
1257 buffer
1258 });
1259}
1260
1261#[gpui::test]
1262fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
1263 init_settings(cx, |_| {});
1264
1265 cx.add_model(|cx| {
1266 let text = "a\nb";
1267 let mut buffer =
1268 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
1269 buffer.edit(
1270 [(0..1, "\n"), (2..3, "\n")],
1271 Some(AutoindentMode::EachLine),
1272 cx,
1273 );
1274 assert_eq!(buffer.text(), "\n\n\n");
1275 buffer
1276 });
1277}
1278
1279#[gpui::test]
1280fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
1281 init_settings(cx, |_| {});
1282
1283 cx.add_model(|cx| {
1284 let text = "
1285 const a: usize = 1;
1286 fn b() {
1287 if c {
1288 let d = 2;
1289 }
1290 }
1291 "
1292 .unindent();
1293
1294 let mut buffer =
1295 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
1296 buffer.edit(
1297 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
1298 Some(AutoindentMode::EachLine),
1299 cx,
1300 );
1301 assert_eq!(
1302 buffer.text(),
1303 "
1304 const a: usize = 1;
1305 fn b() {
1306 if c {
1307 e(
1308 f()
1309 );
1310 let d = 2;
1311 }
1312 }
1313 "
1314 .unindent()
1315 );
1316
1317 buffer
1318 });
1319}
1320
1321#[gpui::test]
1322fn test_autoindent_block_mode(cx: &mut AppContext) {
1323 init_settings(cx, |_| {});
1324
1325 cx.add_model(|cx| {
1326 let text = r#"
1327 fn a() {
1328 b();
1329 }
1330 "#
1331 .unindent();
1332 let mut buffer =
1333 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
1334
1335 // When this text was copied, both of the quotation marks were at the same
1336 // indent level, but the indentation of the first line was not included in
1337 // the copied text. This information is retained in the
1338 // 'original_indent_columns' vector.
1339 let original_indent_columns = vec![4];
1340 let inserted_text = r#"
1341 "
1342 c
1343 d
1344 e
1345 "
1346 "#
1347 .unindent();
1348
1349 // Insert the block at column zero. The entire block is indented
1350 // so that the first line matches the previous line's indentation.
1351 buffer.edit(
1352 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
1353 Some(AutoindentMode::Block {
1354 original_indent_columns: original_indent_columns.clone(),
1355 }),
1356 cx,
1357 );
1358 assert_eq!(
1359 buffer.text(),
1360 r#"
1361 fn a() {
1362 b();
1363 "
1364 c
1365 d
1366 e
1367 "
1368 }
1369 "#
1370 .unindent()
1371 );
1372
1373 // Grouping is disabled in tests, so we need 2 undos
1374 buffer.undo(cx); // Undo the auto-indent
1375 buffer.undo(cx); // Undo the original edit
1376
1377 // Insert the block at a deeper indent level. The entire block is outdented.
1378 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
1379 buffer.edit(
1380 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
1381 Some(AutoindentMode::Block {
1382 original_indent_columns: original_indent_columns.clone(),
1383 }),
1384 cx,
1385 );
1386 assert_eq!(
1387 buffer.text(),
1388 r#"
1389 fn a() {
1390 b();
1391 "
1392 c
1393 d
1394 e
1395 "
1396 }
1397 "#
1398 .unindent()
1399 );
1400
1401 buffer
1402 });
1403}
1404
1405#[gpui::test]
1406fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
1407 init_settings(cx, |_| {});
1408
1409 cx.add_model(|cx| {
1410 let text = r#"
1411 fn a() {
1412 if b() {
1413
1414 }
1415 }
1416 "#
1417 .unindent();
1418 let mut buffer =
1419 Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
1420
1421 // The original indent columns are not known, so this text is
1422 // auto-indented in a block as if the first line was copied in
1423 // its entirety.
1424 let original_indent_columns = Vec::new();
1425 let inserted_text = " c\n .d()\n .e();";
1426
1427 // Insert the block at column zero. The entire block is indented
1428 // so that the first line matches the previous line's indentation.
1429 buffer.edit(
1430 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1431 Some(AutoindentMode::Block {
1432 original_indent_columns: original_indent_columns.clone(),
1433 }),
1434 cx,
1435 );
1436 assert_eq!(
1437 buffer.text(),
1438 r#"
1439 fn a() {
1440 if b() {
1441 c
1442 .d()
1443 .e();
1444 }
1445 }
1446 "#
1447 .unindent()
1448 );
1449
1450 // Grouping is disabled in tests, so we need 2 undos
1451 buffer.undo(cx); // Undo the auto-indent
1452 buffer.undo(cx); // Undo the original edit
1453
1454 // Insert the block at a deeper indent level. The entire block is outdented.
1455 buffer.edit(
1456 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
1457 None,
1458 cx,
1459 );
1460 buffer.edit(
1461 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
1462 Some(AutoindentMode::Block {
1463 original_indent_columns: Vec::new(),
1464 }),
1465 cx,
1466 );
1467 assert_eq!(
1468 buffer.text(),
1469 r#"
1470 fn a() {
1471 if b() {
1472 c
1473 .d()
1474 .e();
1475 }
1476 }
1477 "#
1478 .unindent()
1479 );
1480
1481 buffer
1482 });
1483}
1484
1485#[gpui::test]
1486fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
1487 init_settings(cx, |_| {});
1488
1489 cx.add_model(|cx| {
1490 let text = "
1491 * one
1492 - a
1493 - b
1494 * two
1495 "
1496 .unindent();
1497
1498 let mut buffer = Buffer::new(0, cx.model_id() as u64, text).with_language(
1499 Arc::new(Language::new(
1500 LanguageConfig {
1501 name: "Markdown".into(),
1502 auto_indent_using_last_non_empty_line: false,
1503 ..Default::default()
1504 },
1505 Some(tree_sitter_json::language()),
1506 )),
1507 cx,
1508 );
1509 buffer.edit(
1510 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
1511 Some(AutoindentMode::EachLine),
1512 cx,
1513 );
1514 assert_eq!(
1515 buffer.text(),
1516 "
1517 * one
1518 - a
1519 - b
1520
1521 * two
1522 "
1523 .unindent()
1524 );
1525 buffer
1526 });
1527}
1528
1529#[gpui::test]
1530fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
1531 init_settings(cx, |settings| {
1532 settings.languages.extend([
1533 (
1534 "HTML".into(),
1535 LanguageSettingsContent {
1536 tab_size: Some(2.try_into().unwrap()),
1537 ..Default::default()
1538 },
1539 ),
1540 (
1541 "JavaScript".into(),
1542 LanguageSettingsContent {
1543 tab_size: Some(8.try_into().unwrap()),
1544 ..Default::default()
1545 },
1546 ),
1547 ])
1548 });
1549
1550 let html_language = Arc::new(html_lang());
1551
1552 let javascript_language = Arc::new(javascript_lang());
1553
1554 let language_registry = Arc::new(LanguageRegistry::test());
1555 language_registry.add(html_language.clone());
1556 language_registry.add(javascript_language.clone());
1557
1558 cx.add_model(|cx| {
1559 let (text, ranges) = marked_text_ranges(
1560 &"
1561 <div>ˇ
1562 </div>
1563 <script>
1564 init({ˇ
1565 })
1566 </script>
1567 <span>ˇ
1568 </span>
1569 "
1570 .unindent(),
1571 false,
1572 );
1573
1574 let mut buffer = Buffer::new(0, cx.model_id() as u64, text);
1575 buffer.set_language_registry(language_registry);
1576 buffer.set_language(Some(html_language), cx);
1577 buffer.edit(
1578 ranges.into_iter().map(|range| (range, "\na")),
1579 Some(AutoindentMode::EachLine),
1580 cx,
1581 );
1582 assert_eq!(
1583 buffer.text(),
1584 "
1585 <div>
1586 a
1587 </div>
1588 <script>
1589 init({
1590 a
1591 })
1592 </script>
1593 <span>
1594 a
1595 </span>
1596 "
1597 .unindent()
1598 );
1599 buffer
1600 });
1601}
1602
1603#[gpui::test]
1604fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
1605 init_settings(cx, |settings| {
1606 settings.defaults.tab_size = Some(2.try_into().unwrap());
1607 });
1608
1609 cx.add_model(|cx| {
1610 let mut buffer =
1611 Buffer::new(0, cx.model_id() as u64, "").with_language(Arc::new(ruby_lang()), cx);
1612
1613 let text = r#"
1614 class C
1615 def a(b, c)
1616 puts b
1617 puts c
1618 rescue
1619 puts "errored"
1620 exit 1
1621 end
1622 end
1623 "#
1624 .unindent();
1625
1626 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
1627
1628 assert_eq!(
1629 buffer.text(),
1630 r#"
1631 class C
1632 def a(b, c)
1633 puts b
1634 puts c
1635 rescue
1636 puts "errored"
1637 exit 1
1638 end
1639 end
1640 "#
1641 .unindent()
1642 );
1643
1644 buffer
1645 });
1646}
1647
1648#[gpui::test]
1649fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
1650 init_settings(cx, |_| {});
1651
1652 cx.add_model(|cx| {
1653 let language = Language::new(
1654 LanguageConfig {
1655 name: "JavaScript".into(),
1656 line_comment: Some("// ".into()),
1657 brackets: BracketPairConfig {
1658 pairs: vec![
1659 BracketPair {
1660 start: "{".into(),
1661 end: "}".into(),
1662 close: true,
1663 newline: false,
1664 },
1665 BracketPair {
1666 start: "'".into(),
1667 end: "'".into(),
1668 close: true,
1669 newline: false,
1670 },
1671 ],
1672 disabled_scopes_by_bracket_ix: vec![
1673 Vec::new(), //
1674 vec!["string".into()],
1675 ],
1676 },
1677 overrides: [(
1678 "element".into(),
1679 LanguageConfigOverride {
1680 line_comment: Override::Remove { remove: true },
1681 block_comment: Override::Set(("{/*".into(), "*/}".into())),
1682 ..Default::default()
1683 },
1684 )]
1685 .into_iter()
1686 .collect(),
1687 ..Default::default()
1688 },
1689 Some(tree_sitter_typescript::language_tsx()),
1690 )
1691 .with_override_query(
1692 r#"
1693 (jsx_element) @element
1694 (string) @string
1695 [
1696 (jsx_opening_element)
1697 (jsx_closing_element)
1698 (jsx_expression)
1699 ] @default
1700 "#,
1701 )
1702 .unwrap();
1703
1704 let text = r#"
1705 a["b"] = <C d="e">
1706 <F></F>
1707 { g() }
1708 </C>;
1709 "#
1710 .unindent();
1711
1712 let buffer =
1713 Buffer::new(0, cx.model_id() as u64, &text).with_language(Arc::new(language), cx);
1714 let snapshot = buffer.snapshot();
1715
1716 let config = snapshot.language_scope_at(0).unwrap();
1717 assert_eq!(config.line_comment_prefix().unwrap().as_ref(), "// ");
1718 // Both bracket pairs are enabled
1719 assert_eq!(
1720 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1721 &[true, true]
1722 );
1723
1724 let string_config = snapshot
1725 .language_scope_at(text.find("b\"").unwrap())
1726 .unwrap();
1727 assert_eq!(string_config.line_comment_prefix().unwrap().as_ref(), "// ");
1728 // Second bracket pair is disabled
1729 assert_eq!(
1730 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1731 &[true, false]
1732 );
1733
1734 // In between JSX tags: use the `element` override.
1735 let element_config = snapshot
1736 .language_scope_at(text.find("<F>").unwrap())
1737 .unwrap();
1738 assert_eq!(element_config.line_comment_prefix(), None);
1739 assert_eq!(
1740 element_config.block_comment_delimiters(),
1741 Some((&"{/*".into(), &"*/}".into()))
1742 );
1743 assert_eq!(
1744 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1745 &[true, true]
1746 );
1747
1748 // Within a JSX tag: use the default config.
1749 let tag_config = snapshot
1750 .language_scope_at(text.find(" d=").unwrap() + 1)
1751 .unwrap();
1752 assert_eq!(tag_config.line_comment_prefix().unwrap().as_ref(), "// ");
1753 assert_eq!(
1754 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1755 &[true, true]
1756 );
1757
1758 // In a JSX expression: use the default config.
1759 let expression_in_element_config = snapshot
1760 .language_scope_at(text.find("{").unwrap() + 1)
1761 .unwrap();
1762 assert_eq!(
1763 expression_in_element_config
1764 .line_comment_prefix()
1765 .unwrap()
1766 .as_ref(),
1767 "// "
1768 );
1769 assert_eq!(
1770 expression_in_element_config
1771 .brackets()
1772 .map(|e| e.1)
1773 .collect::<Vec<_>>(),
1774 &[true, true]
1775 );
1776
1777 buffer
1778 });
1779}
1780
1781#[gpui::test]
1782fn test_language_scope_at_with_rust(cx: &mut AppContext) {
1783 init_settings(cx, |_| {});
1784
1785 cx.add_model(|cx| {
1786 let language = Language::new(
1787 LanguageConfig {
1788 name: "Rust".into(),
1789 brackets: BracketPairConfig {
1790 pairs: vec![
1791 BracketPair {
1792 start: "{".into(),
1793 end: "}".into(),
1794 close: true,
1795 newline: false,
1796 },
1797 BracketPair {
1798 start: "'".into(),
1799 end: "'".into(),
1800 close: true,
1801 newline: false,
1802 },
1803 ],
1804 disabled_scopes_by_bracket_ix: vec![
1805 Vec::new(), //
1806 vec!["string".into()],
1807 ],
1808 },
1809 ..Default::default()
1810 },
1811 Some(tree_sitter_rust::language()),
1812 )
1813 .with_override_query(
1814 r#"
1815 (string_literal) @string
1816 "#,
1817 )
1818 .unwrap();
1819
1820 let text = r#"
1821 const S: &'static str = "hello";
1822 "#
1823 .unindent();
1824
1825 let buffer = Buffer::new(0, cx.model_id() as u64, text.clone())
1826 .with_language(Arc::new(language), cx);
1827 let snapshot = buffer.snapshot();
1828
1829 // By default, all brackets are enabled
1830 let config = snapshot.language_scope_at(0).unwrap();
1831 assert_eq!(
1832 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1833 &[true, true]
1834 );
1835
1836 // Within a string, the quotation brackets are disabled.
1837 let string_config = snapshot
1838 .language_scope_at(text.find("ello").unwrap())
1839 .unwrap();
1840 assert_eq!(
1841 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
1842 &[true, false]
1843 );
1844
1845 buffer
1846 });
1847}
1848
1849#[gpui::test]
1850fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
1851 init_settings(cx, |_| {});
1852
1853 cx.add_model(|cx| {
1854 let text = r#"
1855 <ol>
1856 <% people.each do |person| %>
1857 <li>
1858 <%= person.name %>
1859 </li>
1860 <% end %>
1861 </ol>
1862 "#
1863 .unindent();
1864
1865 let language_registry = Arc::new(LanguageRegistry::test());
1866 language_registry.add(Arc::new(ruby_lang()));
1867 language_registry.add(Arc::new(html_lang()));
1868 language_registry.add(Arc::new(erb_lang()));
1869
1870 let mut buffer = Buffer::new(0, cx.model_id() as u64, text);
1871 buffer.set_language_registry(language_registry.clone());
1872 buffer.set_language(
1873 language_registry
1874 .language_for_name("ERB")
1875 .now_or_never()
1876 .unwrap()
1877 .ok(),
1878 cx,
1879 );
1880
1881 let snapshot = buffer.snapshot();
1882 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
1883 assert_eq!(html_config.line_comment_prefix(), None);
1884 assert_eq!(
1885 html_config.block_comment_delimiters(),
1886 Some((&"<!--".into(), &"-->".into()))
1887 );
1888
1889 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
1890 assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# ");
1891 assert_eq!(ruby_config.block_comment_delimiters(), None);
1892
1893 buffer
1894 });
1895}
1896
1897#[gpui::test]
1898fn test_serialization(cx: &mut gpui::AppContext) {
1899 let mut now = Instant::now();
1900
1901 let buffer1 = cx.add_model(|cx| {
1902 let mut buffer = Buffer::new(0, cx.model_id() as u64, "abc");
1903 buffer.edit([(3..3, "D")], None, cx);
1904
1905 now += Duration::from_secs(1);
1906 buffer.start_transaction_at(now);
1907 buffer.edit([(4..4, "E")], None, cx);
1908 buffer.end_transaction_at(now, cx);
1909 assert_eq!(buffer.text(), "abcDE");
1910
1911 buffer.undo(cx);
1912 assert_eq!(buffer.text(), "abcD");
1913
1914 buffer.edit([(4..4, "F")], None, cx);
1915 assert_eq!(buffer.text(), "abcDF");
1916 buffer
1917 });
1918 assert_eq!(buffer1.read(cx).text(), "abcDF");
1919
1920 let state = buffer1.read(cx).to_proto();
1921 let ops = cx
1922 .background()
1923 .block(buffer1.read(cx).serialize_ops(None, cx));
1924 let buffer2 = cx.add_model(|cx| {
1925 let mut buffer = Buffer::from_proto(1, state, None).unwrap();
1926 buffer
1927 .apply_ops(
1928 ops.into_iter()
1929 .map(|op| proto::deserialize_operation(op).unwrap()),
1930 cx,
1931 )
1932 .unwrap();
1933 buffer
1934 });
1935 assert_eq!(buffer2.read(cx).text(), "abcDF");
1936}
1937
1938#[gpui::test(iterations = 100)]
1939fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
1940 let min_peers = env::var("MIN_PEERS")
1941 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
1942 .unwrap_or(1);
1943 let max_peers = env::var("MAX_PEERS")
1944 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
1945 .unwrap_or(5);
1946 let operations = env::var("OPERATIONS")
1947 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1948 .unwrap_or(10);
1949
1950 let base_text_len = rng.gen_range(0..10);
1951 let base_text = RandomCharIter::new(&mut rng)
1952 .take(base_text_len)
1953 .collect::<String>();
1954 let mut replica_ids = Vec::new();
1955 let mut buffers = Vec::new();
1956 let network = Rc::new(RefCell::new(Network::new(rng.clone())));
1957 let base_buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, base_text.as_str()));
1958
1959 for i in 0..rng.gen_range(min_peers..=max_peers) {
1960 let buffer = cx.add_model(|cx| {
1961 let state = base_buffer.read(cx).to_proto();
1962 let ops = cx
1963 .background()
1964 .block(base_buffer.read(cx).serialize_ops(None, cx));
1965 let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
1966 buffer
1967 .apply_ops(
1968 ops.into_iter()
1969 .map(|op| proto::deserialize_operation(op).unwrap()),
1970 cx,
1971 )
1972 .unwrap();
1973 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
1974 let network = network.clone();
1975 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
1976 if let Event::Operation(op) = event {
1977 network
1978 .borrow_mut()
1979 .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
1980 }
1981 })
1982 .detach();
1983 buffer
1984 });
1985 buffers.push(buffer);
1986 replica_ids.push(i as ReplicaId);
1987 network.borrow_mut().add_peer(i as ReplicaId);
1988 log::info!("Adding initial peer with replica id {}", i);
1989 }
1990
1991 log::info!("initial text: {:?}", base_text);
1992
1993 let mut now = Instant::now();
1994 let mut mutation_count = operations;
1995 let mut next_diagnostic_id = 0;
1996 let mut active_selections = BTreeMap::default();
1997 loop {
1998 let replica_index = rng.gen_range(0..replica_ids.len());
1999 let replica_id = replica_ids[replica_index];
2000 let buffer = &mut buffers[replica_index];
2001 let mut new_buffer = None;
2002 match rng.gen_range(0..100) {
2003 0..=29 if mutation_count != 0 => {
2004 buffer.update(cx, |buffer, cx| {
2005 buffer.start_transaction_at(now);
2006 buffer.randomly_edit(&mut rng, 5, cx);
2007 buffer.end_transaction_at(now, cx);
2008 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2009 });
2010 mutation_count -= 1;
2011 }
2012 30..=39 if mutation_count != 0 => {
2013 buffer.update(cx, |buffer, cx| {
2014 if rng.gen_bool(0.2) {
2015 log::info!("peer {} clearing active selections", replica_id);
2016 active_selections.remove(&replica_id);
2017 buffer.remove_active_selections(cx);
2018 } else {
2019 let mut selections = Vec::new();
2020 for id in 0..rng.gen_range(1..=5) {
2021 let range = buffer.random_byte_range(0, &mut rng);
2022 selections.push(Selection {
2023 id,
2024 start: buffer.anchor_before(range.start),
2025 end: buffer.anchor_before(range.end),
2026 reversed: false,
2027 goal: SelectionGoal::None,
2028 });
2029 }
2030 let selections: Arc<[Selection<Anchor>]> = selections.into();
2031 log::info!(
2032 "peer {} setting active selections: {:?}",
2033 replica_id,
2034 selections
2035 );
2036 active_selections.insert(replica_id, selections.clone());
2037 buffer.set_active_selections(selections, false, Default::default(), cx);
2038 }
2039 });
2040 mutation_count -= 1;
2041 }
2042 40..=49 if mutation_count != 0 && replica_id == 0 => {
2043 let entry_count = rng.gen_range(1..=5);
2044 buffer.update(cx, |buffer, cx| {
2045 let diagnostics = DiagnosticSet::new(
2046 (0..entry_count).map(|_| {
2047 let range = buffer.random_byte_range(0, &mut rng);
2048 let range = range.to_point_utf16(buffer);
2049 let range = range.start..range.end;
2050 DiagnosticEntry {
2051 range,
2052 diagnostic: Diagnostic {
2053 message: post_inc(&mut next_diagnostic_id).to_string(),
2054 ..Default::default()
2055 },
2056 }
2057 }),
2058 buffer,
2059 );
2060 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2061 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2062 });
2063 mutation_count -= 1;
2064 }
2065 50..=59 if replica_ids.len() < max_peers => {
2066 let old_buffer_state = buffer.read(cx).to_proto();
2067 let old_buffer_ops = cx
2068 .background()
2069 .block(buffer.read(cx).serialize_ops(None, cx));
2070 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2071 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2072 .choose(&mut rng)
2073 .unwrap();
2074 log::info!(
2075 "Adding new replica {} (replicating from {})",
2076 new_replica_id,
2077 replica_id
2078 );
2079 new_buffer = Some(cx.add_model(|cx| {
2080 let mut new_buffer =
2081 Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
2082 new_buffer
2083 .apply_ops(
2084 old_buffer_ops
2085 .into_iter()
2086 .map(|op| deserialize_operation(op).unwrap()),
2087 cx,
2088 )
2089 .unwrap();
2090 log::info!(
2091 "New replica {} text: {:?}",
2092 new_buffer.replica_id(),
2093 new_buffer.text()
2094 );
2095 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2096 let network = network.clone();
2097 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2098 if let Event::Operation(op) = event {
2099 network.borrow_mut().broadcast(
2100 buffer.replica_id(),
2101 vec![proto::serialize_operation(op)],
2102 );
2103 }
2104 })
2105 .detach();
2106 new_buffer
2107 }));
2108 network.borrow_mut().replicate(replica_id, new_replica_id);
2109
2110 if new_replica_id as usize == replica_ids.len() {
2111 replica_ids.push(new_replica_id);
2112 } else {
2113 let new_buffer = new_buffer.take().unwrap();
2114 while network.borrow().has_unreceived(new_replica_id) {
2115 let ops = network
2116 .borrow_mut()
2117 .receive(new_replica_id)
2118 .into_iter()
2119 .map(|op| proto::deserialize_operation(op).unwrap());
2120 if ops.len() > 0 {
2121 log::info!(
2122 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2123 new_replica_id,
2124 buffer.read(cx).version(),
2125 ops.len(),
2126 ops
2127 );
2128 new_buffer.update(cx, |new_buffer, cx| {
2129 new_buffer.apply_ops(ops, cx).unwrap();
2130 });
2131 }
2132 }
2133 buffers[new_replica_id as usize] = new_buffer;
2134 }
2135 }
2136 60..=69 if mutation_count != 0 => {
2137 buffer.update(cx, |buffer, cx| {
2138 buffer.randomly_undo_redo(&mut rng, cx);
2139 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2140 });
2141 mutation_count -= 1;
2142 }
2143 _ if network.borrow().has_unreceived(replica_id) => {
2144 let ops = network
2145 .borrow_mut()
2146 .receive(replica_id)
2147 .into_iter()
2148 .map(|op| proto::deserialize_operation(op).unwrap());
2149 if ops.len() > 0 {
2150 log::info!(
2151 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2152 replica_id,
2153 buffer.read(cx).version(),
2154 ops.len(),
2155 ops
2156 );
2157 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
2158 }
2159 }
2160 _ => {}
2161 }
2162
2163 now += Duration::from_millis(rng.gen_range(0..=200));
2164 buffers.extend(new_buffer);
2165
2166 for buffer in &buffers {
2167 buffer.read(cx).check_invariants();
2168 }
2169
2170 if mutation_count == 0 && network.borrow().is_idle() {
2171 break;
2172 }
2173 }
2174
2175 let first_buffer = buffers[0].read(cx).snapshot();
2176 for buffer in &buffers[1..] {
2177 let buffer = buffer.read(cx).snapshot();
2178 assert_eq!(
2179 buffer.version(),
2180 first_buffer.version(),
2181 "Replica {} version != Replica 0 version",
2182 buffer.replica_id()
2183 );
2184 assert_eq!(
2185 buffer.text(),
2186 first_buffer.text(),
2187 "Replica {} text != Replica 0 text",
2188 buffer.replica_id()
2189 );
2190 assert_eq!(
2191 buffer
2192 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
2193 .collect::<Vec<_>>(),
2194 first_buffer
2195 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
2196 .collect::<Vec<_>>(),
2197 "Replica {} diagnostics != Replica 0 diagnostics",
2198 buffer.replica_id()
2199 );
2200 }
2201
2202 for buffer in &buffers {
2203 let buffer = buffer.read(cx).snapshot();
2204 let actual_remote_selections = buffer
2205 .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
2206 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
2207 .collect::<Vec<_>>();
2208 let expected_remote_selections = active_selections
2209 .iter()
2210 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
2211 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
2212 .collect::<Vec<_>>();
2213 assert_eq!(
2214 actual_remote_selections,
2215 expected_remote_selections,
2216 "Replica {} remote selections != expected selections",
2217 buffer.replica_id()
2218 );
2219 }
2220}
2221
2222#[test]
2223fn test_contiguous_ranges() {
2224 assert_eq!(
2225 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
2226 &[1..4, 5..7, 9..13]
2227 );
2228
2229 // Respects the `max_len` parameter
2230 assert_eq!(
2231 contiguous_ranges(
2232 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
2233 3
2234 )
2235 .collect::<Vec<_>>(),
2236 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
2237 );
2238}
2239
2240#[gpui::test(iterations = 500)]
2241fn test_trailing_whitespace_ranges(mut rng: StdRng) {
2242 // Generate a random multi-line string containing
2243 // some lines with trailing whitespace.
2244 let mut text = String::new();
2245 for _ in 0..rng.gen_range(0..16) {
2246 for _ in 0..rng.gen_range(0..36) {
2247 text.push(match rng.gen_range(0..10) {
2248 0..=1 => ' ',
2249 3 => '\t',
2250 _ => rng.gen_range('a'..'z'),
2251 });
2252 }
2253 text.push('\n');
2254 }
2255
2256 match rng.gen_range(0..10) {
2257 // sometimes remove the last newline
2258 0..=1 => drop(text.pop()), //
2259
2260 // sometimes add extra newlines
2261 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
2262 _ => {}
2263 }
2264
2265 let rope = Rope::from(text.as_str());
2266 let actual_ranges = trailing_whitespace_ranges(&rope);
2267 let expected_ranges = TRAILING_WHITESPACE_REGEX
2268 .find_iter(&text)
2269 .map(|m| m.range())
2270 .collect::<Vec<_>>();
2271 assert_eq!(
2272 actual_ranges,
2273 expected_ranges,
2274 "wrong ranges for text lines:\n{:?}",
2275 text.split("\n").collect::<Vec<_>>()
2276 );
2277}
2278
2279fn ruby_lang() -> Language {
2280 Language::new(
2281 LanguageConfig {
2282 name: "Ruby".into(),
2283 path_suffixes: vec!["rb".to_string()],
2284 line_comment: Some("# ".into()),
2285 ..Default::default()
2286 },
2287 Some(tree_sitter_ruby::language()),
2288 )
2289 .with_indents_query(
2290 r#"
2291 (class "end" @end) @indent
2292 (method "end" @end) @indent
2293 (rescue) @outdent
2294 (then) @indent
2295 "#,
2296 )
2297 .unwrap()
2298}
2299
2300fn html_lang() -> Language {
2301 Language::new(
2302 LanguageConfig {
2303 name: "HTML".into(),
2304 block_comment: Some(("<!--".into(), "-->".into())),
2305 ..Default::default()
2306 },
2307 Some(tree_sitter_html::language()),
2308 )
2309 .with_indents_query(
2310 "
2311 (element
2312 (start_tag) @start
2313 (end_tag)? @end) @indent
2314 ",
2315 )
2316 .unwrap()
2317 .with_injection_query(
2318 r#"
2319 (script_element
2320 (raw_text) @content
2321 (#set! "language" "javascript"))
2322 "#,
2323 )
2324 .unwrap()
2325}
2326
2327fn erb_lang() -> Language {
2328 Language::new(
2329 LanguageConfig {
2330 name: "ERB".into(),
2331 path_suffixes: vec!["erb".to_string()],
2332 block_comment: Some(("<%#".into(), "%>".into())),
2333 ..Default::default()
2334 },
2335 Some(tree_sitter_embedded_template::language()),
2336 )
2337 .with_injection_query(
2338 r#"
2339 (
2340 (code) @content
2341 (#set! "language" "ruby")
2342 (#set! "combined")
2343 )
2344
2345 (
2346 (content) @content
2347 (#set! "language" "html")
2348 (#set! "combined")
2349 )
2350 "#,
2351 )
2352 .unwrap()
2353}
2354
2355fn rust_lang() -> Language {
2356 Language::new(
2357 LanguageConfig {
2358 name: "Rust".into(),
2359 path_suffixes: vec!["rs".to_string()],
2360 ..Default::default()
2361 },
2362 Some(tree_sitter_rust::language()),
2363 )
2364 .with_indents_query(
2365 r#"
2366 (call_expression) @indent
2367 (field_expression) @indent
2368 (_ "(" ")" @end) @indent
2369 (_ "{" "}" @end) @indent
2370 "#,
2371 )
2372 .unwrap()
2373 .with_brackets_query(
2374 r#"
2375 ("{" @open "}" @close)
2376 "#,
2377 )
2378 .unwrap()
2379 .with_outline_query(
2380 r#"
2381 (struct_item
2382 "struct" @context
2383 name: (_) @name) @item
2384 (enum_item
2385 "enum" @context
2386 name: (_) @name) @item
2387 (enum_variant
2388 name: (_) @name) @item
2389 (field_declaration
2390 name: (_) @name) @item
2391 (impl_item
2392 "impl" @context
2393 trait: (_)? @name
2394 "for"? @context
2395 type: (_) @name) @item
2396 (function_item
2397 "fn" @context
2398 name: (_) @name) @item
2399 (mod_item
2400 "mod" @context
2401 name: (_) @name) @item
2402 "#,
2403 )
2404 .unwrap()
2405}
2406
2407fn json_lang() -> Language {
2408 Language::new(
2409 LanguageConfig {
2410 name: "Json".into(),
2411 path_suffixes: vec!["js".to_string()],
2412 ..Default::default()
2413 },
2414 Some(tree_sitter_json::language()),
2415 )
2416}
2417
2418fn javascript_lang() -> Language {
2419 Language::new(
2420 LanguageConfig {
2421 name: "JavaScript".into(),
2422 ..Default::default()
2423 },
2424 Some(tree_sitter_typescript::language_tsx()),
2425 )
2426 .with_brackets_query(
2427 r#"
2428 ("{" @open "}" @close)
2429 ("(" @open ")" @close)
2430 "#,
2431 )
2432 .unwrap()
2433 .with_indents_query(
2434 r#"
2435 (object "}" @end) @indent
2436 "#,
2437 )
2438 .unwrap()
2439}
2440
2441fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
2442 buffer.read_with(cx, |buffer, _| {
2443 let snapshot = buffer.snapshot();
2444 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
2445 layers[0].node().to_sexp()
2446 })
2447}
2448
2449// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
2450fn assert_bracket_pairs(
2451 selection_text: &'static str,
2452 bracket_pair_texts: Vec<&'static str>,
2453 language: Language,
2454 cx: &mut AppContext,
2455) {
2456 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
2457 let buffer = cx.add_model(|cx| {
2458 Buffer::new(0, cx.model_id() as u64, expected_text.clone())
2459 .with_language(Arc::new(language), cx)
2460 });
2461 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
2462
2463 let selection_range = selection_ranges[0].clone();
2464
2465 let bracket_pairs = bracket_pair_texts
2466 .into_iter()
2467 .map(|pair_text| {
2468 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
2469 assert_eq!(bracket_text, expected_text);
2470 (ranges[0].clone(), ranges[1].clone())
2471 })
2472 .collect::<Vec<_>>();
2473
2474 assert_set_eq!(
2475 buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
2476 bracket_pairs
2477 );
2478}
2479
2480fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
2481 cx.set_global(SettingsStore::test(cx));
2482 crate::init(cx);
2483 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
2484 settings.update_user_settings::<AllLanguageSettings>(cx, f);
2485 });
2486}