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_block_mode_multiple_adjacent_ranges(cx: &mut AppContext) {
1663 init_settings(cx, |_| {});
1664
1665 cx.new_model(|cx| {
1666 let (text, ranges_to_replace) = marked_text_ranges(
1667 &"
1668 mod numbers {
1669 «fn one() {
1670 1
1671 }
1672 »
1673 «fn two() {
1674 2
1675 }
1676 »
1677 «fn three() {
1678 3
1679 }
1680 »}
1681 "
1682 .unindent(),
1683 false,
1684 );
1685
1686 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1687
1688 buffer.edit(
1689 [
1690 (ranges_to_replace[0].clone(), "fn one() {\n 101\n}\n"),
1691 (ranges_to_replace[1].clone(), "fn two() {\n 102\n}\n"),
1692 (ranges_to_replace[2].clone(), "fn three() {\n 103\n}\n"),
1693 ],
1694 Some(AutoindentMode::Block {
1695 original_indent_columns: vec![0, 0, 0],
1696 }),
1697 cx,
1698 );
1699
1700 pretty_assertions::assert_eq!(
1701 buffer.text(),
1702 "
1703 mod numbers {
1704 fn one() {
1705 101
1706 }
1707
1708 fn two() {
1709 102
1710 }
1711
1712 fn three() {
1713 103
1714 }
1715 }
1716 "
1717 .unindent()
1718 );
1719
1720 buffer
1721 });
1722}
1723
1724#[gpui::test]
1725fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
1726 init_settings(cx, |_| {});
1727
1728 cx.new_model(|cx| {
1729 let text = "
1730 * one
1731 - a
1732 - b
1733 * two
1734 "
1735 .unindent();
1736
1737 let mut buffer = Buffer::local(text, cx).with_language(
1738 Arc::new(Language::new(
1739 LanguageConfig {
1740 name: "Markdown".into(),
1741 auto_indent_using_last_non_empty_line: false,
1742 ..Default::default()
1743 },
1744 Some(tree_sitter_json::LANGUAGE.into()),
1745 )),
1746 cx,
1747 );
1748 buffer.edit(
1749 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
1750 Some(AutoindentMode::EachLine),
1751 cx,
1752 );
1753 assert_eq!(
1754 buffer.text(),
1755 "
1756 * one
1757 - a
1758 - b
1759
1760 * two
1761 "
1762 .unindent()
1763 );
1764 buffer
1765 });
1766}
1767
1768#[gpui::test]
1769fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
1770 init_settings(cx, |settings| {
1771 settings.languages.extend([
1772 (
1773 "HTML".into(),
1774 LanguageSettingsContent {
1775 tab_size: Some(2.try_into().unwrap()),
1776 ..Default::default()
1777 },
1778 ),
1779 (
1780 "JavaScript".into(),
1781 LanguageSettingsContent {
1782 tab_size: Some(8.try_into().unwrap()),
1783 ..Default::default()
1784 },
1785 ),
1786 ])
1787 });
1788
1789 let html_language = Arc::new(html_lang());
1790
1791 let javascript_language = Arc::new(javascript_lang());
1792
1793 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1794 language_registry.add(html_language.clone());
1795 language_registry.add(javascript_language.clone());
1796
1797 cx.new_model(|cx| {
1798 let (text, ranges) = marked_text_ranges(
1799 &"
1800 <div>ˇ
1801 </div>
1802 <script>
1803 init({ˇ
1804 })
1805 </script>
1806 <span>ˇ
1807 </span>
1808 "
1809 .unindent(),
1810 false,
1811 );
1812
1813 let mut buffer = Buffer::local(text, cx);
1814 buffer.set_language_registry(language_registry);
1815 buffer.set_language(Some(html_language), cx);
1816 buffer.edit(
1817 ranges.into_iter().map(|range| (range, "\na")),
1818 Some(AutoindentMode::EachLine),
1819 cx,
1820 );
1821 assert_eq!(
1822 buffer.text(),
1823 "
1824 <div>
1825 a
1826 </div>
1827 <script>
1828 init({
1829 a
1830 })
1831 </script>
1832 <span>
1833 a
1834 </span>
1835 "
1836 .unindent()
1837 );
1838 buffer
1839 });
1840}
1841
1842#[gpui::test]
1843fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
1844 init_settings(cx, |settings| {
1845 settings.defaults.tab_size = Some(2.try_into().unwrap());
1846 });
1847
1848 cx.new_model(|cx| {
1849 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
1850
1851 let text = r#"
1852 class C
1853 def a(b, c)
1854 puts b
1855 puts c
1856 rescue
1857 puts "errored"
1858 exit 1
1859 end
1860 end
1861 "#
1862 .unindent();
1863
1864 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
1865
1866 assert_eq!(
1867 buffer.text(),
1868 r#"
1869 class C
1870 def a(b, c)
1871 puts b
1872 puts c
1873 rescue
1874 puts "errored"
1875 exit 1
1876 end
1877 end
1878 "#
1879 .unindent()
1880 );
1881
1882 buffer
1883 });
1884}
1885
1886#[gpui::test]
1887async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
1888 cx.update(|cx| init_settings(cx, |_| {}));
1889
1890 // First we insert some newlines to request an auto-indent (asynchronously).
1891 // Then we request that a preview tab be preserved for the new version, even though it's edited.
1892 let buffer = cx.new_model(|cx| {
1893 let text = "fn a() {}";
1894 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1895
1896 // This causes autoindent to be async.
1897 buffer.set_sync_parse_timeout(Duration::ZERO);
1898
1899 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1900 buffer.refresh_preview();
1901
1902 // Synchronously, we haven't auto-indented and we're still preserving the preview.
1903 assert_eq!(buffer.text(), "fn a() {\n\n}");
1904 assert!(buffer.preserve_preview());
1905 buffer
1906 });
1907
1908 // Now let the autoindent finish
1909 cx.executor().run_until_parked();
1910
1911 // The auto-indent applied, but didn't dismiss our preview
1912 buffer.update(cx, |buffer, cx| {
1913 assert_eq!(buffer.text(), "fn a() {\n \n}");
1914 assert!(buffer.preserve_preview());
1915
1916 // Edit inserting another line. It will autoindent async.
1917 // Then refresh the preview version.
1918 buffer.edit(
1919 [(Point::new(1, 4)..Point::new(1, 4), "\n")],
1920 Some(AutoindentMode::EachLine),
1921 cx,
1922 );
1923 buffer.refresh_preview();
1924 assert_eq!(buffer.text(), "fn a() {\n \n\n}");
1925 assert!(buffer.preserve_preview());
1926
1927 // Then perform another edit, this time without refreshing the preview version.
1928 buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
1929 // This causes the preview to not be preserved.
1930 assert!(!buffer.preserve_preview());
1931 });
1932
1933 // Let the async autoindent from the first edit finish.
1934 cx.executor().run_until_parked();
1935
1936 // The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
1937 buffer.update(cx, |buffer, _| {
1938 assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
1939 assert!(!buffer.preserve_preview());
1940 });
1941}
1942
1943#[gpui::test]
1944fn test_insert_empty_line(cx: &mut AppContext) {
1945 init_settings(cx, |_| {});
1946
1947 // Insert empty line at the beginning, requesting an empty line above
1948 cx.new_model(|cx| {
1949 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1950 let point = buffer.insert_empty_line(Point::new(0, 0), true, false, cx);
1951 assert_eq!(buffer.text(), "\nabc\ndef\nghi");
1952 assert_eq!(point, Point::new(0, 0));
1953 buffer
1954 });
1955
1956 // Insert empty line at the beginning, requesting an empty line above and below
1957 cx.new_model(|cx| {
1958 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1959 let point = buffer.insert_empty_line(Point::new(0, 0), true, true, cx);
1960 assert_eq!(buffer.text(), "\n\nabc\ndef\nghi");
1961 assert_eq!(point, Point::new(0, 0));
1962 buffer
1963 });
1964
1965 // Insert empty line at the start of a line, requesting empty lines above and below
1966 cx.new_model(|cx| {
1967 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
1968 let point = buffer.insert_empty_line(Point::new(2, 0), true, true, cx);
1969 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi");
1970 assert_eq!(point, Point::new(3, 0));
1971 buffer
1972 });
1973
1974 // Insert empty line in the middle of a line, requesting empty lines above and below
1975 cx.new_model(|cx| {
1976 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
1977 let point = buffer.insert_empty_line(Point::new(1, 3), true, true, cx);
1978 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi\njkl");
1979 assert_eq!(point, Point::new(3, 0));
1980 buffer
1981 });
1982
1983 // Insert empty line in the middle of a line, requesting empty line above only
1984 cx.new_model(|cx| {
1985 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
1986 let point = buffer.insert_empty_line(Point::new(1, 3), true, false, cx);
1987 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
1988 assert_eq!(point, Point::new(3, 0));
1989 buffer
1990 });
1991
1992 // Insert empty line in the middle of a line, requesting empty line below only
1993 cx.new_model(|cx| {
1994 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
1995 let point = buffer.insert_empty_line(Point::new(1, 3), false, true, cx);
1996 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
1997 assert_eq!(point, Point::new(2, 0));
1998 buffer
1999 });
2000
2001 // Insert empty line at the end, requesting empty lines above and below
2002 cx.new_model(|cx| {
2003 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2004 let point = buffer.insert_empty_line(Point::new(2, 3), true, true, cx);
2005 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n\n");
2006 assert_eq!(point, Point::new(4, 0));
2007 buffer
2008 });
2009
2010 // Insert empty line at the end, requesting empty line above only
2011 cx.new_model(|cx| {
2012 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2013 let point = buffer.insert_empty_line(Point::new(2, 3), true, false, cx);
2014 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2015 assert_eq!(point, Point::new(4, 0));
2016 buffer
2017 });
2018
2019 // Insert empty line at the end, requesting empty line below only
2020 cx.new_model(|cx| {
2021 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2022 let point = buffer.insert_empty_line(Point::new(2, 3), false, true, cx);
2023 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2024 assert_eq!(point, Point::new(3, 0));
2025 buffer
2026 });
2027}
2028
2029#[gpui::test]
2030fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
2031 init_settings(cx, |_| {});
2032
2033 cx.new_model(|cx| {
2034 let language = Language::new(
2035 LanguageConfig {
2036 name: "JavaScript".into(),
2037 line_comments: vec!["// ".into()],
2038 brackets: BracketPairConfig {
2039 pairs: vec![
2040 BracketPair {
2041 start: "{".into(),
2042 end: "}".into(),
2043 close: true,
2044 surround: true,
2045 newline: false,
2046 },
2047 BracketPair {
2048 start: "'".into(),
2049 end: "'".into(),
2050 close: true,
2051 surround: true,
2052 newline: false,
2053 },
2054 ],
2055 disabled_scopes_by_bracket_ix: vec![
2056 Vec::new(), //
2057 vec!["string".into()],
2058 ],
2059 },
2060 overrides: [(
2061 "element".into(),
2062 LanguageConfigOverride {
2063 line_comments: Override::Remove { remove: true },
2064 block_comment: Override::Set(("{/*".into(), "*/}".into())),
2065 ..Default::default()
2066 },
2067 )]
2068 .into_iter()
2069 .collect(),
2070 ..Default::default()
2071 },
2072 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2073 )
2074 .with_override_query(
2075 r#"
2076 (jsx_element) @element
2077 (string) @string
2078 [
2079 (jsx_opening_element)
2080 (jsx_closing_element)
2081 (jsx_expression)
2082 ] @default
2083 "#,
2084 )
2085 .unwrap();
2086
2087 let text = r#"
2088 a["b"] = <C d="e">
2089 <F></F>
2090 { g() }
2091 </C>;
2092 "#
2093 .unindent();
2094
2095 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2096 let snapshot = buffer.snapshot();
2097
2098 let config = snapshot.language_scope_at(0).unwrap();
2099 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2100 // Both bracket pairs are enabled
2101 assert_eq!(
2102 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2103 &[true, true]
2104 );
2105
2106 let string_config = snapshot
2107 .language_scope_at(text.find("b\"").unwrap())
2108 .unwrap();
2109 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2110 // Second bracket pair is disabled
2111 assert_eq!(
2112 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2113 &[true, false]
2114 );
2115
2116 // In between JSX tags: use the `element` override.
2117 let element_config = snapshot
2118 .language_scope_at(text.find("<F>").unwrap())
2119 .unwrap();
2120 // TODO nested blocks after newlines are captured with all whitespaces
2121 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2122 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2123 // assert_eq!(
2124 // element_config.block_comment_delimiters(),
2125 // Some((&"{/*".into(), &"*/}".into()))
2126 // );
2127 assert_eq!(
2128 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2129 &[true, true]
2130 );
2131
2132 // Within a JSX tag: use the default config.
2133 let tag_config = snapshot
2134 .language_scope_at(text.find(" d=").unwrap() + 1)
2135 .unwrap();
2136 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2137 assert_eq!(
2138 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2139 &[true, true]
2140 );
2141
2142 // In a JSX expression: use the default config.
2143 let expression_in_element_config = snapshot
2144 .language_scope_at(text.find('{').unwrap() + 1)
2145 .unwrap();
2146 assert_eq!(
2147 expression_in_element_config.line_comment_prefixes(),
2148 &[Arc::from("// ")]
2149 );
2150 assert_eq!(
2151 expression_in_element_config
2152 .brackets()
2153 .map(|e| e.1)
2154 .collect::<Vec<_>>(),
2155 &[true, true]
2156 );
2157
2158 buffer
2159 });
2160}
2161
2162#[gpui::test]
2163fn test_language_scope_at_with_rust(cx: &mut AppContext) {
2164 init_settings(cx, |_| {});
2165
2166 cx.new_model(|cx| {
2167 let language = Language::new(
2168 LanguageConfig {
2169 name: "Rust".into(),
2170 brackets: BracketPairConfig {
2171 pairs: vec![
2172 BracketPair {
2173 start: "{".into(),
2174 end: "}".into(),
2175 close: true,
2176 surround: true,
2177 newline: false,
2178 },
2179 BracketPair {
2180 start: "'".into(),
2181 end: "'".into(),
2182 close: true,
2183 surround: true,
2184 newline: false,
2185 },
2186 ],
2187 disabled_scopes_by_bracket_ix: vec![
2188 Vec::new(), //
2189 vec!["string".into()],
2190 ],
2191 },
2192 ..Default::default()
2193 },
2194 Some(tree_sitter_rust::LANGUAGE.into()),
2195 )
2196 .with_override_query(
2197 r#"
2198 (string_literal) @string
2199 "#,
2200 )
2201 .unwrap();
2202
2203 let text = r#"
2204 const S: &'static str = "hello";
2205 "#
2206 .unindent();
2207
2208 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2209 let snapshot = buffer.snapshot();
2210
2211 // By default, all brackets are enabled
2212 let config = snapshot.language_scope_at(0).unwrap();
2213 assert_eq!(
2214 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2215 &[true, true]
2216 );
2217
2218 // Within a string, the quotation brackets are disabled.
2219 let string_config = snapshot
2220 .language_scope_at(text.find("ello").unwrap())
2221 .unwrap();
2222 assert_eq!(
2223 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2224 &[true, false]
2225 );
2226
2227 buffer
2228 });
2229}
2230
2231#[gpui::test]
2232fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
2233 init_settings(cx, |_| {});
2234
2235 cx.new_model(|cx| {
2236 let text = r#"
2237 <ol>
2238 <% people.each do |person| %>
2239 <li>
2240 <%= person.name %>
2241 </li>
2242 <% end %>
2243 </ol>
2244 "#
2245 .unindent();
2246
2247 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2248 language_registry.add(Arc::new(ruby_lang()));
2249 language_registry.add(Arc::new(html_lang()));
2250 language_registry.add(Arc::new(erb_lang()));
2251
2252 let mut buffer = Buffer::local(text, cx);
2253 buffer.set_language_registry(language_registry.clone());
2254 buffer.set_language(
2255 language_registry
2256 .language_for_name("ERB")
2257 .now_or_never()
2258 .unwrap()
2259 .ok(),
2260 cx,
2261 );
2262
2263 let snapshot = buffer.snapshot();
2264 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2265 assert_eq!(html_config.line_comment_prefixes(), &[]);
2266 assert_eq!(
2267 html_config.block_comment_delimiters(),
2268 Some((&"<!--".into(), &"-->".into()))
2269 );
2270
2271 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2272 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2273 assert_eq!(ruby_config.block_comment_delimiters(), None);
2274
2275 buffer
2276 });
2277}
2278
2279#[gpui::test]
2280fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
2281 init_settings(cx, |_| {});
2282
2283 cx.new_model(|cx| {
2284 let text = r#"
2285 this is an *emphasized* word.
2286 "#
2287 .unindent();
2288
2289 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2290 language_registry.add(Arc::new(markdown_lang()));
2291 language_registry.add(Arc::new(markdown_inline_lang()));
2292
2293 let mut buffer = Buffer::local(text, cx);
2294 buffer.set_language_registry(language_registry.clone());
2295 buffer.set_language(
2296 language_registry
2297 .language_for_name("Markdown")
2298 .now_or_never()
2299 .unwrap()
2300 .ok(),
2301 cx,
2302 );
2303
2304 let snapshot = buffer.snapshot();
2305
2306 for point in [Point::new(0, 4), Point::new(0, 16)] {
2307 let config = snapshot.language_scope_at(point).unwrap();
2308 assert_eq!(config.language_name(), "Markdown".into());
2309
2310 let language = snapshot.language_at(point).unwrap();
2311 assert_eq!(language.name().0.as_ref(), "Markdown");
2312 }
2313
2314 buffer
2315 });
2316}
2317
2318#[gpui::test]
2319fn test_serialization(cx: &mut gpui::AppContext) {
2320 let mut now = Instant::now();
2321
2322 let buffer1 = cx.new_model(|cx| {
2323 let mut buffer = Buffer::local("abc", cx);
2324 buffer.edit([(3..3, "D")], None, cx);
2325
2326 now += Duration::from_secs(1);
2327 buffer.start_transaction_at(now);
2328 buffer.edit([(4..4, "E")], None, cx);
2329 buffer.end_transaction_at(now, cx);
2330 assert_eq!(buffer.text(), "abcDE");
2331
2332 buffer.undo(cx);
2333 assert_eq!(buffer.text(), "abcD");
2334
2335 buffer.edit([(4..4, "F")], None, cx);
2336 assert_eq!(buffer.text(), "abcDF");
2337 buffer
2338 });
2339 assert_eq!(buffer1.read(cx).text(), "abcDF");
2340
2341 let state = buffer1.read(cx).to_proto(cx);
2342 let ops = cx
2343 .background_executor()
2344 .block(buffer1.read(cx).serialize_ops(None, cx));
2345 let buffer2 = cx.new_model(|cx| {
2346 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2347 buffer.apply_ops(
2348 ops.into_iter()
2349 .map(|op| proto::deserialize_operation(op).unwrap()),
2350 cx,
2351 );
2352 buffer
2353 });
2354 assert_eq!(buffer2.read(cx).text(), "abcDF");
2355}
2356
2357#[gpui::test]
2358async fn test_find_matching_indent(cx: &mut TestAppContext) {
2359 cx.update(|cx| init_settings(cx, |_| {}));
2360
2361 async fn enclosing_indent(
2362 text: impl Into<String>,
2363 buffer_row: u32,
2364 cx: &mut TestAppContext,
2365 ) -> Option<(Range<u32>, LineIndent)> {
2366 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
2367 let snapshot = cx.read(|cx| buffer.read(cx).snapshot());
2368 snapshot.enclosing_indent(buffer_row).await
2369 }
2370
2371 assert_eq!(
2372 enclosing_indent(
2373 "
2374 fn b() {
2375 if c {
2376 let d = 2;
2377 }
2378 }"
2379 .unindent(),
2380 1,
2381 cx,
2382 )
2383 .await,
2384 Some((
2385 1..2,
2386 LineIndent {
2387 tabs: 0,
2388 spaces: 4,
2389 line_blank: false,
2390 }
2391 ))
2392 );
2393
2394 assert_eq!(
2395 enclosing_indent(
2396 "
2397 fn b() {
2398 if c {
2399 let d = 2;
2400 }
2401 }"
2402 .unindent(),
2403 2,
2404 cx,
2405 )
2406 .await,
2407 Some((
2408 1..2,
2409 LineIndent {
2410 tabs: 0,
2411 spaces: 4,
2412 line_blank: false,
2413 }
2414 ))
2415 );
2416
2417 assert_eq!(
2418 enclosing_indent(
2419 "
2420 fn b() {
2421 if c {
2422 let d = 2;
2423
2424 let e = 5;
2425 }
2426 }"
2427 .unindent(),
2428 3,
2429 cx,
2430 )
2431 .await,
2432 Some((
2433 1..4,
2434 LineIndent {
2435 tabs: 0,
2436 spaces: 4,
2437 line_blank: false,
2438 }
2439 ))
2440 );
2441}
2442
2443#[gpui::test]
2444fn test_branch_and_merge(cx: &mut TestAppContext) {
2445 cx.update(|cx| init_settings(cx, |_| {}));
2446
2447 let base = cx.new_model(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2448
2449 // Create a remote replica of the base buffer.
2450 let base_replica = cx.new_model(|cx| {
2451 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2452 });
2453 base.update(cx, |_buffer, cx| {
2454 cx.subscribe(&base_replica, |this, _, event, cx| {
2455 if let BufferEvent::Operation {
2456 operation,
2457 is_local: true,
2458 } = event
2459 {
2460 this.apply_ops([operation.clone()], cx);
2461 }
2462 })
2463 .detach();
2464 });
2465
2466 // Create a branch, which initially has the same state as the base buffer.
2467 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2468 branch.read_with(cx, |buffer, _| {
2469 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2470 });
2471
2472 // Edits to the branch are not applied to the base.
2473 branch.update(cx, |buffer, cx| {
2474 buffer.edit(
2475 [
2476 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2477 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2478 ],
2479 None,
2480 cx,
2481 )
2482 });
2483 branch.read_with(cx, |buffer, cx| {
2484 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2485 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2486 });
2487
2488 // Convert from branch buffer ranges to the corresoponing ranges in the
2489 // base buffer.
2490 branch.read_with(cx, |buffer, cx| {
2491 assert_eq!(
2492 buffer.range_to_version(4..7, &base.read(cx).version()),
2493 4..4
2494 );
2495 assert_eq!(
2496 buffer.range_to_version(2..9, &base.read(cx).version()),
2497 2..5
2498 );
2499 });
2500
2501 // The branch buffer maintains a diff with respect to its base buffer.
2502 start_recalculating_diff(&branch, cx);
2503 cx.run_until_parked();
2504 assert_diff_hunks(
2505 &branch,
2506 cx,
2507 &[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
2508 );
2509
2510 // Edits to the base are applied to the branch.
2511 base.update(cx, |buffer, cx| {
2512 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2513 });
2514 branch.read_with(cx, |buffer, cx| {
2515 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2516 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2517 });
2518
2519 // Until the git diff recalculation is complete, the git diff references
2520 // the previous content of the base buffer, so that it stays in sync.
2521 start_recalculating_diff(&branch, cx);
2522 assert_diff_hunks(
2523 &branch,
2524 cx,
2525 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2526 );
2527 cx.run_until_parked();
2528 assert_diff_hunks(
2529 &branch,
2530 cx,
2531 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2532 );
2533
2534 // Edits to any replica of the base are applied to the branch.
2535 base_replica.update(cx, |buffer, cx| {
2536 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2537 });
2538 branch.read_with(cx, |buffer, cx| {
2539 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2540 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2541 });
2542
2543 // Merging the branch applies all of its changes to the base.
2544 branch.update(cx, |buffer, cx| {
2545 buffer.merge_into_base(Vec::new(), cx);
2546 });
2547
2548 branch.update(cx, |buffer, cx| {
2549 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2550 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2551 });
2552}
2553
2554#[gpui::test]
2555fn test_merge_into_base(cx: &mut TestAppContext) {
2556 cx.update(|cx| init_settings(cx, |_| {}));
2557
2558 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2559 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2560
2561 // Make 3 edits, merge one into the base.
2562 branch.update(cx, |branch, cx| {
2563 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2564 branch.merge_into_base(vec![5..8], cx);
2565 });
2566
2567 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2568 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2569
2570 // Undo the one already-merged edit. Merge that into the base.
2571 branch.update(cx, |branch, cx| {
2572 branch.edit([(7..9, "hi")], None, cx);
2573 branch.merge_into_base(vec![5..8], cx);
2574 });
2575 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2576
2577 // Merge an insertion into the base.
2578 branch.update(cx, |branch, cx| {
2579 branch.merge_into_base(vec![11..11], cx);
2580 });
2581
2582 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2583 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2584
2585 // Deleted the inserted text and merge that into the base.
2586 branch.update(cx, |branch, cx| {
2587 branch.edit([(11..14, "")], None, cx);
2588 branch.merge_into_base(vec![10..11], cx);
2589 });
2590
2591 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2592}
2593
2594#[gpui::test]
2595fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2596 cx.update(|cx| init_settings(cx, |_| {}));
2597
2598 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2599 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2600
2601 // Make 2 edits, merge one into the base.
2602 branch.update(cx, |branch, cx| {
2603 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2604 branch.merge_into_base(vec![7..7], cx);
2605 });
2606 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2607 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2608
2609 // Undo the merge in the base buffer.
2610 base.update(cx, |base, cx| {
2611 base.undo(cx);
2612 });
2613 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2614 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2615
2616 // Merge that operation into the base again.
2617 branch.update(cx, |branch, cx| {
2618 branch.merge_into_base(vec![7..7], cx);
2619 });
2620 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2621 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2622}
2623
2624fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
2625 buffer
2626 .update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
2627 .detach();
2628}
2629
2630#[track_caller]
2631fn assert_diff_hunks(
2632 buffer: &Model<Buffer>,
2633 cx: &mut TestAppContext,
2634 expected_hunks: &[(Range<u32>, &str, &str)],
2635) {
2636 let (snapshot, diff_base) = buffer.read_with(cx, |buffer, _| {
2637 (buffer.snapshot(), buffer.diff_base().unwrap().to_string())
2638 });
2639 assert_hunks(
2640 snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
2641 &snapshot,
2642 &diff_base,
2643 expected_hunks,
2644 );
2645}
2646
2647#[gpui::test(iterations = 100)]
2648fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
2649 let min_peers = env::var("MIN_PEERS")
2650 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2651 .unwrap_or(1);
2652 let max_peers = env::var("MAX_PEERS")
2653 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2654 .unwrap_or(5);
2655 let operations = env::var("OPERATIONS")
2656 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2657 .unwrap_or(10);
2658
2659 let base_text_len = rng.gen_range(0..10);
2660 let base_text = RandomCharIter::new(&mut rng)
2661 .take(base_text_len)
2662 .collect::<String>();
2663 let mut replica_ids = Vec::new();
2664 let mut buffers = Vec::new();
2665 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2666 let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
2667
2668 for i in 0..rng.gen_range(min_peers..=max_peers) {
2669 let buffer = cx.new_model(|cx| {
2670 let state = base_buffer.read(cx).to_proto(cx);
2671 let ops = cx
2672 .background_executor()
2673 .block(base_buffer.read(cx).serialize_ops(None, cx));
2674 let mut buffer =
2675 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2676 buffer.apply_ops(
2677 ops.into_iter()
2678 .map(|op| proto::deserialize_operation(op).unwrap()),
2679 cx,
2680 );
2681 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2682 let network = network.clone();
2683 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2684 if let BufferEvent::Operation {
2685 operation,
2686 is_local: true,
2687 } = event
2688 {
2689 network.lock().broadcast(
2690 buffer.replica_id(),
2691 vec![proto::serialize_operation(operation)],
2692 );
2693 }
2694 })
2695 .detach();
2696 buffer
2697 });
2698
2699 buffers.push(buffer);
2700 replica_ids.push(i as ReplicaId);
2701 network.lock().add_peer(i as ReplicaId);
2702 log::info!("Adding initial peer with replica id {}", i);
2703 }
2704
2705 log::info!("initial text: {:?}", base_text);
2706
2707 let mut now = Instant::now();
2708 let mut mutation_count = operations;
2709 let mut next_diagnostic_id = 0;
2710 let mut active_selections = BTreeMap::default();
2711 loop {
2712 let replica_index = rng.gen_range(0..replica_ids.len());
2713 let replica_id = replica_ids[replica_index];
2714 let buffer = &mut buffers[replica_index];
2715 let mut new_buffer = None;
2716 match rng.gen_range(0..100) {
2717 0..=29 if mutation_count != 0 => {
2718 buffer.update(cx, |buffer, cx| {
2719 buffer.start_transaction_at(now);
2720 buffer.randomly_edit(&mut rng, 5, cx);
2721 buffer.end_transaction_at(now, cx);
2722 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2723 });
2724 mutation_count -= 1;
2725 }
2726 30..=39 if mutation_count != 0 => {
2727 buffer.update(cx, |buffer, cx| {
2728 if rng.gen_bool(0.2) {
2729 log::info!("peer {} clearing active selections", replica_id);
2730 active_selections.remove(&replica_id);
2731 buffer.remove_active_selections(cx);
2732 } else {
2733 let mut selections = Vec::new();
2734 for id in 0..rng.gen_range(1..=5) {
2735 let range = buffer.random_byte_range(0, &mut rng);
2736 selections.push(Selection {
2737 id,
2738 start: buffer.anchor_before(range.start),
2739 end: buffer.anchor_before(range.end),
2740 reversed: false,
2741 goal: SelectionGoal::None,
2742 });
2743 }
2744 let selections: Arc<[Selection<Anchor>]> = selections.into();
2745 log::info!(
2746 "peer {} setting active selections: {:?}",
2747 replica_id,
2748 selections
2749 );
2750 active_selections.insert(replica_id, selections.clone());
2751 buffer.set_active_selections(selections, false, Default::default(), cx);
2752 }
2753 });
2754 mutation_count -= 1;
2755 }
2756 40..=49 if mutation_count != 0 && replica_id == 0 => {
2757 let entry_count = rng.gen_range(1..=5);
2758 buffer.update(cx, |buffer, cx| {
2759 let diagnostics = DiagnosticSet::new(
2760 (0..entry_count).map(|_| {
2761 let range = buffer.random_byte_range(0, &mut rng);
2762 let range = range.to_point_utf16(buffer);
2763 let range = range.start..range.end;
2764 DiagnosticEntry {
2765 range,
2766 diagnostic: Diagnostic {
2767 message: post_inc(&mut next_diagnostic_id).to_string(),
2768 ..Default::default()
2769 },
2770 }
2771 }),
2772 buffer,
2773 );
2774 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2775 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2776 });
2777 mutation_count -= 1;
2778 }
2779 50..=59 if replica_ids.len() < max_peers => {
2780 let old_buffer_state = buffer.read(cx).to_proto(cx);
2781 let old_buffer_ops = cx
2782 .background_executor()
2783 .block(buffer.read(cx).serialize_ops(None, cx));
2784 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2785 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2786 .choose(&mut rng)
2787 .unwrap();
2788 log::info!(
2789 "Adding new replica {} (replicating from {})",
2790 new_replica_id,
2791 replica_id
2792 );
2793 new_buffer = Some(cx.new_model(|cx| {
2794 let mut new_buffer = Buffer::from_proto(
2795 new_replica_id,
2796 Capability::ReadWrite,
2797 old_buffer_state,
2798 None,
2799 )
2800 .unwrap();
2801 new_buffer.apply_ops(
2802 old_buffer_ops
2803 .into_iter()
2804 .map(|op| deserialize_operation(op).unwrap()),
2805 cx,
2806 );
2807 log::info!(
2808 "New replica {} text: {:?}",
2809 new_buffer.replica_id(),
2810 new_buffer.text()
2811 );
2812 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2813 let network = network.clone();
2814 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2815 if let BufferEvent::Operation {
2816 operation,
2817 is_local: true,
2818 } = event
2819 {
2820 network.lock().broadcast(
2821 buffer.replica_id(),
2822 vec![proto::serialize_operation(operation)],
2823 );
2824 }
2825 })
2826 .detach();
2827 new_buffer
2828 }));
2829 network.lock().replicate(replica_id, new_replica_id);
2830
2831 if new_replica_id as usize == replica_ids.len() {
2832 replica_ids.push(new_replica_id);
2833 } else {
2834 let new_buffer = new_buffer.take().unwrap();
2835 while network.lock().has_unreceived(new_replica_id) {
2836 let ops = network
2837 .lock()
2838 .receive(new_replica_id)
2839 .into_iter()
2840 .map(|op| proto::deserialize_operation(op).unwrap());
2841 if ops.len() > 0 {
2842 log::info!(
2843 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2844 new_replica_id,
2845 buffer.read(cx).version(),
2846 ops.len(),
2847 ops
2848 );
2849 new_buffer.update(cx, |new_buffer, cx| {
2850 new_buffer.apply_ops(ops, cx);
2851 });
2852 }
2853 }
2854 buffers[new_replica_id as usize] = new_buffer;
2855 }
2856 }
2857 60..=69 if mutation_count != 0 => {
2858 buffer.update(cx, |buffer, cx| {
2859 buffer.randomly_undo_redo(&mut rng, cx);
2860 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2861 });
2862 mutation_count -= 1;
2863 }
2864 _ if network.lock().has_unreceived(replica_id) => {
2865 let ops = network
2866 .lock()
2867 .receive(replica_id)
2868 .into_iter()
2869 .map(|op| proto::deserialize_operation(op).unwrap());
2870 if ops.len() > 0 {
2871 log::info!(
2872 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2873 replica_id,
2874 buffer.read(cx).version(),
2875 ops.len(),
2876 ops
2877 );
2878 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
2879 }
2880 }
2881 _ => {}
2882 }
2883
2884 now += Duration::from_millis(rng.gen_range(0..=200));
2885 buffers.extend(new_buffer);
2886
2887 for buffer in &buffers {
2888 buffer.read(cx).check_invariants();
2889 }
2890
2891 if mutation_count == 0 && network.lock().is_idle() {
2892 break;
2893 }
2894 }
2895
2896 let first_buffer = buffers[0].read(cx).snapshot();
2897 for buffer in &buffers[1..] {
2898 let buffer = buffer.read(cx).snapshot();
2899 assert_eq!(
2900 buffer.version(),
2901 first_buffer.version(),
2902 "Replica {} version != Replica 0 version",
2903 buffer.replica_id()
2904 );
2905 assert_eq!(
2906 buffer.text(),
2907 first_buffer.text(),
2908 "Replica {} text != Replica 0 text",
2909 buffer.replica_id()
2910 );
2911 assert_eq!(
2912 buffer
2913 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
2914 .collect::<Vec<_>>(),
2915 first_buffer
2916 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
2917 .collect::<Vec<_>>(),
2918 "Replica {} diagnostics != Replica 0 diagnostics",
2919 buffer.replica_id()
2920 );
2921 }
2922
2923 for buffer in &buffers {
2924 let buffer = buffer.read(cx).snapshot();
2925 let actual_remote_selections = buffer
2926 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
2927 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
2928 .collect::<Vec<_>>();
2929 let expected_remote_selections = active_selections
2930 .iter()
2931 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
2932 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
2933 .collect::<Vec<_>>();
2934 assert_eq!(
2935 actual_remote_selections,
2936 expected_remote_selections,
2937 "Replica {} remote selections != expected selections",
2938 buffer.replica_id()
2939 );
2940 }
2941}
2942
2943#[test]
2944fn test_contiguous_ranges() {
2945 assert_eq!(
2946 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
2947 &[1..4, 5..7, 9..13]
2948 );
2949
2950 // Respects the `max_len` parameter
2951 assert_eq!(
2952 contiguous_ranges(
2953 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
2954 3
2955 )
2956 .collect::<Vec<_>>(),
2957 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
2958 );
2959}
2960
2961#[gpui::test(iterations = 500)]
2962fn test_trailing_whitespace_ranges(mut rng: StdRng) {
2963 // Generate a random multi-line string containing
2964 // some lines with trailing whitespace.
2965 let mut text = String::new();
2966 for _ in 0..rng.gen_range(0..16) {
2967 for _ in 0..rng.gen_range(0..36) {
2968 text.push(match rng.gen_range(0..10) {
2969 0..=1 => ' ',
2970 3 => '\t',
2971 _ => rng.gen_range('a'..='z'),
2972 });
2973 }
2974 text.push('\n');
2975 }
2976
2977 match rng.gen_range(0..10) {
2978 // sometimes remove the last newline
2979 0..=1 => drop(text.pop()), //
2980
2981 // sometimes add extra newlines
2982 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
2983 _ => {}
2984 }
2985
2986 let rope = Rope::from(text.as_str());
2987 let actual_ranges = trailing_whitespace_ranges(&rope);
2988 let expected_ranges = TRAILING_WHITESPACE_REGEX
2989 .find_iter(&text)
2990 .map(|m| m.range())
2991 .collect::<Vec<_>>();
2992 assert_eq!(
2993 actual_ranges,
2994 expected_ranges,
2995 "wrong ranges for text lines:\n{:?}",
2996 text.split('\n').collect::<Vec<_>>()
2997 );
2998}
2999
3000fn ruby_lang() -> Language {
3001 Language::new(
3002 LanguageConfig {
3003 name: "Ruby".into(),
3004 matcher: LanguageMatcher {
3005 path_suffixes: vec!["rb".to_string()],
3006 ..Default::default()
3007 },
3008 line_comments: vec!["# ".into()],
3009 ..Default::default()
3010 },
3011 Some(tree_sitter_ruby::LANGUAGE.into()),
3012 )
3013 .with_indents_query(
3014 r#"
3015 (class "end" @end) @indent
3016 (method "end" @end) @indent
3017 (rescue) @outdent
3018 (then) @indent
3019 "#,
3020 )
3021 .unwrap()
3022}
3023
3024fn html_lang() -> Language {
3025 Language::new(
3026 LanguageConfig {
3027 name: LanguageName::new("HTML"),
3028 block_comment: Some(("<!--".into(), "-->".into())),
3029 ..Default::default()
3030 },
3031 Some(tree_sitter_html::language()),
3032 )
3033 .with_indents_query(
3034 "
3035 (element
3036 (start_tag) @start
3037 (end_tag)? @end) @indent
3038 ",
3039 )
3040 .unwrap()
3041 .with_injection_query(
3042 r#"
3043 (script_element
3044 (raw_text) @content
3045 (#set! "language" "javascript"))
3046 "#,
3047 )
3048 .unwrap()
3049}
3050
3051fn erb_lang() -> Language {
3052 Language::new(
3053 LanguageConfig {
3054 name: "ERB".into(),
3055 matcher: LanguageMatcher {
3056 path_suffixes: vec!["erb".to_string()],
3057 ..Default::default()
3058 },
3059 block_comment: Some(("<%#".into(), "%>".into())),
3060 ..Default::default()
3061 },
3062 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3063 )
3064 .with_injection_query(
3065 r#"
3066 (
3067 (code) @content
3068 (#set! "language" "ruby")
3069 (#set! "combined")
3070 )
3071
3072 (
3073 (content) @content
3074 (#set! "language" "html")
3075 (#set! "combined")
3076 )
3077 "#,
3078 )
3079 .unwrap()
3080}
3081
3082fn rust_lang() -> Language {
3083 Language::new(
3084 LanguageConfig {
3085 name: "Rust".into(),
3086 matcher: LanguageMatcher {
3087 path_suffixes: vec!["rs".to_string()],
3088 ..Default::default()
3089 },
3090 ..Default::default()
3091 },
3092 Some(tree_sitter_rust::LANGUAGE.into()),
3093 )
3094 .with_indents_query(
3095 r#"
3096 (call_expression) @indent
3097 (field_expression) @indent
3098 (_ "(" ")" @end) @indent
3099 (_ "{" "}" @end) @indent
3100 "#,
3101 )
3102 .unwrap()
3103 .with_brackets_query(
3104 r#"
3105 ("{" @open "}" @close)
3106 "#,
3107 )
3108 .unwrap()
3109 .with_outline_query(
3110 r#"
3111 (line_comment) @annotation
3112
3113 (struct_item
3114 "struct" @context
3115 name: (_) @name) @item
3116 (enum_item
3117 "enum" @context
3118 name: (_) @name) @item
3119 (enum_variant
3120 name: (_) @name) @item
3121 (field_declaration
3122 name: (_) @name) @item
3123 (impl_item
3124 "impl" @context
3125 trait: (_)? @name
3126 "for"? @context
3127 type: (_) @name
3128 body: (_ "{" (_)* "}")) @item
3129 (function_item
3130 "fn" @context
3131 name: (_) @name) @item
3132 (mod_item
3133 "mod" @context
3134 name: (_) @name) @item
3135 "#,
3136 )
3137 .unwrap()
3138}
3139
3140fn json_lang() -> Language {
3141 Language::new(
3142 LanguageConfig {
3143 name: "Json".into(),
3144 matcher: LanguageMatcher {
3145 path_suffixes: vec!["js".to_string()],
3146 ..Default::default()
3147 },
3148 ..Default::default()
3149 },
3150 Some(tree_sitter_json::LANGUAGE.into()),
3151 )
3152}
3153
3154fn javascript_lang() -> Language {
3155 Language::new(
3156 LanguageConfig {
3157 name: "JavaScript".into(),
3158 ..Default::default()
3159 },
3160 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3161 )
3162 .with_brackets_query(
3163 r#"
3164 ("{" @open "}" @close)
3165 ("(" @open ")" @close)
3166 "#,
3167 )
3168 .unwrap()
3169 .with_indents_query(
3170 r#"
3171 (object "}" @end) @indent
3172 "#,
3173 )
3174 .unwrap()
3175}
3176
3177pub fn markdown_lang() -> Language {
3178 Language::new(
3179 LanguageConfig {
3180 name: "Markdown".into(),
3181 matcher: LanguageMatcher {
3182 path_suffixes: vec!["md".into()],
3183 ..Default::default()
3184 },
3185 ..Default::default()
3186 },
3187 Some(tree_sitter_md::LANGUAGE.into()),
3188 )
3189 .with_injection_query(
3190 r#"
3191 (fenced_code_block
3192 (info_string
3193 (language) @language)
3194 (code_fence_content) @content)
3195
3196 ((inline) @content
3197 (#set! "language" "markdown-inline"))
3198 "#,
3199 )
3200 .unwrap()
3201}
3202
3203pub fn markdown_inline_lang() -> Language {
3204 Language::new(
3205 LanguageConfig {
3206 name: "Markdown-Inline".into(),
3207 hidden: true,
3208 ..LanguageConfig::default()
3209 },
3210 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3211 )
3212 .with_highlights_query("(emphasis) @emphasis")
3213 .unwrap()
3214}
3215
3216fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3217 buffer.update(cx, |buffer, _| {
3218 let snapshot = buffer.snapshot();
3219 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3220 layers[0].node().to_sexp()
3221 })
3222}
3223
3224// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3225fn assert_bracket_pairs(
3226 selection_text: &'static str,
3227 bracket_pair_texts: Vec<&'static str>,
3228 language: Language,
3229 cx: &mut AppContext,
3230) {
3231 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3232 let buffer = cx.new_model(|cx| {
3233 Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
3234 });
3235 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3236
3237 let selection_range = selection_ranges[0].clone();
3238
3239 let bracket_pairs = bracket_pair_texts
3240 .into_iter()
3241 .map(|pair_text| {
3242 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3243 assert_eq!(bracket_text, expected_text);
3244 (ranges[0].clone(), ranges[1].clone())
3245 })
3246 .collect::<Vec<_>>();
3247
3248 assert_set_eq!(
3249 buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
3250 bracket_pairs
3251 );
3252}
3253
3254fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
3255 let settings_store = SettingsStore::test(cx);
3256 cx.set_global(settings_store);
3257 crate::init(cx);
3258 cx.update_global::<SettingsStore, _>(|settings, cx| {
3259 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3260 });
3261}