1use super::*;
2use crate::language_settings::{
3 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
4};
5use crate::Buffer;
6use clock::ReplicaId;
7use collections::BTreeMap;
8use futures::FutureExt as _;
9use git::diff::assert_hunks;
10use gpui::{AppContext, BorrowAppContext, Model};
11use gpui::{Context, TestAppContext};
12use indoc::indoc;
13use proto::deserialize_operation;
14use rand::prelude::*;
15use regex::RegexBuilder;
16use settings::SettingsStore;
17use std::{
18 env,
19 ops::Range,
20 sync::LazyLock,
21 time::{Duration, Instant},
22};
23use text::network::Network;
24use text::{BufferId, LineEnding, LineIndent};
25use text::{Point, ToPoint};
26use unindent::Unindent as _;
27use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
28
29pub static TRAILING_WHITESPACE_REGEX: LazyLock<regex::Regex> = LazyLock::new(|| {
30 RegexBuilder::new(r"[ \t]+$")
31 .multi_line(true)
32 .build()
33 .expect("Failed to create TRAILING_WHITESPACE_REGEX")
34});
35
36#[cfg(test)]
37#[ctor::ctor]
38fn init_logger() {
39 if std::env::var("RUST_LOG").is_ok() {
40 env_logger::init();
41 }
42}
43
44#[gpui::test]
45fn test_line_endings(cx: &mut gpui::AppContext) {
46 init_settings(cx, |_| {});
47
48 cx.new_model(|cx| {
49 let mut buffer =
50 Buffer::local("one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
51 assert_eq!(buffer.text(), "one\ntwo\nthree");
52 assert_eq!(buffer.line_ending(), LineEnding::Windows);
53
54 buffer.check_invariants();
55 buffer.edit(
56 [(buffer.len()..buffer.len(), "\r\nfour")],
57 Some(AutoindentMode::EachLine),
58 cx,
59 );
60 buffer.edit([(0..0, "zero\r\n")], None, cx);
61 assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour");
62 assert_eq!(buffer.line_ending(), LineEnding::Windows);
63 buffer.check_invariants();
64
65 buffer
66 });
67}
68
69#[gpui::test]
70fn test_select_language(cx: &mut AppContext) {
71 init_settings(cx, |_| {});
72
73 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
74 registry.add(Arc::new(Language::new(
75 LanguageConfig {
76 name: LanguageName::new("Rust"),
77 matcher: LanguageMatcher {
78 path_suffixes: vec!["rs".to_string()],
79 ..Default::default()
80 },
81 ..Default::default()
82 },
83 Some(tree_sitter_rust::LANGUAGE.into()),
84 )));
85 registry.add(Arc::new(Language::new(
86 LanguageConfig {
87 name: LanguageName::new("Make"),
88 matcher: LanguageMatcher {
89 path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
90 ..Default::default()
91 },
92 ..Default::default()
93 },
94 Some(tree_sitter_rust::LANGUAGE.into()),
95 )));
96
97 // matching file extension
98 assert_eq!(
99 registry
100 .language_for_file(&file("src/lib.rs"), None, cx)
101 .map(|l| l.name()),
102 Some("Rust".into())
103 );
104 assert_eq!(
105 registry
106 .language_for_file(&file("src/lib.mk"), None, cx)
107 .map(|l| l.name()),
108 Some("Make".into())
109 );
110
111 // matching filename
112 assert_eq!(
113 registry
114 .language_for_file(&file("src/Makefile"), None, cx)
115 .map(|l| l.name()),
116 Some("Make".into())
117 );
118
119 // matching suffix that is not the full file extension or filename
120 assert_eq!(
121 registry
122 .language_for_file(&file("zed/cars"), None, cx)
123 .map(|l| l.name()),
124 None
125 );
126 assert_eq!(
127 registry
128 .language_for_file(&file("zed/a.cars"), None, cx)
129 .map(|l| l.name()),
130 None
131 );
132 assert_eq!(
133 registry
134 .language_for_file(&file("zed/sumk"), None, cx)
135 .map(|l| l.name()),
136 None
137 );
138}
139
140#[gpui::test(iterations = 10)]
141async fn test_first_line_pattern(cx: &mut TestAppContext) {
142 cx.update(|cx| init_settings(cx, |_| {}));
143
144 let languages = LanguageRegistry::test(cx.executor());
145 let languages = Arc::new(languages);
146
147 languages.register_test_language(LanguageConfig {
148 name: "JavaScript".into(),
149 matcher: LanguageMatcher {
150 path_suffixes: vec!["js".into()],
151 first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
152 },
153 ..Default::default()
154 });
155
156 assert!(cx
157 .read(|cx| languages.language_for_file(&file("the/script"), None, cx))
158 .is_none());
159 assert!(cx
160 .read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
161 .is_none());
162
163 assert_eq!(
164 cx.read(|cx| languages.language_for_file(
165 &file("the/script"),
166 Some(&"#!/bin/env node".into()),
167 cx
168 ))
169 .unwrap()
170 .name(),
171 "JavaScript".into()
172 );
173}
174
175#[gpui::test]
176async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) {
177 cx.update(|cx| {
178 init_settings(cx, |settings| {
179 settings.file_types.extend([
180 ("TypeScript".into(), vec!["js".into()]),
181 ("C++".into(), vec!["c".into()]),
182 (
183 "Dockerfile".into(),
184 vec!["Dockerfile".into(), "Dockerfile.*".into()],
185 ),
186 ]);
187 })
188 });
189
190 let languages = Arc::new(LanguageRegistry::test(cx.executor()));
191
192 for config in [
193 LanguageConfig {
194 name: "JavaScript".into(),
195 matcher: LanguageMatcher {
196 path_suffixes: vec!["js".to_string()],
197 ..Default::default()
198 },
199 ..Default::default()
200 },
201 LanguageConfig {
202 name: "TypeScript".into(),
203 matcher: LanguageMatcher {
204 path_suffixes: vec!["js".to_string()],
205 ..Default::default()
206 },
207 ..Default::default()
208 },
209 LanguageConfig {
210 name: "C++".into(),
211 matcher: LanguageMatcher {
212 path_suffixes: vec!["cpp".to_string()],
213 ..Default::default()
214 },
215 ..Default::default()
216 },
217 LanguageConfig {
218 name: "C".into(),
219 matcher: LanguageMatcher {
220 path_suffixes: vec!["c".to_string()],
221 ..Default::default()
222 },
223 ..Default::default()
224 },
225 LanguageConfig {
226 name: "Dockerfile".into(),
227 matcher: LanguageMatcher {
228 path_suffixes: vec!["Dockerfile".to_string()],
229 ..Default::default()
230 },
231 ..Default::default()
232 },
233 ] {
234 languages.add(Arc::new(Language::new(config, None)));
235 }
236
237 let language = cx
238 .read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
239 .unwrap();
240 assert_eq!(language.name(), "TypeScript".into());
241 let language = cx
242 .read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
243 .unwrap();
244 assert_eq!(language.name(), "C++".into());
245 let language = cx
246 .read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
247 .unwrap();
248 assert_eq!(language.name(), "Dockerfile".into());
249}
250
251fn file(path: &str) -> Arc<dyn File> {
252 Arc::new(TestFile {
253 path: Path::new(path).into(),
254 root_name: "zed".into(),
255 })
256}
257
258#[gpui::test]
259fn test_edit_events(cx: &mut gpui::AppContext) {
260 let mut now = Instant::now();
261 let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
262 let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
263
264 let buffer1 = cx.new_model(|cx| Buffer::local("abcdef", cx));
265 let buffer2 = cx.new_model(|cx| {
266 Buffer::remote(
267 BufferId::from(cx.entity_id().as_non_zero_u64()),
268 1,
269 Capability::ReadWrite,
270 "abcdef",
271 )
272 });
273 let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
274 buffer1.update(cx, {
275 let buffer1_ops = buffer1_ops.clone();
276 |buffer, cx| {
277 let buffer_1_events = buffer_1_events.clone();
278 cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
279 BufferEvent::Operation {
280 operation,
281 is_local: true,
282 } => buffer1_ops.lock().push(operation),
283 event => buffer_1_events.lock().push(event),
284 })
285 .detach();
286 let buffer_2_events = buffer_2_events.clone();
287 cx.subscribe(&buffer2, move |_, _, event, _| match event.clone() {
288 BufferEvent::Operation {
289 is_local: false, ..
290 } => {}
291 event => buffer_2_events.lock().push(event),
292 })
293 .detach();
294
295 // An edit emits an edited event, followed by a dirty changed event,
296 // since the buffer was previously in a clean state.
297 buffer.edit([(2..4, "XYZ")], None, cx);
298
299 // An empty transaction does not emit any events.
300 buffer.start_transaction();
301 buffer.end_transaction(cx);
302
303 // A transaction containing two edits emits one edited event.
304 now += Duration::from_secs(1);
305 buffer.start_transaction_at(now);
306 buffer.edit([(5..5, "u")], None, cx);
307 buffer.edit([(6..6, "w")], None, cx);
308 buffer.end_transaction_at(now, cx);
309
310 // Undoing a transaction emits one edited event.
311 buffer.undo(cx);
312 }
313 });
314
315 // Incorporating a set of remote ops emits a single edited event,
316 // followed by a dirty changed event.
317 buffer2.update(cx, |buffer, cx| {
318 buffer.apply_ops(buffer1_ops.lock().drain(..), cx);
319 });
320 assert_eq!(
321 mem::take(&mut *buffer_1_events.lock()),
322 vec![
323 BufferEvent::Edited,
324 BufferEvent::DirtyChanged,
325 BufferEvent::Edited,
326 BufferEvent::Edited,
327 ]
328 );
329 assert_eq!(
330 mem::take(&mut *buffer_2_events.lock()),
331 vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
332 );
333
334 buffer1.update(cx, |buffer, cx| {
335 // Undoing the first transaction emits edited event, followed by a
336 // dirty changed event, since the buffer is again in a clean state.
337 buffer.undo(cx);
338 });
339 // Incorporating the remote ops again emits a single edited event,
340 // followed by a dirty changed event.
341 buffer2.update(cx, |buffer, cx| {
342 buffer.apply_ops(buffer1_ops.lock().drain(..), cx);
343 });
344 assert_eq!(
345 mem::take(&mut *buffer_1_events.lock()),
346 vec![BufferEvent::Edited, BufferEvent::DirtyChanged,]
347 );
348 assert_eq!(
349 mem::take(&mut *buffer_2_events.lock()),
350 vec![BufferEvent::Edited, BufferEvent::DirtyChanged]
351 );
352}
353
354#[gpui::test]
355async fn test_apply_diff(cx: &mut TestAppContext) {
356 let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
357 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
358 let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
359
360 let text = "a\nccc\ndddd\nffffff\n";
361 let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
362 buffer.update(cx, |buffer, cx| {
363 buffer.apply_diff(diff, cx).unwrap();
364 assert_eq!(buffer.text(), text);
365 assert_eq!(anchor.to_point(buffer), Point::new(2, 3));
366 });
367
368 let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
369 let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
370 buffer.update(cx, |buffer, cx| {
371 buffer.apply_diff(diff, cx).unwrap();
372 assert_eq!(buffer.text(), text);
373 assert_eq!(anchor.to_point(buffer), Point::new(4, 4));
374 });
375}
376
377#[gpui::test(iterations = 10)]
378async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
379 let text = [
380 "zero", //
381 "one ", // 2 trailing spaces
382 "two", //
383 "three ", // 3 trailing spaces
384 "four", //
385 "five ", // 4 trailing spaces
386 ]
387 .join("\n");
388
389 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
390
391 // Spawn a task to format the buffer's whitespace.
392 // Pause so that the formatting task starts running.
393 let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
394 smol::future::yield_now().await;
395
396 // Edit the buffer while the normalization task is running.
397 let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());
398 buffer.update(cx, |buffer, cx| {
399 buffer.edit(
400 [
401 (Point::new(0, 1)..Point::new(0, 1), "EE"),
402 (Point::new(3, 5)..Point::new(3, 5), "EEE"),
403 ],
404 None,
405 cx,
406 );
407 });
408
409 let format_diff = format.await;
410 buffer.update(cx, |buffer, cx| {
411 let version_before_format = format_diff.base_version.clone();
412 buffer.apply_diff(format_diff, cx);
413
414 // The outcome depends on the order of concurrent tasks.
415 //
416 // If the edit occurred while searching for trailing whitespace ranges,
417 // then the trailing whitespace region touched by the edit is left intact.
418 if version_before_format == version_before_edit {
419 assert_eq!(
420 buffer.text(),
421 [
422 "zEEero", //
423 "one", //
424 "two", //
425 "threeEEE ", //
426 "four", //
427 "five", //
428 ]
429 .join("\n")
430 );
431 }
432 // Otherwise, all trailing whitespace is removed.
433 else {
434 assert_eq!(
435 buffer.text(),
436 [
437 "zEEero", //
438 "one", //
439 "two", //
440 "threeEEE", //
441 "four", //
442 "five", //
443 ]
444 .join("\n")
445 );
446 }
447 });
448}
449
450#[gpui::test]
451async fn test_reparse(cx: &mut gpui::TestAppContext) {
452 let text = "fn a() {}";
453 let buffer =
454 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
455
456 // Wait for the initial text to parse
457 cx.executor().run_until_parked();
458 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
459 assert_eq!(
460 get_tree_sexp(&buffer, cx),
461 concat!(
462 "(source_file (function_item name: (identifier) ",
463 "parameters: (parameters) ",
464 "body: (block)))"
465 )
466 );
467
468 buffer.update(cx, |buffer, _| {
469 buffer.set_sync_parse_timeout(Duration::ZERO)
470 });
471
472 // Perform some edits (add parameter and variable reference)
473 // Parsing doesn't begin until the transaction is complete
474 buffer.update(cx, |buf, cx| {
475 buf.start_transaction();
476
477 let offset = buf.text().find(')').unwrap();
478 buf.edit([(offset..offset, "b: C")], None, cx);
479 assert!(!buf.is_parsing());
480
481 let offset = buf.text().find('}').unwrap();
482 buf.edit([(offset..offset, " d; ")], None, cx);
483 assert!(!buf.is_parsing());
484
485 buf.end_transaction(cx);
486 assert_eq!(buf.text(), "fn a(b: C) { d; }");
487 assert!(buf.is_parsing());
488 });
489 cx.executor().run_until_parked();
490 assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
491 assert_eq!(
492 get_tree_sexp(&buffer, cx),
493 concat!(
494 "(source_file (function_item name: (identifier) ",
495 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
496 "body: (block (expression_statement (identifier)))))"
497 )
498 );
499
500 // Perform a series of edits without waiting for the current parse to complete:
501 // * turn identifier into a field expression
502 // * turn field expression into a method call
503 // * add a turbofish to the method call
504 buffer.update(cx, |buf, cx| {
505 let offset = buf.text().find(';').unwrap();
506 buf.edit([(offset..offset, ".e")], None, cx);
507 assert_eq!(buf.text(), "fn a(b: C) { d.e; }");
508 assert!(buf.is_parsing());
509 });
510 buffer.update(cx, |buf, cx| {
511 let offset = buf.text().find(';').unwrap();
512 buf.edit([(offset..offset, "(f)")], None, cx);
513 assert_eq!(buf.text(), "fn a(b: C) { d.e(f); }");
514 assert!(buf.is_parsing());
515 });
516 buffer.update(cx, |buf, cx| {
517 let offset = buf.text().find("(f)").unwrap();
518 buf.edit([(offset..offset, "::<G>")], None, cx);
519 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
520 assert!(buf.is_parsing());
521 });
522 cx.executor().run_until_parked();
523 assert_eq!(
524 get_tree_sexp(&buffer, cx),
525 concat!(
526 "(source_file (function_item name: (identifier) ",
527 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
528 "body: (block (expression_statement (call_expression ",
529 "function: (generic_function ",
530 "function: (field_expression value: (identifier) field: (field_identifier)) ",
531 "type_arguments: (type_arguments (type_identifier))) ",
532 "arguments: (arguments (identifier)))))))",
533 )
534 );
535
536 buffer.update(cx, |buf, cx| {
537 buf.undo(cx);
538 buf.undo(cx);
539 buf.undo(cx);
540 buf.undo(cx);
541 assert_eq!(buf.text(), "fn a() {}");
542 assert!(buf.is_parsing());
543 });
544
545 cx.executor().run_until_parked();
546 assert_eq!(
547 get_tree_sexp(&buffer, cx),
548 concat!(
549 "(source_file (function_item name: (identifier) ",
550 "parameters: (parameters) ",
551 "body: (block)))"
552 )
553 );
554
555 buffer.update(cx, |buf, cx| {
556 buf.redo(cx);
557 buf.redo(cx);
558 buf.redo(cx);
559 buf.redo(cx);
560 assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
561 assert!(buf.is_parsing());
562 });
563 cx.executor().run_until_parked();
564 assert_eq!(
565 get_tree_sexp(&buffer, cx),
566 concat!(
567 "(source_file (function_item name: (identifier) ",
568 "parameters: (parameters (parameter pattern: (identifier) type: (type_identifier))) ",
569 "body: (block (expression_statement (call_expression ",
570 "function: (generic_function ",
571 "function: (field_expression value: (identifier) field: (field_identifier)) ",
572 "type_arguments: (type_arguments (type_identifier))) ",
573 "arguments: (arguments (identifier)))))))",
574 )
575 );
576}
577
578#[gpui::test]
579async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
580 let buffer = cx.new_model(|cx| {
581 let mut buffer = Buffer::local("{}", cx).with_language(Arc::new(rust_lang()), cx);
582 buffer.set_sync_parse_timeout(Duration::ZERO);
583 buffer
584 });
585
586 // Wait for the initial text to parse
587 cx.executor().run_until_parked();
588 assert_eq!(
589 get_tree_sexp(&buffer, cx),
590 "(source_file (expression_statement (block)))"
591 );
592
593 buffer.update(cx, |buffer, cx| {
594 buffer.set_language(Some(Arc::new(json_lang())), cx)
595 });
596 cx.executor().run_until_parked();
597 assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
598}
599
600#[gpui::test]
601async fn test_outline(cx: &mut gpui::TestAppContext) {
602 let text = r#"
603 struct Person {
604 name: String,
605 age: usize,
606 }
607
608 mod module {
609 enum LoginState {
610 LoggedOut,
611 LoggingOn,
612 LoggedIn {
613 person: Person,
614 time: Instant,
615 }
616 }
617 }
618
619 impl Eq for Person {}
620
621 impl Drop for Person {
622 fn drop(&mut self) {
623 println!("bye");
624 }
625 }
626 "#
627 .unindent();
628
629 let buffer =
630 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
631 let outline = buffer
632 .update(cx, |buffer, _| buffer.snapshot().outline(None))
633 .unwrap();
634
635 assert_eq!(
636 outline
637 .items
638 .iter()
639 .map(|item| (item.text.as_str(), item.depth))
640 .collect::<Vec<_>>(),
641 &[
642 ("struct Person", 0),
643 ("name", 1),
644 ("age", 1),
645 ("mod module", 0),
646 ("enum LoginState", 1),
647 ("LoggedOut", 2),
648 ("LoggingOn", 2),
649 ("LoggedIn", 2),
650 ("person", 3),
651 ("time", 3),
652 ("impl Eq for Person", 0),
653 ("impl Drop for Person", 0),
654 ("fn drop", 1),
655 ]
656 );
657
658 // Without space, we only match on names
659 assert_eq!(
660 search(&outline, "oon", cx).await,
661 &[
662 ("mod module", vec![]), // included as the parent of a match
663 ("enum LoginState", vec![]), // included as the parent of a match
664 ("LoggingOn", vec![1, 7, 8]), // matches
665 ("impl Drop for Person", vec![7, 18, 19]), // matches in two disjoint names
666 ]
667 );
668
669 assert_eq!(
670 search(&outline, "dp p", cx).await,
671 &[
672 ("impl Drop for Person", vec![5, 8, 9, 14]),
673 ("fn drop", vec![]),
674 ]
675 );
676 assert_eq!(
677 search(&outline, "dpn", cx).await,
678 &[("impl Drop for Person", vec![5, 14, 19])]
679 );
680 assert_eq!(
681 search(&outline, "impl ", cx).await,
682 &[
683 ("impl Eq for Person", vec![0, 1, 2, 3, 4]),
684 ("impl Drop for Person", vec![0, 1, 2, 3, 4]),
685 ("fn drop", vec![]),
686 ]
687 );
688
689 async fn search<'a>(
690 outline: &'a Outline<Anchor>,
691 query: &'a str,
692 cx: &'a gpui::TestAppContext,
693 ) -> Vec<(&'a str, Vec<usize>)> {
694 let matches = cx
695 .update(|cx| outline.search(query, cx.background_executor().clone()))
696 .await;
697 matches
698 .into_iter()
699 .map(|mat| (outline.items[mat.candidate_id].text.as_str(), mat.positions))
700 .collect::<Vec<_>>()
701 }
702}
703
704#[gpui::test]
705async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
706 let text = r#"
707 impl A for B<
708 C
709 > {
710 };
711 "#
712 .unindent();
713
714 let buffer =
715 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
716 let outline = buffer
717 .update(cx, |buffer, _| buffer.snapshot().outline(None))
718 .unwrap();
719
720 assert_eq!(
721 outline
722 .items
723 .iter()
724 .map(|item| (item.text.as_str(), item.depth))
725 .collect::<Vec<_>>(),
726 &[("impl A for B<", 0)]
727 );
728}
729
730#[gpui::test]
731async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
732 let language = javascript_lang()
733 .with_outline_query(
734 r#"
735 (function_declaration
736 "function" @context
737 name: (_) @name
738 parameters: (formal_parameters
739 "(" @context.extra
740 ")" @context.extra)) @item
741 "#,
742 )
743 .unwrap();
744
745 let text = r#"
746 function a() {}
747 function b(c) {}
748 "#
749 .unindent();
750
751 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(language), cx));
752 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
753
754 // extra context nodes are included in the outline.
755 let outline = snapshot.outline(None).unwrap();
756 assert_eq!(
757 outline
758 .items
759 .iter()
760 .map(|item| (item.text.as_str(), item.depth))
761 .collect::<Vec<_>>(),
762 &[("function a()", 0), ("function b( )", 0),]
763 );
764
765 // extra context nodes do not appear in breadcrumbs.
766 let symbols = snapshot.symbols_containing(3, None).unwrap();
767 assert_eq!(
768 symbols
769 .iter()
770 .map(|item| (item.text.as_str(), item.depth))
771 .collect::<Vec<_>>(),
772 &[("function a", 0)]
773 );
774}
775
776#[gpui::test]
777fn test_outline_annotations(cx: &mut AppContext) {
778 // Add this new test case
779 let text = r#"
780 /// This is a doc comment
781 /// that spans multiple lines
782 fn annotated_function() {
783 // This is not an annotation
784 }
785
786 // This is a single-line annotation
787 fn another_function() {}
788
789 fn unannotated_function() {}
790
791 // This comment is not an annotation
792
793 fn function_after_blank_line() {}
794 "#
795 .unindent();
796
797 let buffer =
798 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
799 let outline = buffer
800 .update(cx, |buffer, _| buffer.snapshot().outline(None))
801 .unwrap();
802
803 assert_eq!(
804 outline
805 .items
806 .into_iter()
807 .map(|item| (
808 item.text,
809 item.depth,
810 item.annotation_range
811 .map(|range| { buffer.read(cx).text_for_range(range).collect::<String>() })
812 ))
813 .collect::<Vec<_>>(),
814 &[
815 (
816 "fn annotated_function".to_string(),
817 0,
818 Some("/// This is a doc comment\n/// that spans multiple lines".to_string())
819 ),
820 (
821 "fn another_function".to_string(),
822 0,
823 Some("// This is a single-line annotation".to_string())
824 ),
825 ("fn unannotated_function".to_string(), 0, None),
826 ("fn function_after_blank_line".to_string(), 0, None),
827 ]
828 );
829}
830
831#[gpui::test]
832async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
833 let text = r#"
834 impl Person {
835 fn one() {
836 1
837 }
838
839 fn two() {
840 2
841 }fn three() {
842 3
843 }
844 }
845 "#
846 .unindent();
847
848 let buffer =
849 cx.new_model(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
850 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
851
852 // point is at the start of an item
853 assert_eq!(
854 symbols_containing(Point::new(1, 4), &snapshot),
855 vec![
856 (
857 "impl Person".to_string(),
858 Point::new(0, 0)..Point::new(10, 1)
859 ),
860 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
861 ]
862 );
863
864 // point is in the middle of an item
865 assert_eq!(
866 symbols_containing(Point::new(2, 8), &snapshot),
867 vec![
868 (
869 "impl Person".to_string(),
870 Point::new(0, 0)..Point::new(10, 1)
871 ),
872 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
873 ]
874 );
875
876 // point is at the end of an item
877 assert_eq!(
878 symbols_containing(Point::new(3, 5), &snapshot),
879 vec![
880 (
881 "impl Person".to_string(),
882 Point::new(0, 0)..Point::new(10, 1)
883 ),
884 ("fn one".to_string(), Point::new(1, 4)..Point::new(3, 5))
885 ]
886 );
887
888 // point is in between two adjacent items
889 assert_eq!(
890 symbols_containing(Point::new(7, 5), &snapshot),
891 vec![
892 (
893 "impl Person".to_string(),
894 Point::new(0, 0)..Point::new(10, 1)
895 ),
896 ("fn two".to_string(), Point::new(5, 4)..Point::new(7, 5))
897 ]
898 );
899
900 fn symbols_containing(
901 position: Point,
902 snapshot: &BufferSnapshot,
903 ) -> Vec<(String, Range<Point>)> {
904 snapshot
905 .symbols_containing(position, None)
906 .unwrap()
907 .into_iter()
908 .map(|item| {
909 (
910 item.text,
911 item.range.start.to_point(snapshot)..item.range.end.to_point(snapshot),
912 )
913 })
914 .collect()
915 }
916}
917
918#[gpui::test]
919fn test_enclosing_bracket_ranges(cx: &mut AppContext) {
920 let mut assert = |selection_text, range_markers| {
921 assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
922 };
923
924 assert(
925 indoc! {"
926 mod x {
927 moˇd y {
928
929 }
930 }
931 let foo = 1;"},
932 vec![indoc! {"
933 mod x «{»
934 mod y {
935
936 }
937 «}»
938 let foo = 1;"}],
939 );
940
941 assert(
942 indoc! {"
943 mod x {
944 mod y ˇ{
945
946 }
947 }
948 let foo = 1;"},
949 vec![
950 indoc! {"
951 mod x «{»
952 mod y {
953
954 }
955 «}»
956 let foo = 1;"},
957 indoc! {"
958 mod x {
959 mod y «{»
960
961 «}»
962 }
963 let foo = 1;"},
964 ],
965 );
966
967 assert(
968 indoc! {"
969 mod x {
970 mod y {
971
972 }ˇ
973 }
974 let foo = 1;"},
975 vec![
976 indoc! {"
977 mod x «{»
978 mod y {
979
980 }
981 «}»
982 let foo = 1;"},
983 indoc! {"
984 mod x {
985 mod y «{»
986
987 «}»
988 }
989 let foo = 1;"},
990 ],
991 );
992
993 assert(
994 indoc! {"
995 mod x {
996 mod y {
997
998 }
999 ˇ}
1000 let foo = 1;"},
1001 vec![indoc! {"
1002 mod x «{»
1003 mod y {
1004
1005 }
1006 «}»
1007 let foo = 1;"}],
1008 );
1009
1010 assert(
1011 indoc! {"
1012 mod x {
1013 mod y {
1014
1015 }
1016 }
1017 let fˇoo = 1;"},
1018 vec![],
1019 );
1020
1021 // Regression test: avoid crash when querying at the end of the buffer.
1022 assert(
1023 indoc! {"
1024 mod x {
1025 mod y {
1026
1027 }
1028 }
1029 let foo = 1;ˇ"},
1030 vec![],
1031 );
1032}
1033
1034#[gpui::test]
1035fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut AppContext) {
1036 let mut assert = |selection_text, bracket_pair_texts| {
1037 assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
1038 };
1039
1040 assert(
1041 indoc! {"
1042 for (const a in b)ˇ {
1043 // a comment that's longer than the for-loop header
1044 }"},
1045 vec![indoc! {"
1046 for «(»const a in b«)» {
1047 // a comment that's longer than the for-loop header
1048 }"}],
1049 );
1050
1051 // Regression test: even though the parent node of the parentheses (the for loop) does
1052 // intersect the given range, the parentheses themselves do not contain the range, so
1053 // they should not be returned. Only the curly braces contain the range.
1054 assert(
1055 indoc! {"
1056 for (const a in b) {ˇ
1057 // a comment that's longer than the for-loop header
1058 }"},
1059 vec![indoc! {"
1060 for (const a in b) «{»
1061 // a comment that's longer than the for-loop header
1062 «}»"}],
1063 );
1064}
1065
1066#[gpui::test]
1067fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
1068 cx.new_model(|cx| {
1069 let text = "fn a() { b(|c| {}) }";
1070 let buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1071 let snapshot = buffer.snapshot();
1072
1073 assert_eq!(
1074 snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")),
1075 Some(range_of(text, "|"))
1076 );
1077 assert_eq!(
1078 snapshot.range_for_syntax_ancestor(range_of(text, "|")),
1079 Some(range_of(text, "|c|"))
1080 );
1081 assert_eq!(
1082 snapshot.range_for_syntax_ancestor(range_of(text, "|c|")),
1083 Some(range_of(text, "|c| {}"))
1084 );
1085 assert_eq!(
1086 snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")),
1087 Some(range_of(text, "(|c| {})"))
1088 );
1089
1090 buffer
1091 });
1092
1093 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
1094 let start = text.find(part).unwrap();
1095 start..start
1096 }
1097
1098 fn range_of(text: &str, part: &str) -> Range<usize> {
1099 let start = text.find(part).unwrap();
1100 start..start + part.len()
1101 }
1102}
1103
1104#[gpui::test]
1105fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
1106 init_settings(cx, |_| {});
1107
1108 cx.new_model(|cx| {
1109 let text = "fn a() {}";
1110 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1111
1112 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1113 assert_eq!(buffer.text(), "fn a() {\n \n}");
1114
1115 buffer.edit(
1116 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
1117 Some(AutoindentMode::EachLine),
1118 cx,
1119 );
1120 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
1121
1122 // Create a field expression on a new line, causing that line
1123 // to be indented.
1124 buffer.edit(
1125 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
1126 Some(AutoindentMode::EachLine),
1127 cx,
1128 );
1129 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
1130
1131 // Remove the dot so that the line is no longer a field expression,
1132 // causing the line to be outdented.
1133 buffer.edit(
1134 [(Point::new(2, 8)..Point::new(2, 9), "")],
1135 Some(AutoindentMode::EachLine),
1136 cx,
1137 );
1138 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
1139
1140 buffer
1141 });
1142}
1143
1144#[gpui::test]
1145fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
1146 init_settings(cx, |settings| {
1147 settings.defaults.hard_tabs = Some(true);
1148 });
1149
1150 cx.new_model(|cx| {
1151 let text = "fn a() {}";
1152 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1153
1154 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1155 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
1156
1157 buffer.edit(
1158 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
1159 Some(AutoindentMode::EachLine),
1160 cx,
1161 );
1162 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
1163
1164 // Create a field expression on a new line, causing that line
1165 // to be indented.
1166 buffer.edit(
1167 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
1168 Some(AutoindentMode::EachLine),
1169 cx,
1170 );
1171 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
1172
1173 // Remove the dot so that the line is no longer a field expression,
1174 // causing the line to be outdented.
1175 buffer.edit(
1176 [(Point::new(2, 2)..Point::new(2, 3), "")],
1177 Some(AutoindentMode::EachLine),
1178 cx,
1179 );
1180 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
1181
1182 buffer
1183 });
1184}
1185
1186#[gpui::test]
1187fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
1188 init_settings(cx, |_| {});
1189
1190 cx.new_model(|cx| {
1191 let mut buffer = Buffer::local(
1192 "
1193 fn a() {
1194 c;
1195 d;
1196 }
1197 "
1198 .unindent(),
1199 cx,
1200 )
1201 .with_language(Arc::new(rust_lang()), cx);
1202
1203 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
1204 // their indentation is not adjusted.
1205 buffer.edit_via_marked_text(
1206 &"
1207 fn a() {
1208 c«()»;
1209 d«()»;
1210 }
1211 "
1212 .unindent(),
1213 Some(AutoindentMode::EachLine),
1214 cx,
1215 );
1216 assert_eq!(
1217 buffer.text(),
1218 "
1219 fn a() {
1220 c();
1221 d();
1222 }
1223 "
1224 .unindent()
1225 );
1226
1227 // When appending new content after these lines, the indentation is based on the
1228 // preceding lines' actual indentation.
1229 buffer.edit_via_marked_text(
1230 &"
1231 fn a() {
1232 c«
1233 .f
1234 .g()»;
1235 d«
1236 .f
1237 .g()»;
1238 }
1239 "
1240 .unindent(),
1241 Some(AutoindentMode::EachLine),
1242 cx,
1243 );
1244
1245 assert_eq!(
1246 buffer.text(),
1247 "
1248 fn a() {
1249 c
1250 .f
1251 .g();
1252 d
1253 .f
1254 .g();
1255 }
1256 "
1257 .unindent()
1258 );
1259 buffer
1260 });
1261
1262 cx.new_model(|cx| {
1263 eprintln!("second buffer: {:?}", cx.entity_id());
1264
1265 let mut buffer = Buffer::local(
1266 "
1267 fn a() {
1268 b();
1269 |
1270 "
1271 .replace('|', "") // marker to preserve trailing whitespace
1272 .unindent(),
1273 cx,
1274 )
1275 .with_language(Arc::new(rust_lang()), cx);
1276
1277 // Insert a closing brace. It is outdented.
1278 buffer.edit_via_marked_text(
1279 &"
1280 fn a() {
1281 b();
1282 «}»
1283 "
1284 .unindent(),
1285 Some(AutoindentMode::EachLine),
1286 cx,
1287 );
1288 assert_eq!(
1289 buffer.text(),
1290 "
1291 fn a() {
1292 b();
1293 }
1294 "
1295 .unindent()
1296 );
1297
1298 // Manually edit the leading whitespace. The edit is preserved.
1299 buffer.edit_via_marked_text(
1300 &"
1301 fn a() {
1302 b();
1303 « »}
1304 "
1305 .unindent(),
1306 Some(AutoindentMode::EachLine),
1307 cx,
1308 );
1309 assert_eq!(
1310 buffer.text(),
1311 "
1312 fn a() {
1313 b();
1314 }
1315 "
1316 .unindent()
1317 );
1318 buffer
1319 });
1320
1321 eprintln!("DONE");
1322}
1323
1324#[gpui::test]
1325fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
1326 init_settings(cx, |_| {});
1327
1328 cx.new_model(|cx| {
1329 let mut buffer = Buffer::local(
1330 "
1331 fn a() {
1332 i
1333 }
1334 "
1335 .unindent(),
1336 cx,
1337 )
1338 .with_language(Arc::new(rust_lang()), cx);
1339
1340 // Regression test: line does not get outdented due to syntax error
1341 buffer.edit_via_marked_text(
1342 &"
1343 fn a() {
1344 i«f let Some(x) = y»
1345 }
1346 "
1347 .unindent(),
1348 Some(AutoindentMode::EachLine),
1349 cx,
1350 );
1351 assert_eq!(
1352 buffer.text(),
1353 "
1354 fn a() {
1355 if let Some(x) = y
1356 }
1357 "
1358 .unindent()
1359 );
1360
1361 buffer.edit_via_marked_text(
1362 &"
1363 fn a() {
1364 if let Some(x) = y« {»
1365 }
1366 "
1367 .unindent(),
1368 Some(AutoindentMode::EachLine),
1369 cx,
1370 );
1371 assert_eq!(
1372 buffer.text(),
1373 "
1374 fn a() {
1375 if let Some(x) = y {
1376 }
1377 "
1378 .unindent()
1379 );
1380
1381 buffer
1382 });
1383}
1384
1385#[gpui::test]
1386fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
1387 init_settings(cx, |_| {});
1388
1389 cx.new_model(|cx| {
1390 let mut buffer = Buffer::local(
1391 "
1392 fn a() {}
1393 "
1394 .unindent(),
1395 cx,
1396 )
1397 .with_language(Arc::new(rust_lang()), cx);
1398
1399 buffer.edit_via_marked_text(
1400 &"
1401 fn a(«
1402 b») {}
1403 "
1404 .unindent(),
1405 Some(AutoindentMode::EachLine),
1406 cx,
1407 );
1408 assert_eq!(
1409 buffer.text(),
1410 "
1411 fn a(
1412 b) {}
1413 "
1414 .unindent()
1415 );
1416
1417 // The indentation suggestion changed because `@end` node (a close paren)
1418 // is now at the beginning of the line.
1419 buffer.edit_via_marked_text(
1420 &"
1421 fn a(
1422 ˇ) {}
1423 "
1424 .unindent(),
1425 Some(AutoindentMode::EachLine),
1426 cx,
1427 );
1428 assert_eq!(
1429 buffer.text(),
1430 "
1431 fn a(
1432 ) {}
1433 "
1434 .unindent()
1435 );
1436
1437 buffer
1438 });
1439}
1440
1441#[gpui::test]
1442fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
1443 init_settings(cx, |_| {});
1444
1445 cx.new_model(|cx| {
1446 let text = "a\nb";
1447 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1448 buffer.edit(
1449 [(0..1, "\n"), (2..3, "\n")],
1450 Some(AutoindentMode::EachLine),
1451 cx,
1452 );
1453 assert_eq!(buffer.text(), "\n\n\n");
1454 buffer
1455 });
1456}
1457
1458#[gpui::test]
1459fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
1460 init_settings(cx, |_| {});
1461
1462 cx.new_model(|cx| {
1463 let text = "
1464 const a: usize = 1;
1465 fn b() {
1466 if c {
1467 let d = 2;
1468 }
1469 }
1470 "
1471 .unindent();
1472
1473 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1474 buffer.edit(
1475 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
1476 Some(AutoindentMode::EachLine),
1477 cx,
1478 );
1479 assert_eq!(
1480 buffer.text(),
1481 "
1482 const a: usize = 1;
1483 fn b() {
1484 if c {
1485 e(
1486 f()
1487 );
1488 let d = 2;
1489 }
1490 }
1491 "
1492 .unindent()
1493 );
1494
1495 buffer
1496 });
1497}
1498
1499#[gpui::test]
1500fn test_autoindent_block_mode(cx: &mut AppContext) {
1501 init_settings(cx, |_| {});
1502
1503 cx.new_model(|cx| {
1504 let text = r#"
1505 fn a() {
1506 b();
1507 }
1508 "#
1509 .unindent();
1510 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1511
1512 // When this text was copied, both of the quotation marks were at the same
1513 // indent level, but the indentation of the first line was not included in
1514 // the copied text. This information is retained in the
1515 // 'original_indent_columns' vector.
1516 let original_indent_columns = vec![4];
1517 let inserted_text = r#"
1518 "
1519 c
1520 d
1521 e
1522 "
1523 "#
1524 .unindent();
1525
1526 // Insert the block at column zero. The entire block is indented
1527 // so that the first line matches the previous line's indentation.
1528 buffer.edit(
1529 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
1530 Some(AutoindentMode::Block {
1531 original_indent_columns: original_indent_columns.clone(),
1532 }),
1533 cx,
1534 );
1535 assert_eq!(
1536 buffer.text(),
1537 r#"
1538 fn a() {
1539 b();
1540 "
1541 c
1542 d
1543 e
1544 "
1545 }
1546 "#
1547 .unindent()
1548 );
1549
1550 // Grouping is disabled in tests, so we need 2 undos
1551 buffer.undo(cx); // Undo the auto-indent
1552 buffer.undo(cx); // Undo the original edit
1553
1554 // Insert the block at a deeper indent level. The entire block is outdented.
1555 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
1556 buffer.edit(
1557 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
1558 Some(AutoindentMode::Block {
1559 original_indent_columns: original_indent_columns.clone(),
1560 }),
1561 cx,
1562 );
1563 assert_eq!(
1564 buffer.text(),
1565 r#"
1566 fn a() {
1567 b();
1568 "
1569 c
1570 d
1571 e
1572 "
1573 }
1574 "#
1575 .unindent()
1576 );
1577
1578 buffer
1579 });
1580}
1581
1582#[gpui::test]
1583fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
1584 init_settings(cx, |_| {});
1585
1586 cx.new_model(|cx| {
1587 let text = r#"
1588 fn a() {
1589 if b() {
1590
1591 }
1592 }
1593 "#
1594 .unindent();
1595 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1596
1597 // The original indent columns are not known, so this text is
1598 // auto-indented in a block as if the first line was copied in
1599 // its entirety.
1600 let original_indent_columns = Vec::new();
1601 let inserted_text = " c\n .d()\n .e();";
1602
1603 // Insert the block at column zero. The entire block is indented
1604 // so that the first line matches the previous line's indentation.
1605 buffer.edit(
1606 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1607 Some(AutoindentMode::Block {
1608 original_indent_columns: original_indent_columns.clone(),
1609 }),
1610 cx,
1611 );
1612 assert_eq!(
1613 buffer.text(),
1614 r#"
1615 fn a() {
1616 if b() {
1617 c
1618 .d()
1619 .e();
1620 }
1621 }
1622 "#
1623 .unindent()
1624 );
1625
1626 // Grouping is disabled in tests, so we need 2 undos
1627 buffer.undo(cx); // Undo the auto-indent
1628 buffer.undo(cx); // Undo the original edit
1629
1630 // Insert the block at a deeper indent level. The entire block is outdented.
1631 buffer.edit(
1632 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
1633 None,
1634 cx,
1635 );
1636 buffer.edit(
1637 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
1638 Some(AutoindentMode::Block {
1639 original_indent_columns: Vec::new(),
1640 }),
1641 cx,
1642 );
1643 assert_eq!(
1644 buffer.text(),
1645 r#"
1646 fn a() {
1647 if b() {
1648 c
1649 .d()
1650 .e();
1651 }
1652 }
1653 "#
1654 .unindent()
1655 );
1656
1657 buffer
1658 });
1659}
1660
1661#[gpui::test]
1662fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
1663 init_settings(cx, |_| {});
1664
1665 cx.new_model(|cx| {
1666 let text = "
1667 * one
1668 - a
1669 - b
1670 * two
1671 "
1672 .unindent();
1673
1674 let mut buffer = Buffer::local(text, cx).with_language(
1675 Arc::new(Language::new(
1676 LanguageConfig {
1677 name: "Markdown".into(),
1678 auto_indent_using_last_non_empty_line: false,
1679 ..Default::default()
1680 },
1681 Some(tree_sitter_json::LANGUAGE.into()),
1682 )),
1683 cx,
1684 );
1685 buffer.edit(
1686 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
1687 Some(AutoindentMode::EachLine),
1688 cx,
1689 );
1690 assert_eq!(
1691 buffer.text(),
1692 "
1693 * one
1694 - a
1695 - b
1696
1697 * two
1698 "
1699 .unindent()
1700 );
1701 buffer
1702 });
1703}
1704
1705#[gpui::test]
1706fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
1707 init_settings(cx, |settings| {
1708 settings.languages.extend([
1709 (
1710 "HTML".into(),
1711 LanguageSettingsContent {
1712 tab_size: Some(2.try_into().unwrap()),
1713 ..Default::default()
1714 },
1715 ),
1716 (
1717 "JavaScript".into(),
1718 LanguageSettingsContent {
1719 tab_size: Some(8.try_into().unwrap()),
1720 ..Default::default()
1721 },
1722 ),
1723 ])
1724 });
1725
1726 let html_language = Arc::new(html_lang());
1727
1728 let javascript_language = Arc::new(javascript_lang());
1729
1730 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1731 language_registry.add(html_language.clone());
1732 language_registry.add(javascript_language.clone());
1733
1734 cx.new_model(|cx| {
1735 let (text, ranges) = marked_text_ranges(
1736 &"
1737 <div>ˇ
1738 </div>
1739 <script>
1740 init({ˇ
1741 })
1742 </script>
1743 <span>ˇ
1744 </span>
1745 "
1746 .unindent(),
1747 false,
1748 );
1749
1750 let mut buffer = Buffer::local(text, cx);
1751 buffer.set_language_registry(language_registry);
1752 buffer.set_language(Some(html_language), cx);
1753 buffer.edit(
1754 ranges.into_iter().map(|range| (range, "\na")),
1755 Some(AutoindentMode::EachLine),
1756 cx,
1757 );
1758 assert_eq!(
1759 buffer.text(),
1760 "
1761 <div>
1762 a
1763 </div>
1764 <script>
1765 init({
1766 a
1767 })
1768 </script>
1769 <span>
1770 a
1771 </span>
1772 "
1773 .unindent()
1774 );
1775 buffer
1776 });
1777}
1778
1779#[gpui::test]
1780fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
1781 init_settings(cx, |settings| {
1782 settings.defaults.tab_size = Some(2.try_into().unwrap());
1783 });
1784
1785 cx.new_model(|cx| {
1786 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
1787
1788 let text = r#"
1789 class C
1790 def a(b, c)
1791 puts b
1792 puts c
1793 rescue
1794 puts "errored"
1795 exit 1
1796 end
1797 end
1798 "#
1799 .unindent();
1800
1801 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
1802
1803 assert_eq!(
1804 buffer.text(),
1805 r#"
1806 class C
1807 def a(b, c)
1808 puts b
1809 puts c
1810 rescue
1811 puts "errored"
1812 exit 1
1813 end
1814 end
1815 "#
1816 .unindent()
1817 );
1818
1819 buffer
1820 });
1821}
1822
1823#[gpui::test]
1824async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
1825 cx.update(|cx| init_settings(cx, |_| {}));
1826
1827 // First we insert some newlines to request an auto-indent (asynchronously).
1828 // Then we request that a preview tab be preserved for the new version, even though it's edited.
1829 let buffer = cx.new_model(|cx| {
1830 let text = "fn a() {}";
1831 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1832
1833 // This causes autoindent to be async.
1834 buffer.set_sync_parse_timeout(Duration::ZERO);
1835
1836 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1837 buffer.refresh_preview();
1838
1839 // Synchronously, we haven't auto-indented and we're still preserving the preview.
1840 assert_eq!(buffer.text(), "fn a() {\n\n}");
1841 assert!(buffer.preserve_preview());
1842 buffer
1843 });
1844
1845 // Now let the autoindent finish
1846 cx.executor().run_until_parked();
1847
1848 // The auto-indent applied, but didn't dismiss our preview
1849 buffer.update(cx, |buffer, cx| {
1850 assert_eq!(buffer.text(), "fn a() {\n \n}");
1851 assert!(buffer.preserve_preview());
1852
1853 // Edit inserting another line. It will autoindent async.
1854 // Then refresh the preview version.
1855 buffer.edit(
1856 [(Point::new(1, 4)..Point::new(1, 4), "\n")],
1857 Some(AutoindentMode::EachLine),
1858 cx,
1859 );
1860 buffer.refresh_preview();
1861 assert_eq!(buffer.text(), "fn a() {\n \n\n}");
1862 assert!(buffer.preserve_preview());
1863
1864 // Then perform another edit, this time without refreshing the preview version.
1865 buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
1866 // This causes the preview to not be preserved.
1867 assert!(!buffer.preserve_preview());
1868 });
1869
1870 // Let the async autoindent from the first edit finish.
1871 cx.executor().run_until_parked();
1872
1873 // The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
1874 buffer.update(cx, |buffer, _| {
1875 assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
1876 assert!(!buffer.preserve_preview());
1877 });
1878}
1879
1880#[gpui::test]
1881fn test_insert_empty_line(cx: &mut AppContext) {
1882 init_settings(cx, |_| {});
1883
1884 // Insert empty line at the beginning, requesting an empty line above
1885 cx.new_model(|cx| {
1886 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1887 let point = buffer.insert_empty_line(Point::new(0, 0), true, false, cx);
1888 assert_eq!(buffer.text(), "\nabc\ndef\nghi");
1889 assert_eq!(point, Point::new(0, 0));
1890 buffer
1891 });
1892
1893 // Insert empty line at the beginning, requesting an empty line above and below
1894 cx.new_model(|cx| {
1895 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1896 let point = buffer.insert_empty_line(Point::new(0, 0), true, true, cx);
1897 assert_eq!(buffer.text(), "\n\nabc\ndef\nghi");
1898 assert_eq!(point, Point::new(0, 0));
1899 buffer
1900 });
1901
1902 // Insert empty line at the start of a line, requesting empty lines above and below
1903 cx.new_model(|cx| {
1904 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1905 let point = buffer.insert_empty_line(Point::new(2, 0), true, true, cx);
1906 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi");
1907 assert_eq!(point, Point::new(3, 0));
1908 buffer
1909 });
1910
1911 // Insert empty line in the middle of a line, requesting empty lines above and below
1912 cx.new_model(|cx| {
1913 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
1914 let point = buffer.insert_empty_line(Point::new(1, 3), true, true, cx);
1915 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi\njkl");
1916 assert_eq!(point, Point::new(3, 0));
1917 buffer
1918 });
1919
1920 // Insert empty line in the middle of a line, requesting empty line above only
1921 cx.new_model(|cx| {
1922 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
1923 let point = buffer.insert_empty_line(Point::new(1, 3), true, false, cx);
1924 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
1925 assert_eq!(point, Point::new(3, 0));
1926 buffer
1927 });
1928
1929 // Insert empty line in the middle of a line, requesting empty line below only
1930 cx.new_model(|cx| {
1931 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
1932 let point = buffer.insert_empty_line(Point::new(1, 3), false, true, cx);
1933 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
1934 assert_eq!(point, Point::new(2, 0));
1935 buffer
1936 });
1937
1938 // Insert empty line at the end, requesting empty lines above and below
1939 cx.new_model(|cx| {
1940 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1941 let point = buffer.insert_empty_line(Point::new(2, 3), true, true, cx);
1942 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n\n");
1943 assert_eq!(point, Point::new(4, 0));
1944 buffer
1945 });
1946
1947 // Insert empty line at the end, requesting empty line above only
1948 cx.new_model(|cx| {
1949 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1950 let point = buffer.insert_empty_line(Point::new(2, 3), true, false, cx);
1951 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
1952 assert_eq!(point, Point::new(4, 0));
1953 buffer
1954 });
1955
1956 // Insert empty line at the end, requesting empty line below only
1957 cx.new_model(|cx| {
1958 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1959 let point = buffer.insert_empty_line(Point::new(2, 3), false, true, cx);
1960 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
1961 assert_eq!(point, Point::new(3, 0));
1962 buffer
1963 });
1964}
1965
1966#[gpui::test]
1967fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
1968 init_settings(cx, |_| {});
1969
1970 cx.new_model(|cx| {
1971 let language = Language::new(
1972 LanguageConfig {
1973 name: "JavaScript".into(),
1974 line_comments: vec!["// ".into()],
1975 brackets: BracketPairConfig {
1976 pairs: vec![
1977 BracketPair {
1978 start: "{".into(),
1979 end: "}".into(),
1980 close: true,
1981 surround: true,
1982 newline: false,
1983 },
1984 BracketPair {
1985 start: "'".into(),
1986 end: "'".into(),
1987 close: true,
1988 surround: true,
1989 newline: false,
1990 },
1991 ],
1992 disabled_scopes_by_bracket_ix: vec![
1993 Vec::new(), //
1994 vec!["string".into()],
1995 ],
1996 },
1997 overrides: [(
1998 "element".into(),
1999 LanguageConfigOverride {
2000 line_comments: Override::Remove { remove: true },
2001 block_comment: Override::Set(("{/*".into(), "*/}".into())),
2002 ..Default::default()
2003 },
2004 )]
2005 .into_iter()
2006 .collect(),
2007 ..Default::default()
2008 },
2009 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2010 )
2011 .with_override_query(
2012 r#"
2013 (jsx_element) @element
2014 (string) @string
2015 [
2016 (jsx_opening_element)
2017 (jsx_closing_element)
2018 (jsx_expression)
2019 ] @default
2020 "#,
2021 )
2022 .unwrap();
2023
2024 let text = r#"
2025 a["b"] = <C d="e">
2026 <F></F>
2027 { g() }
2028 </C>;
2029 "#
2030 .unindent();
2031
2032 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2033 let snapshot = buffer.snapshot();
2034
2035 let config = snapshot.language_scope_at(0).unwrap();
2036 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2037 // Both bracket pairs are enabled
2038 assert_eq!(
2039 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2040 &[true, true]
2041 );
2042
2043 let string_config = snapshot
2044 .language_scope_at(text.find("b\"").unwrap())
2045 .unwrap();
2046 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2047 // Second bracket pair is disabled
2048 assert_eq!(
2049 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2050 &[true, false]
2051 );
2052
2053 // In between JSX tags: use the `element` override.
2054 let element_config = snapshot
2055 .language_scope_at(text.find("<F>").unwrap())
2056 .unwrap();
2057 // TODO nested blocks after newlines are captured with all whitespaces
2058 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2059 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2060 // assert_eq!(
2061 // element_config.block_comment_delimiters(),
2062 // Some((&"{/*".into(), &"*/}".into()))
2063 // );
2064 assert_eq!(
2065 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2066 &[true, true]
2067 );
2068
2069 // Within a JSX tag: use the default config.
2070 let tag_config = snapshot
2071 .language_scope_at(text.find(" d=").unwrap() + 1)
2072 .unwrap();
2073 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2074 assert_eq!(
2075 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2076 &[true, true]
2077 );
2078
2079 // In a JSX expression: use the default config.
2080 let expression_in_element_config = snapshot
2081 .language_scope_at(text.find('{').unwrap() + 1)
2082 .unwrap();
2083 assert_eq!(
2084 expression_in_element_config.line_comment_prefixes(),
2085 &[Arc::from("// ")]
2086 );
2087 assert_eq!(
2088 expression_in_element_config
2089 .brackets()
2090 .map(|e| e.1)
2091 .collect::<Vec<_>>(),
2092 &[true, true]
2093 );
2094
2095 buffer
2096 });
2097}
2098
2099#[gpui::test]
2100fn test_language_scope_at_with_rust(cx: &mut AppContext) {
2101 init_settings(cx, |_| {});
2102
2103 cx.new_model(|cx| {
2104 let language = Language::new(
2105 LanguageConfig {
2106 name: "Rust".into(),
2107 brackets: BracketPairConfig {
2108 pairs: vec![
2109 BracketPair {
2110 start: "{".into(),
2111 end: "}".into(),
2112 close: true,
2113 surround: true,
2114 newline: false,
2115 },
2116 BracketPair {
2117 start: "'".into(),
2118 end: "'".into(),
2119 close: true,
2120 surround: true,
2121 newline: false,
2122 },
2123 ],
2124 disabled_scopes_by_bracket_ix: vec![
2125 Vec::new(), //
2126 vec!["string".into()],
2127 ],
2128 },
2129 ..Default::default()
2130 },
2131 Some(tree_sitter_rust::LANGUAGE.into()),
2132 )
2133 .with_override_query(
2134 r#"
2135 (string_literal) @string
2136 "#,
2137 )
2138 .unwrap();
2139
2140 let text = r#"
2141 const S: &'static str = "hello";
2142 "#
2143 .unindent();
2144
2145 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2146 let snapshot = buffer.snapshot();
2147
2148 // By default, all brackets are enabled
2149 let config = snapshot.language_scope_at(0).unwrap();
2150 assert_eq!(
2151 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2152 &[true, true]
2153 );
2154
2155 // Within a string, the quotation brackets are disabled.
2156 let string_config = snapshot
2157 .language_scope_at(text.find("ello").unwrap())
2158 .unwrap();
2159 assert_eq!(
2160 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2161 &[true, false]
2162 );
2163
2164 buffer
2165 });
2166}
2167
2168#[gpui::test]
2169fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
2170 init_settings(cx, |_| {});
2171
2172 cx.new_model(|cx| {
2173 let text = r#"
2174 <ol>
2175 <% people.each do |person| %>
2176 <li>
2177 <%= person.name %>
2178 </li>
2179 <% end %>
2180 </ol>
2181 "#
2182 .unindent();
2183
2184 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2185 language_registry.add(Arc::new(ruby_lang()));
2186 language_registry.add(Arc::new(html_lang()));
2187 language_registry.add(Arc::new(erb_lang()));
2188
2189 let mut buffer = Buffer::local(text, cx);
2190 buffer.set_language_registry(language_registry.clone());
2191 buffer.set_language(
2192 language_registry
2193 .language_for_name("ERB")
2194 .now_or_never()
2195 .unwrap()
2196 .ok(),
2197 cx,
2198 );
2199
2200 let snapshot = buffer.snapshot();
2201 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2202 assert_eq!(html_config.line_comment_prefixes(), &[]);
2203 assert_eq!(
2204 html_config.block_comment_delimiters(),
2205 Some((&"<!--".into(), &"-->".into()))
2206 );
2207
2208 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2209 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2210 assert_eq!(ruby_config.block_comment_delimiters(), None);
2211
2212 buffer
2213 });
2214}
2215
2216#[gpui::test]
2217fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
2218 init_settings(cx, |_| {});
2219
2220 cx.new_model(|cx| {
2221 let text = r#"
2222 this is an *emphasized* word.
2223 "#
2224 .unindent();
2225
2226 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2227 language_registry.add(Arc::new(markdown_lang()));
2228 language_registry.add(Arc::new(markdown_inline_lang()));
2229
2230 let mut buffer = Buffer::local(text, cx);
2231 buffer.set_language_registry(language_registry.clone());
2232 buffer.set_language(
2233 language_registry
2234 .language_for_name("Markdown")
2235 .now_or_never()
2236 .unwrap()
2237 .ok(),
2238 cx,
2239 );
2240
2241 let snapshot = buffer.snapshot();
2242
2243 for point in [Point::new(0, 4), Point::new(0, 16)] {
2244 let config = snapshot.language_scope_at(point).unwrap();
2245 assert_eq!(config.language_name(), "Markdown".into());
2246
2247 let language = snapshot.language_at(point).unwrap();
2248 assert_eq!(language.name().0.as_ref(), "Markdown");
2249 }
2250
2251 buffer
2252 });
2253}
2254
2255#[gpui::test]
2256fn test_serialization(cx: &mut gpui::AppContext) {
2257 let mut now = Instant::now();
2258
2259 let buffer1 = cx.new_model(|cx| {
2260 let mut buffer = Buffer::local("abc", cx);
2261 buffer.edit([(3..3, "D")], None, cx);
2262
2263 now += Duration::from_secs(1);
2264 buffer.start_transaction_at(now);
2265 buffer.edit([(4..4, "E")], None, cx);
2266 buffer.end_transaction_at(now, cx);
2267 assert_eq!(buffer.text(), "abcDE");
2268
2269 buffer.undo(cx);
2270 assert_eq!(buffer.text(), "abcD");
2271
2272 buffer.edit([(4..4, "F")], None, cx);
2273 assert_eq!(buffer.text(), "abcDF");
2274 buffer
2275 });
2276 assert_eq!(buffer1.read(cx).text(), "abcDF");
2277
2278 let state = buffer1.read(cx).to_proto(cx);
2279 let ops = cx
2280 .background_executor()
2281 .block(buffer1.read(cx).serialize_ops(None, cx));
2282 let buffer2 = cx.new_model(|cx| {
2283 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2284 buffer.apply_ops(
2285 ops.into_iter()
2286 .map(|op| proto::deserialize_operation(op).unwrap()),
2287 cx,
2288 );
2289 buffer
2290 });
2291 assert_eq!(buffer2.read(cx).text(), "abcDF");
2292}
2293
2294#[gpui::test]
2295async fn test_find_matching_indent(cx: &mut TestAppContext) {
2296 cx.update(|cx| init_settings(cx, |_| {}));
2297
2298 async fn enclosing_indent(
2299 text: impl Into<String>,
2300 buffer_row: u32,
2301 cx: &mut TestAppContext,
2302 ) -> Option<(Range<u32>, LineIndent)> {
2303 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
2304 let snapshot = cx.read(|cx| buffer.read(cx).snapshot());
2305 snapshot.enclosing_indent(buffer_row).await
2306 }
2307
2308 assert_eq!(
2309 enclosing_indent(
2310 "
2311 fn b() {
2312 if c {
2313 let d = 2;
2314 }
2315 }"
2316 .unindent(),
2317 1,
2318 cx,
2319 )
2320 .await,
2321 Some((
2322 1..2,
2323 LineIndent {
2324 tabs: 0,
2325 spaces: 4,
2326 line_blank: false,
2327 }
2328 ))
2329 );
2330
2331 assert_eq!(
2332 enclosing_indent(
2333 "
2334 fn b() {
2335 if c {
2336 let d = 2;
2337 }
2338 }"
2339 .unindent(),
2340 2,
2341 cx,
2342 )
2343 .await,
2344 Some((
2345 1..2,
2346 LineIndent {
2347 tabs: 0,
2348 spaces: 4,
2349 line_blank: false,
2350 }
2351 ))
2352 );
2353
2354 assert_eq!(
2355 enclosing_indent(
2356 "
2357 fn b() {
2358 if c {
2359 let d = 2;
2360
2361 let e = 5;
2362 }
2363 }"
2364 .unindent(),
2365 3,
2366 cx,
2367 )
2368 .await,
2369 Some((
2370 1..4,
2371 LineIndent {
2372 tabs: 0,
2373 spaces: 4,
2374 line_blank: false,
2375 }
2376 ))
2377 );
2378}
2379
2380#[gpui::test]
2381fn test_branch_and_merge(cx: &mut TestAppContext) {
2382 cx.update(|cx| init_settings(cx, |_| {}));
2383
2384 let base_buffer = cx.new_model(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2385
2386 // Create a remote replica of the base buffer.
2387 let base_buffer_replica = cx.new_model(|cx| {
2388 Buffer::from_proto(
2389 1,
2390 Capability::ReadWrite,
2391 base_buffer.read(cx).to_proto(cx),
2392 None,
2393 )
2394 .unwrap()
2395 });
2396 base_buffer.update(cx, |_buffer, cx| {
2397 cx.subscribe(&base_buffer_replica, |this, _, event, cx| {
2398 if let BufferEvent::Operation {
2399 operation,
2400 is_local: true,
2401 } = event
2402 {
2403 this.apply_ops([operation.clone()], cx);
2404 }
2405 })
2406 .detach();
2407 });
2408
2409 // Create a branch, which initially has the same state as the base buffer.
2410 let branch_buffer = base_buffer.update(cx, |buffer, cx| buffer.branch(cx));
2411 branch_buffer.read_with(cx, |buffer, _| {
2412 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2413 });
2414
2415 // Edits to the branch are not applied to the base.
2416 branch_buffer.update(cx, |branch_buffer, cx| {
2417 branch_buffer.edit(
2418 [
2419 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2420 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2421 ],
2422 None,
2423 cx,
2424 )
2425 });
2426 branch_buffer.read_with(cx, |branch_buffer, cx| {
2427 assert_eq!(base_buffer.read(cx).text(), "one\ntwo\nthree\n");
2428 assert_eq!(branch_buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2429 });
2430
2431 // The branch buffer maintains a diff with respect to its base buffer.
2432 start_recalculating_diff(&branch_buffer, cx);
2433 cx.run_until_parked();
2434 assert_diff_hunks(
2435 &branch_buffer,
2436 cx,
2437 &[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
2438 );
2439
2440 // Edits to the base are applied to the branch.
2441 base_buffer.update(cx, |buffer, cx| {
2442 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2443 });
2444 branch_buffer.read_with(cx, |branch_buffer, cx| {
2445 assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2446 assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2447 });
2448
2449 // Until the git diff recalculation is complete, the git diff references
2450 // the previous content of the base buffer, so that it stays in sync.
2451 start_recalculating_diff(&branch_buffer, cx);
2452 assert_diff_hunks(
2453 &branch_buffer,
2454 cx,
2455 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2456 );
2457 cx.run_until_parked();
2458 assert_diff_hunks(
2459 &branch_buffer,
2460 cx,
2461 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2462 );
2463
2464 // Edits to any replica of the base are applied to the branch.
2465 base_buffer_replica.update(cx, |buffer, cx| {
2466 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2467 });
2468 branch_buffer.read_with(cx, |branch_buffer, cx| {
2469 assert_eq!(base_buffer.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2470 assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2471 });
2472
2473 // Merging the branch applies all of its changes to the base.
2474 branch_buffer.update(cx, |branch_buffer, cx| {
2475 branch_buffer.merge_into_base(Vec::new(), cx);
2476 });
2477
2478 branch_buffer.update(cx, |branch_buffer, cx| {
2479 assert_eq!(
2480 base_buffer.read(cx).text(),
2481 "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n"
2482 );
2483 assert_eq!(branch_buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2484 });
2485}
2486
2487#[gpui::test]
2488fn test_merge_into_base(cx: &mut TestAppContext) {
2489 cx.update(|cx| init_settings(cx, |_| {}));
2490
2491 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2492 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2493
2494 // Make 3 edits, merge one into the base.
2495 branch.update(cx, |branch, cx| {
2496 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2497 branch.merge_into_base(vec![5..8], cx);
2498 });
2499
2500 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2501 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2502
2503 // Undo the one already-merged edit. Merge that into the base.
2504 branch.update(cx, |branch, cx| {
2505 branch.edit([(7..9, "hi")], None, cx);
2506 branch.merge_into_base(vec![5..8], cx);
2507 });
2508 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2509
2510 // Merge an insertion into the base.
2511 branch.update(cx, |branch, cx| {
2512 branch.merge_into_base(vec![11..11], cx);
2513 });
2514
2515 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2516 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2517
2518 // Deleted the inserted text and merge that into the base.
2519 branch.update(cx, |branch, cx| {
2520 branch.edit([(11..14, "")], None, cx);
2521 branch.merge_into_base(vec![10..11], cx);
2522 });
2523
2524 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2525}
2526
2527#[gpui::test]
2528fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2529 cx.update(|cx| init_settings(cx, |_| {}));
2530
2531 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2532 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2533
2534 // Make 2 edits, merge one into the base.
2535 branch.update(cx, |branch, cx| {
2536 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2537 branch.merge_into_base(vec![7..7], cx);
2538 });
2539 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2540 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2541
2542 // Undo the merge in the base buffer.
2543 base.update(cx, |base, cx| {
2544 base.undo(cx);
2545 });
2546 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2547 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2548
2549 // Merge that operation into the base again.
2550 branch.update(cx, |branch, cx| {
2551 branch.merge_into_base(vec![7..7], cx);
2552 });
2553 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2554 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2555}
2556
2557fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
2558 buffer
2559 .update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
2560 .detach();
2561}
2562
2563#[track_caller]
2564fn assert_diff_hunks(
2565 buffer: &Model<Buffer>,
2566 cx: &mut TestAppContext,
2567 expected_hunks: &[(Range<u32>, &str, &str)],
2568) {
2569 let (snapshot, diff_base) = buffer.read_with(cx, |buffer, _| {
2570 (buffer.snapshot(), buffer.diff_base().unwrap().to_string())
2571 });
2572 assert_hunks(
2573 snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
2574 &snapshot,
2575 &diff_base,
2576 expected_hunks,
2577 );
2578}
2579
2580#[gpui::test(iterations = 100)]
2581fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
2582 let min_peers = env::var("MIN_PEERS")
2583 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2584 .unwrap_or(1);
2585 let max_peers = env::var("MAX_PEERS")
2586 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2587 .unwrap_or(5);
2588 let operations = env::var("OPERATIONS")
2589 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2590 .unwrap_or(10);
2591
2592 let base_text_len = rng.gen_range(0..10);
2593 let base_text = RandomCharIter::new(&mut rng)
2594 .take(base_text_len)
2595 .collect::<String>();
2596 let mut replica_ids = Vec::new();
2597 let mut buffers = Vec::new();
2598 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2599 let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
2600
2601 for i in 0..rng.gen_range(min_peers..=max_peers) {
2602 let buffer = cx.new_model(|cx| {
2603 let state = base_buffer.read(cx).to_proto(cx);
2604 let ops = cx
2605 .background_executor()
2606 .block(base_buffer.read(cx).serialize_ops(None, cx));
2607 let mut buffer =
2608 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2609 buffer.apply_ops(
2610 ops.into_iter()
2611 .map(|op| proto::deserialize_operation(op).unwrap()),
2612 cx,
2613 );
2614 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2615 let network = network.clone();
2616 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2617 if let BufferEvent::Operation {
2618 operation,
2619 is_local: true,
2620 } = event
2621 {
2622 network.lock().broadcast(
2623 buffer.replica_id(),
2624 vec![proto::serialize_operation(operation)],
2625 );
2626 }
2627 })
2628 .detach();
2629 buffer
2630 });
2631
2632 buffers.push(buffer);
2633 replica_ids.push(i as ReplicaId);
2634 network.lock().add_peer(i as ReplicaId);
2635 log::info!("Adding initial peer with replica id {}", i);
2636 }
2637
2638 log::info!("initial text: {:?}", base_text);
2639
2640 let mut now = Instant::now();
2641 let mut mutation_count = operations;
2642 let mut next_diagnostic_id = 0;
2643 let mut active_selections = BTreeMap::default();
2644 loop {
2645 let replica_index = rng.gen_range(0..replica_ids.len());
2646 let replica_id = replica_ids[replica_index];
2647 let buffer = &mut buffers[replica_index];
2648 let mut new_buffer = None;
2649 match rng.gen_range(0..100) {
2650 0..=29 if mutation_count != 0 => {
2651 buffer.update(cx, |buffer, cx| {
2652 buffer.start_transaction_at(now);
2653 buffer.randomly_edit(&mut rng, 5, cx);
2654 buffer.end_transaction_at(now, cx);
2655 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2656 });
2657 mutation_count -= 1;
2658 }
2659 30..=39 if mutation_count != 0 => {
2660 buffer.update(cx, |buffer, cx| {
2661 if rng.gen_bool(0.2) {
2662 log::info!("peer {} clearing active selections", replica_id);
2663 active_selections.remove(&replica_id);
2664 buffer.remove_active_selections(cx);
2665 } else {
2666 let mut selections = Vec::new();
2667 for id in 0..rng.gen_range(1..=5) {
2668 let range = buffer.random_byte_range(0, &mut rng);
2669 selections.push(Selection {
2670 id,
2671 start: buffer.anchor_before(range.start),
2672 end: buffer.anchor_before(range.end),
2673 reversed: false,
2674 goal: SelectionGoal::None,
2675 });
2676 }
2677 let selections: Arc<[Selection<Anchor>]> = selections.into();
2678 log::info!(
2679 "peer {} setting active selections: {:?}",
2680 replica_id,
2681 selections
2682 );
2683 active_selections.insert(replica_id, selections.clone());
2684 buffer.set_active_selections(selections, false, Default::default(), cx);
2685 }
2686 });
2687 mutation_count -= 1;
2688 }
2689 40..=49 if mutation_count != 0 && replica_id == 0 => {
2690 let entry_count = rng.gen_range(1..=5);
2691 buffer.update(cx, |buffer, cx| {
2692 let diagnostics = DiagnosticSet::new(
2693 (0..entry_count).map(|_| {
2694 let range = buffer.random_byte_range(0, &mut rng);
2695 let range = range.to_point_utf16(buffer);
2696 let range = range.start..range.end;
2697 DiagnosticEntry {
2698 range,
2699 diagnostic: Diagnostic {
2700 message: post_inc(&mut next_diagnostic_id).to_string(),
2701 ..Default::default()
2702 },
2703 }
2704 }),
2705 buffer,
2706 );
2707 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2708 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2709 });
2710 mutation_count -= 1;
2711 }
2712 50..=59 if replica_ids.len() < max_peers => {
2713 let old_buffer_state = buffer.read(cx).to_proto(cx);
2714 let old_buffer_ops = cx
2715 .background_executor()
2716 .block(buffer.read(cx).serialize_ops(None, cx));
2717 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2718 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2719 .choose(&mut rng)
2720 .unwrap();
2721 log::info!(
2722 "Adding new replica {} (replicating from {})",
2723 new_replica_id,
2724 replica_id
2725 );
2726 new_buffer = Some(cx.new_model(|cx| {
2727 let mut new_buffer = Buffer::from_proto(
2728 new_replica_id,
2729 Capability::ReadWrite,
2730 old_buffer_state,
2731 None,
2732 )
2733 .unwrap();
2734 new_buffer.apply_ops(
2735 old_buffer_ops
2736 .into_iter()
2737 .map(|op| deserialize_operation(op).unwrap()),
2738 cx,
2739 );
2740 log::info!(
2741 "New replica {} text: {:?}",
2742 new_buffer.replica_id(),
2743 new_buffer.text()
2744 );
2745 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2746 let network = network.clone();
2747 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2748 if let BufferEvent::Operation {
2749 operation,
2750 is_local: true,
2751 } = event
2752 {
2753 network.lock().broadcast(
2754 buffer.replica_id(),
2755 vec![proto::serialize_operation(operation)],
2756 );
2757 }
2758 })
2759 .detach();
2760 new_buffer
2761 }));
2762 network.lock().replicate(replica_id, new_replica_id);
2763
2764 if new_replica_id as usize == replica_ids.len() {
2765 replica_ids.push(new_replica_id);
2766 } else {
2767 let new_buffer = new_buffer.take().unwrap();
2768 while network.lock().has_unreceived(new_replica_id) {
2769 let ops = network
2770 .lock()
2771 .receive(new_replica_id)
2772 .into_iter()
2773 .map(|op| proto::deserialize_operation(op).unwrap());
2774 if ops.len() > 0 {
2775 log::info!(
2776 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2777 new_replica_id,
2778 buffer.read(cx).version(),
2779 ops.len(),
2780 ops
2781 );
2782 new_buffer.update(cx, |new_buffer, cx| {
2783 new_buffer.apply_ops(ops, cx);
2784 });
2785 }
2786 }
2787 buffers[new_replica_id as usize] = new_buffer;
2788 }
2789 }
2790 60..=69 if mutation_count != 0 => {
2791 buffer.update(cx, |buffer, cx| {
2792 buffer.randomly_undo_redo(&mut rng, cx);
2793 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2794 });
2795 mutation_count -= 1;
2796 }
2797 _ if network.lock().has_unreceived(replica_id) => {
2798 let ops = network
2799 .lock()
2800 .receive(replica_id)
2801 .into_iter()
2802 .map(|op| proto::deserialize_operation(op).unwrap());
2803 if ops.len() > 0 {
2804 log::info!(
2805 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2806 replica_id,
2807 buffer.read(cx).version(),
2808 ops.len(),
2809 ops
2810 );
2811 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
2812 }
2813 }
2814 _ => {}
2815 }
2816
2817 now += Duration::from_millis(rng.gen_range(0..=200));
2818 buffers.extend(new_buffer);
2819
2820 for buffer in &buffers {
2821 buffer.read(cx).check_invariants();
2822 }
2823
2824 if mutation_count == 0 && network.lock().is_idle() {
2825 break;
2826 }
2827 }
2828
2829 let first_buffer = buffers[0].read(cx).snapshot();
2830 for buffer in &buffers[1..] {
2831 let buffer = buffer.read(cx).snapshot();
2832 assert_eq!(
2833 buffer.version(),
2834 first_buffer.version(),
2835 "Replica {} version != Replica 0 version",
2836 buffer.replica_id()
2837 );
2838 assert_eq!(
2839 buffer.text(),
2840 first_buffer.text(),
2841 "Replica {} text != Replica 0 text",
2842 buffer.replica_id()
2843 );
2844 assert_eq!(
2845 buffer
2846 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
2847 .collect::<Vec<_>>(),
2848 first_buffer
2849 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
2850 .collect::<Vec<_>>(),
2851 "Replica {} diagnostics != Replica 0 diagnostics",
2852 buffer.replica_id()
2853 );
2854 }
2855
2856 for buffer in &buffers {
2857 let buffer = buffer.read(cx).snapshot();
2858 let actual_remote_selections = buffer
2859 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
2860 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
2861 .collect::<Vec<_>>();
2862 let expected_remote_selections = active_selections
2863 .iter()
2864 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
2865 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
2866 .collect::<Vec<_>>();
2867 assert_eq!(
2868 actual_remote_selections,
2869 expected_remote_selections,
2870 "Replica {} remote selections != expected selections",
2871 buffer.replica_id()
2872 );
2873 }
2874}
2875
2876#[test]
2877fn test_contiguous_ranges() {
2878 assert_eq!(
2879 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
2880 &[1..4, 5..7, 9..13]
2881 );
2882
2883 // Respects the `max_len` parameter
2884 assert_eq!(
2885 contiguous_ranges(
2886 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
2887 3
2888 )
2889 .collect::<Vec<_>>(),
2890 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
2891 );
2892}
2893
2894#[gpui::test(iterations = 500)]
2895fn test_trailing_whitespace_ranges(mut rng: StdRng) {
2896 // Generate a random multi-line string containing
2897 // some lines with trailing whitespace.
2898 let mut text = String::new();
2899 for _ in 0..rng.gen_range(0..16) {
2900 for _ in 0..rng.gen_range(0..36) {
2901 text.push(match rng.gen_range(0..10) {
2902 0..=1 => ' ',
2903 3 => '\t',
2904 _ => rng.gen_range('a'..='z'),
2905 });
2906 }
2907 text.push('\n');
2908 }
2909
2910 match rng.gen_range(0..10) {
2911 // sometimes remove the last newline
2912 0..=1 => drop(text.pop()), //
2913
2914 // sometimes add extra newlines
2915 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
2916 _ => {}
2917 }
2918
2919 let rope = Rope::from(text.as_str());
2920 let actual_ranges = trailing_whitespace_ranges(&rope);
2921 let expected_ranges = TRAILING_WHITESPACE_REGEX
2922 .find_iter(&text)
2923 .map(|m| m.range())
2924 .collect::<Vec<_>>();
2925 assert_eq!(
2926 actual_ranges,
2927 expected_ranges,
2928 "wrong ranges for text lines:\n{:?}",
2929 text.split('\n').collect::<Vec<_>>()
2930 );
2931}
2932
2933fn ruby_lang() -> Language {
2934 Language::new(
2935 LanguageConfig {
2936 name: "Ruby".into(),
2937 matcher: LanguageMatcher {
2938 path_suffixes: vec!["rb".to_string()],
2939 ..Default::default()
2940 },
2941 line_comments: vec!["# ".into()],
2942 ..Default::default()
2943 },
2944 Some(tree_sitter_ruby::LANGUAGE.into()),
2945 )
2946 .with_indents_query(
2947 r#"
2948 (class "end" @end) @indent
2949 (method "end" @end) @indent
2950 (rescue) @outdent
2951 (then) @indent
2952 "#,
2953 )
2954 .unwrap()
2955}
2956
2957fn html_lang() -> Language {
2958 Language::new(
2959 LanguageConfig {
2960 name: LanguageName::new("HTML"),
2961 block_comment: Some(("<!--".into(), "-->".into())),
2962 ..Default::default()
2963 },
2964 Some(tree_sitter_html::language()),
2965 )
2966 .with_indents_query(
2967 "
2968 (element
2969 (start_tag) @start
2970 (end_tag)? @end) @indent
2971 ",
2972 )
2973 .unwrap()
2974 .with_injection_query(
2975 r#"
2976 (script_element
2977 (raw_text) @content
2978 (#set! "language" "javascript"))
2979 "#,
2980 )
2981 .unwrap()
2982}
2983
2984fn erb_lang() -> Language {
2985 Language::new(
2986 LanguageConfig {
2987 name: "ERB".into(),
2988 matcher: LanguageMatcher {
2989 path_suffixes: vec!["erb".to_string()],
2990 ..Default::default()
2991 },
2992 block_comment: Some(("<%#".into(), "%>".into())),
2993 ..Default::default()
2994 },
2995 Some(tree_sitter_embedded_template::LANGUAGE.into()),
2996 )
2997 .with_injection_query(
2998 r#"
2999 (
3000 (code) @content
3001 (#set! "language" "ruby")
3002 (#set! "combined")
3003 )
3004
3005 (
3006 (content) @content
3007 (#set! "language" "html")
3008 (#set! "combined")
3009 )
3010 "#,
3011 )
3012 .unwrap()
3013}
3014
3015fn rust_lang() -> Language {
3016 Language::new(
3017 LanguageConfig {
3018 name: "Rust".into(),
3019 matcher: LanguageMatcher {
3020 path_suffixes: vec!["rs".to_string()],
3021 ..Default::default()
3022 },
3023 ..Default::default()
3024 },
3025 Some(tree_sitter_rust::LANGUAGE.into()),
3026 )
3027 .with_indents_query(
3028 r#"
3029 (call_expression) @indent
3030 (field_expression) @indent
3031 (_ "(" ")" @end) @indent
3032 (_ "{" "}" @end) @indent
3033 "#,
3034 )
3035 .unwrap()
3036 .with_brackets_query(
3037 r#"
3038 ("{" @open "}" @close)
3039 "#,
3040 )
3041 .unwrap()
3042 .with_outline_query(
3043 r#"
3044 (line_comment) @annotation
3045
3046 (struct_item
3047 "struct" @context
3048 name: (_) @name) @item
3049 (enum_item
3050 "enum" @context
3051 name: (_) @name) @item
3052 (enum_variant
3053 name: (_) @name) @item
3054 (field_declaration
3055 name: (_) @name) @item
3056 (impl_item
3057 "impl" @context
3058 trait: (_)? @name
3059 "for"? @context
3060 type: (_) @name
3061 body: (_ "{" (_)* "}")) @item
3062 (function_item
3063 "fn" @context
3064 name: (_) @name) @item
3065 (mod_item
3066 "mod" @context
3067 name: (_) @name) @item
3068 "#,
3069 )
3070 .unwrap()
3071}
3072
3073fn json_lang() -> Language {
3074 Language::new(
3075 LanguageConfig {
3076 name: "Json".into(),
3077 matcher: LanguageMatcher {
3078 path_suffixes: vec!["js".to_string()],
3079 ..Default::default()
3080 },
3081 ..Default::default()
3082 },
3083 Some(tree_sitter_json::LANGUAGE.into()),
3084 )
3085}
3086
3087fn javascript_lang() -> Language {
3088 Language::new(
3089 LanguageConfig {
3090 name: "JavaScript".into(),
3091 ..Default::default()
3092 },
3093 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3094 )
3095 .with_brackets_query(
3096 r#"
3097 ("{" @open "}" @close)
3098 ("(" @open ")" @close)
3099 "#,
3100 )
3101 .unwrap()
3102 .with_indents_query(
3103 r#"
3104 (object "}" @end) @indent
3105 "#,
3106 )
3107 .unwrap()
3108}
3109
3110pub fn markdown_lang() -> Language {
3111 Language::new(
3112 LanguageConfig {
3113 name: "Markdown".into(),
3114 matcher: LanguageMatcher {
3115 path_suffixes: vec!["md".into()],
3116 ..Default::default()
3117 },
3118 ..Default::default()
3119 },
3120 Some(tree_sitter_md::LANGUAGE.into()),
3121 )
3122 .with_injection_query(
3123 r#"
3124 (fenced_code_block
3125 (info_string
3126 (language) @language)
3127 (code_fence_content) @content)
3128
3129 ((inline) @content
3130 (#set! "language" "markdown-inline"))
3131 "#,
3132 )
3133 .unwrap()
3134}
3135
3136pub fn markdown_inline_lang() -> Language {
3137 Language::new(
3138 LanguageConfig {
3139 name: "Markdown-Inline".into(),
3140 hidden: true,
3141 ..LanguageConfig::default()
3142 },
3143 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3144 )
3145 .with_highlights_query("(emphasis) @emphasis")
3146 .unwrap()
3147}
3148
3149fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3150 buffer.update(cx, |buffer, _| {
3151 let snapshot = buffer.snapshot();
3152 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3153 layers[0].node().to_sexp()
3154 })
3155}
3156
3157// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3158fn assert_bracket_pairs(
3159 selection_text: &'static str,
3160 bracket_pair_texts: Vec<&'static str>,
3161 language: Language,
3162 cx: &mut AppContext,
3163) {
3164 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3165 let buffer = cx.new_model(|cx| {
3166 Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
3167 });
3168 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3169
3170 let selection_range = selection_ranges[0].clone();
3171
3172 let bracket_pairs = bracket_pair_texts
3173 .into_iter()
3174 .map(|pair_text| {
3175 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3176 assert_eq!(bracket_text, expected_text);
3177 (ranges[0].clone(), ranges[1].clone())
3178 })
3179 .collect::<Vec<_>>();
3180
3181 assert_set_eq!(
3182 buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
3183 bracket_pairs
3184 );
3185}
3186
3187fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
3188 let settings_store = SettingsStore::test(cx);
3189 cx.set_global(settings_store);
3190 crate::init(cx);
3191 cx.update_global::<SettingsStore, _>(|settings, cx| {
3192 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3193 });
3194}