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