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(), "comment".into()], // single quotes disabled
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 (comment) @comment.inclusive
2146 [
2147 (jsx_opening_element)
2148 (jsx_closing_element)
2149 (jsx_expression)
2150 ] @default
2151 "#,
2152 )
2153 .unwrap();
2154
2155 let text = r#"
2156 a["b"] = <C d="e">
2157 <F></F>
2158 { g() }
2159 </C>; // a comment
2160 "#
2161 .unindent();
2162
2163 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2164 let snapshot = buffer.snapshot();
2165
2166 let config = snapshot.language_scope_at(0).unwrap();
2167 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2168 // Both bracket pairs are enabled
2169 assert_eq!(
2170 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2171 &[true, true]
2172 );
2173
2174 let comment_config = snapshot
2175 .language_scope_at(text.find("comment").unwrap() + "comment".len())
2176 .unwrap();
2177 assert_eq!(
2178 comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2179 &[true, false]
2180 );
2181
2182 let string_config = snapshot
2183 .language_scope_at(text.find("b\"").unwrap())
2184 .unwrap();
2185 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2186 // Second bracket pair is disabled
2187 assert_eq!(
2188 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2189 &[true, false]
2190 );
2191
2192 // In between JSX tags: use the `element` override.
2193 let element_config = snapshot
2194 .language_scope_at(text.find("<F>").unwrap())
2195 .unwrap();
2196 // TODO nested blocks after newlines are captured with all whitespaces
2197 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2198 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2199 // assert_eq!(
2200 // element_config.block_comment_delimiters(),
2201 // Some((&"{/*".into(), &"*/}".into()))
2202 // );
2203 assert_eq!(
2204 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2205 &[true, true]
2206 );
2207
2208 // Within a JSX tag: use the default config.
2209 let tag_config = snapshot
2210 .language_scope_at(text.find(" d=").unwrap() + 1)
2211 .unwrap();
2212 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2213 assert_eq!(
2214 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2215 &[true, true]
2216 );
2217
2218 // In a JSX expression: use the default config.
2219 let expression_in_element_config = snapshot
2220 .language_scope_at(text.find('{').unwrap() + 1)
2221 .unwrap();
2222 assert_eq!(
2223 expression_in_element_config.line_comment_prefixes(),
2224 &[Arc::from("// ")]
2225 );
2226 assert_eq!(
2227 expression_in_element_config
2228 .brackets()
2229 .map(|e| e.1)
2230 .collect::<Vec<_>>(),
2231 &[true, true]
2232 );
2233
2234 buffer
2235 });
2236}
2237
2238#[gpui::test]
2239fn test_language_scope_at_with_rust(cx: &mut AppContext) {
2240 init_settings(cx, |_| {});
2241
2242 cx.new_model(|cx| {
2243 let language = Language::new(
2244 LanguageConfig {
2245 name: "Rust".into(),
2246 brackets: BracketPairConfig {
2247 pairs: vec![
2248 BracketPair {
2249 start: "{".into(),
2250 end: "}".into(),
2251 close: true,
2252 surround: true,
2253 newline: false,
2254 },
2255 BracketPair {
2256 start: "'".into(),
2257 end: "'".into(),
2258 close: true,
2259 surround: true,
2260 newline: false,
2261 },
2262 ],
2263 disabled_scopes_by_bracket_ix: vec![
2264 Vec::new(), //
2265 vec!["string".into()],
2266 ],
2267 },
2268 ..Default::default()
2269 },
2270 Some(tree_sitter_rust::LANGUAGE.into()),
2271 )
2272 .with_override_query(
2273 r#"
2274 (string_literal) @string
2275 "#,
2276 )
2277 .unwrap();
2278
2279 let text = r#"
2280 const S: &'static str = "hello";
2281 "#
2282 .unindent();
2283
2284 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2285 let snapshot = buffer.snapshot();
2286
2287 // By default, all brackets are enabled
2288 let config = snapshot.language_scope_at(0).unwrap();
2289 assert_eq!(
2290 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2291 &[true, true]
2292 );
2293
2294 // Within a string, the quotation brackets are disabled.
2295 let string_config = snapshot
2296 .language_scope_at(text.find("ello").unwrap())
2297 .unwrap();
2298 assert_eq!(
2299 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2300 &[true, false]
2301 );
2302
2303 buffer
2304 });
2305}
2306
2307#[gpui::test]
2308fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
2309 init_settings(cx, |_| {});
2310
2311 cx.new_model(|cx| {
2312 let text = r#"
2313 <ol>
2314 <% people.each do |person| %>
2315 <li>
2316 <%= person.name %>
2317 </li>
2318 <% end %>
2319 </ol>
2320 "#
2321 .unindent();
2322
2323 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2324 language_registry.add(Arc::new(ruby_lang()));
2325 language_registry.add(Arc::new(html_lang()));
2326 language_registry.add(Arc::new(erb_lang()));
2327
2328 let mut buffer = Buffer::local(text, cx);
2329 buffer.set_language_registry(language_registry.clone());
2330 buffer.set_language(
2331 language_registry
2332 .language_for_name("ERB")
2333 .now_or_never()
2334 .unwrap()
2335 .ok(),
2336 cx,
2337 );
2338
2339 let snapshot = buffer.snapshot();
2340 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2341 assert_eq!(html_config.line_comment_prefixes(), &[]);
2342 assert_eq!(
2343 html_config.block_comment_delimiters(),
2344 Some((&"<!--".into(), &"-->".into()))
2345 );
2346
2347 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2348 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2349 assert_eq!(ruby_config.block_comment_delimiters(), None);
2350
2351 buffer
2352 });
2353}
2354
2355#[gpui::test]
2356fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
2357 init_settings(cx, |_| {});
2358
2359 cx.new_model(|cx| {
2360 let text = r#"
2361 this is an *emphasized* word.
2362 "#
2363 .unindent();
2364
2365 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2366 language_registry.add(Arc::new(markdown_lang()));
2367 language_registry.add(Arc::new(markdown_inline_lang()));
2368
2369 let mut buffer = Buffer::local(text, cx);
2370 buffer.set_language_registry(language_registry.clone());
2371 buffer.set_language(
2372 language_registry
2373 .language_for_name("Markdown")
2374 .now_or_never()
2375 .unwrap()
2376 .ok(),
2377 cx,
2378 );
2379
2380 let snapshot = buffer.snapshot();
2381
2382 for point in [Point::new(0, 4), Point::new(0, 16)] {
2383 let config = snapshot.language_scope_at(point).unwrap();
2384 assert_eq!(config.language_name(), "Markdown".into());
2385
2386 let language = snapshot.language_at(point).unwrap();
2387 assert_eq!(language.name().0.as_ref(), "Markdown");
2388 }
2389
2390 buffer
2391 });
2392}
2393
2394#[gpui::test]
2395fn test_serialization(cx: &mut gpui::AppContext) {
2396 let mut now = Instant::now();
2397
2398 let buffer1 = cx.new_model(|cx| {
2399 let mut buffer = Buffer::local("abc", cx);
2400 buffer.edit([(3..3, "D")], None, cx);
2401
2402 now += Duration::from_secs(1);
2403 buffer.start_transaction_at(now);
2404 buffer.edit([(4..4, "E")], None, cx);
2405 buffer.end_transaction_at(now, cx);
2406 assert_eq!(buffer.text(), "abcDE");
2407
2408 buffer.undo(cx);
2409 assert_eq!(buffer.text(), "abcD");
2410
2411 buffer.edit([(4..4, "F")], None, cx);
2412 assert_eq!(buffer.text(), "abcDF");
2413 buffer
2414 });
2415 assert_eq!(buffer1.read(cx).text(), "abcDF");
2416
2417 let state = buffer1.read(cx).to_proto(cx);
2418 let ops = cx
2419 .background_executor()
2420 .block(buffer1.read(cx).serialize_ops(None, cx));
2421 let buffer2 = cx.new_model(|cx| {
2422 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2423 buffer.apply_ops(
2424 ops.into_iter()
2425 .map(|op| proto::deserialize_operation(op).unwrap()),
2426 cx,
2427 );
2428 buffer
2429 });
2430 assert_eq!(buffer2.read(cx).text(), "abcDF");
2431}
2432
2433#[gpui::test]
2434async fn test_find_matching_indent(cx: &mut TestAppContext) {
2435 cx.update(|cx| init_settings(cx, |_| {}));
2436
2437 async fn enclosing_indent(
2438 text: impl Into<String>,
2439 buffer_row: u32,
2440 cx: &mut TestAppContext,
2441 ) -> Option<(Range<u32>, LineIndent)> {
2442 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
2443 let snapshot = cx.read(|cx| buffer.read(cx).snapshot());
2444 snapshot.enclosing_indent(buffer_row).await
2445 }
2446
2447 assert_eq!(
2448 enclosing_indent(
2449 "
2450 fn b() {
2451 if c {
2452 let d = 2;
2453 }
2454 }"
2455 .unindent(),
2456 1,
2457 cx,
2458 )
2459 .await,
2460 Some((
2461 1..2,
2462 LineIndent {
2463 tabs: 0,
2464 spaces: 4,
2465 line_blank: false,
2466 }
2467 ))
2468 );
2469
2470 assert_eq!(
2471 enclosing_indent(
2472 "
2473 fn b() {
2474 if c {
2475 let d = 2;
2476 }
2477 }"
2478 .unindent(),
2479 2,
2480 cx,
2481 )
2482 .await,
2483 Some((
2484 1..2,
2485 LineIndent {
2486 tabs: 0,
2487 spaces: 4,
2488 line_blank: false,
2489 }
2490 ))
2491 );
2492
2493 assert_eq!(
2494 enclosing_indent(
2495 "
2496 fn b() {
2497 if c {
2498 let d = 2;
2499
2500 let e = 5;
2501 }
2502 }"
2503 .unindent(),
2504 3,
2505 cx,
2506 )
2507 .await,
2508 Some((
2509 1..4,
2510 LineIndent {
2511 tabs: 0,
2512 spaces: 4,
2513 line_blank: false,
2514 }
2515 ))
2516 );
2517}
2518
2519#[gpui::test]
2520fn test_branch_and_merge(cx: &mut TestAppContext) {
2521 cx.update(|cx| init_settings(cx, |_| {}));
2522
2523 let base = cx.new_model(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2524
2525 // Create a remote replica of the base buffer.
2526 let base_replica = cx.new_model(|cx| {
2527 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2528 });
2529 base.update(cx, |_buffer, cx| {
2530 cx.subscribe(&base_replica, |this, _, event, cx| {
2531 if let BufferEvent::Operation {
2532 operation,
2533 is_local: true,
2534 } = event
2535 {
2536 this.apply_ops([operation.clone()], cx);
2537 }
2538 })
2539 .detach();
2540 });
2541
2542 // Create a branch, which initially has the same state as the base buffer.
2543 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2544 branch.read_with(cx, |buffer, _| {
2545 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2546 });
2547
2548 // Edits to the branch are not applied to the base.
2549 branch.update(cx, |buffer, cx| {
2550 buffer.edit(
2551 [
2552 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2553 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2554 ],
2555 None,
2556 cx,
2557 )
2558 });
2559 branch.read_with(cx, |buffer, cx| {
2560 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2561 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2562 });
2563
2564 // Convert from branch buffer ranges to the corresoponing ranges in the
2565 // base buffer.
2566 branch.read_with(cx, |buffer, cx| {
2567 assert_eq!(
2568 buffer.range_to_version(4..7, &base.read(cx).version()),
2569 4..4
2570 );
2571 assert_eq!(
2572 buffer.range_to_version(2..9, &base.read(cx).version()),
2573 2..5
2574 );
2575 });
2576
2577 // The branch buffer maintains a diff with respect to its base buffer.
2578 start_recalculating_diff(&branch, cx);
2579 cx.run_until_parked();
2580 assert_diff_hunks(
2581 &branch,
2582 cx,
2583 &[(1..2, "", "1.5\n"), (3..4, "three\n", "THREE\n")],
2584 );
2585
2586 // Edits to the base are applied to the branch.
2587 base.update(cx, |buffer, cx| {
2588 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2589 });
2590 branch.read_with(cx, |buffer, cx| {
2591 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2592 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2593 });
2594
2595 // Until the git diff recalculation is complete, the git diff references
2596 // the previous content of the base buffer, so that it stays in sync.
2597 start_recalculating_diff(&branch, cx);
2598 assert_diff_hunks(
2599 &branch,
2600 cx,
2601 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2602 );
2603 cx.run_until_parked();
2604 assert_diff_hunks(
2605 &branch,
2606 cx,
2607 &[(2..3, "", "1.5\n"), (4..5, "three\n", "THREE\n")],
2608 );
2609
2610 // Edits to any replica of the base are applied to the branch.
2611 base_replica.update(cx, |buffer, cx| {
2612 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2613 });
2614 branch.read_with(cx, |buffer, cx| {
2615 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2616 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2617 });
2618
2619 // Merging the branch applies all of its changes to the base.
2620 branch.update(cx, |buffer, cx| {
2621 buffer.merge_into_base(Vec::new(), cx);
2622 });
2623
2624 branch.update(cx, |buffer, cx| {
2625 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2626 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2627 });
2628}
2629
2630#[gpui::test]
2631fn test_merge_into_base(cx: &mut TestAppContext) {
2632 cx.update(|cx| init_settings(cx, |_| {}));
2633
2634 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2635 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2636
2637 // Make 3 edits, merge one into the base.
2638 branch.update(cx, |branch, cx| {
2639 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2640 branch.merge_into_base(vec![5..8], cx);
2641 });
2642
2643 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2644 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2645
2646 // Undo the one already-merged edit. Merge that into the base.
2647 branch.update(cx, |branch, cx| {
2648 branch.edit([(7..9, "hi")], None, cx);
2649 branch.merge_into_base(vec![5..8], cx);
2650 });
2651 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2652
2653 // Merge an insertion into the base.
2654 branch.update(cx, |branch, cx| {
2655 branch.merge_into_base(vec![11..11], cx);
2656 });
2657
2658 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2659 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2660
2661 // Deleted the inserted text and merge that into the base.
2662 branch.update(cx, |branch, cx| {
2663 branch.edit([(11..14, "")], None, cx);
2664 branch.merge_into_base(vec![10..11], cx);
2665 });
2666
2667 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2668}
2669
2670#[gpui::test]
2671fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2672 cx.update(|cx| init_settings(cx, |_| {}));
2673
2674 let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
2675 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2676
2677 // Make 2 edits, merge one into the base.
2678 branch.update(cx, |branch, cx| {
2679 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2680 branch.merge_into_base(vec![7..7], cx);
2681 });
2682 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2683 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2684
2685 // Undo the merge in the base buffer.
2686 base.update(cx, |base, cx| {
2687 base.undo(cx);
2688 });
2689 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2690 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2691
2692 // Merge that operation into the base again.
2693 branch.update(cx, |branch, cx| {
2694 branch.merge_into_base(vec![7..7], cx);
2695 });
2696 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2697 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2698}
2699
2700fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {
2701 buffer
2702 .update(cx, |buffer, cx| buffer.recalculate_diff(cx).unwrap())
2703 .detach();
2704}
2705
2706#[track_caller]
2707fn assert_diff_hunks(
2708 buffer: &Model<Buffer>,
2709 cx: &mut TestAppContext,
2710 expected_hunks: &[(Range<u32>, &str, &str)],
2711) {
2712 let (snapshot, diff_base) = buffer.read_with(cx, |buffer, _| {
2713 (buffer.snapshot(), buffer.diff_base().unwrap().to_string())
2714 });
2715 assert_hunks(
2716 snapshot.git_diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX),
2717 &snapshot,
2718 &diff_base,
2719 expected_hunks,
2720 );
2721}
2722
2723#[gpui::test(iterations = 100)]
2724fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
2725 let min_peers = env::var("MIN_PEERS")
2726 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2727 .unwrap_or(1);
2728 let max_peers = env::var("MAX_PEERS")
2729 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2730 .unwrap_or(5);
2731 let operations = env::var("OPERATIONS")
2732 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2733 .unwrap_or(10);
2734
2735 let base_text_len = rng.gen_range(0..10);
2736 let base_text = RandomCharIter::new(&mut rng)
2737 .take(base_text_len)
2738 .collect::<String>();
2739 let mut replica_ids = Vec::new();
2740 let mut buffers = Vec::new();
2741 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2742 let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
2743
2744 for i in 0..rng.gen_range(min_peers..=max_peers) {
2745 let buffer = cx.new_model(|cx| {
2746 let state = base_buffer.read(cx).to_proto(cx);
2747 let ops = cx
2748 .background_executor()
2749 .block(base_buffer.read(cx).serialize_ops(None, cx));
2750 let mut buffer =
2751 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2752 buffer.apply_ops(
2753 ops.into_iter()
2754 .map(|op| proto::deserialize_operation(op).unwrap()),
2755 cx,
2756 );
2757 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2758 let network = network.clone();
2759 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2760 if let BufferEvent::Operation {
2761 operation,
2762 is_local: true,
2763 } = event
2764 {
2765 network.lock().broadcast(
2766 buffer.replica_id(),
2767 vec![proto::serialize_operation(operation)],
2768 );
2769 }
2770 })
2771 .detach();
2772 buffer
2773 });
2774
2775 buffers.push(buffer);
2776 replica_ids.push(i as ReplicaId);
2777 network.lock().add_peer(i as ReplicaId);
2778 log::info!("Adding initial peer with replica id {}", i);
2779 }
2780
2781 log::info!("initial text: {:?}", base_text);
2782
2783 let mut now = Instant::now();
2784 let mut mutation_count = operations;
2785 let mut next_diagnostic_id = 0;
2786 let mut active_selections = BTreeMap::default();
2787 loop {
2788 let replica_index = rng.gen_range(0..replica_ids.len());
2789 let replica_id = replica_ids[replica_index];
2790 let buffer = &mut buffers[replica_index];
2791 let mut new_buffer = None;
2792 match rng.gen_range(0..100) {
2793 0..=29 if mutation_count != 0 => {
2794 buffer.update(cx, |buffer, cx| {
2795 buffer.start_transaction_at(now);
2796 buffer.randomly_edit(&mut rng, 5, cx);
2797 buffer.end_transaction_at(now, cx);
2798 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2799 });
2800 mutation_count -= 1;
2801 }
2802 30..=39 if mutation_count != 0 => {
2803 buffer.update(cx, |buffer, cx| {
2804 if rng.gen_bool(0.2) {
2805 log::info!("peer {} clearing active selections", replica_id);
2806 active_selections.remove(&replica_id);
2807 buffer.remove_active_selections(cx);
2808 } else {
2809 let mut selections = Vec::new();
2810 for id in 0..rng.gen_range(1..=5) {
2811 let range = buffer.random_byte_range(0, &mut rng);
2812 selections.push(Selection {
2813 id,
2814 start: buffer.anchor_before(range.start),
2815 end: buffer.anchor_before(range.end),
2816 reversed: false,
2817 goal: SelectionGoal::None,
2818 });
2819 }
2820 let selections: Arc<[Selection<Anchor>]> = selections.into();
2821 log::info!(
2822 "peer {} setting active selections: {:?}",
2823 replica_id,
2824 selections
2825 );
2826 active_selections.insert(replica_id, selections.clone());
2827 buffer.set_active_selections(selections, false, Default::default(), cx);
2828 }
2829 });
2830 mutation_count -= 1;
2831 }
2832 40..=49 if mutation_count != 0 && replica_id == 0 => {
2833 let entry_count = rng.gen_range(1..=5);
2834 buffer.update(cx, |buffer, cx| {
2835 let diagnostics = DiagnosticSet::new(
2836 (0..entry_count).map(|_| {
2837 let range = buffer.random_byte_range(0, &mut rng);
2838 let range = range.to_point_utf16(buffer);
2839 let range = range.start..range.end;
2840 DiagnosticEntry {
2841 range,
2842 diagnostic: Diagnostic {
2843 message: post_inc(&mut next_diagnostic_id).to_string(),
2844 ..Default::default()
2845 },
2846 }
2847 }),
2848 buffer,
2849 );
2850 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2851 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2852 });
2853 mutation_count -= 1;
2854 }
2855 50..=59 if replica_ids.len() < max_peers => {
2856 let old_buffer_state = buffer.read(cx).to_proto(cx);
2857 let old_buffer_ops = cx
2858 .background_executor()
2859 .block(buffer.read(cx).serialize_ops(None, cx));
2860 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2861 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2862 .choose(&mut rng)
2863 .unwrap();
2864 log::info!(
2865 "Adding new replica {} (replicating from {})",
2866 new_replica_id,
2867 replica_id
2868 );
2869 new_buffer = Some(cx.new_model(|cx| {
2870 let mut new_buffer = Buffer::from_proto(
2871 new_replica_id,
2872 Capability::ReadWrite,
2873 old_buffer_state,
2874 None,
2875 )
2876 .unwrap();
2877 new_buffer.apply_ops(
2878 old_buffer_ops
2879 .into_iter()
2880 .map(|op| deserialize_operation(op).unwrap()),
2881 cx,
2882 );
2883 log::info!(
2884 "New replica {} text: {:?}",
2885 new_buffer.replica_id(),
2886 new_buffer.text()
2887 );
2888 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2889 let network = network.clone();
2890 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2891 if let BufferEvent::Operation {
2892 operation,
2893 is_local: true,
2894 } = event
2895 {
2896 network.lock().broadcast(
2897 buffer.replica_id(),
2898 vec![proto::serialize_operation(operation)],
2899 );
2900 }
2901 })
2902 .detach();
2903 new_buffer
2904 }));
2905 network.lock().replicate(replica_id, new_replica_id);
2906
2907 if new_replica_id as usize == replica_ids.len() {
2908 replica_ids.push(new_replica_id);
2909 } else {
2910 let new_buffer = new_buffer.take().unwrap();
2911 while network.lock().has_unreceived(new_replica_id) {
2912 let ops = network
2913 .lock()
2914 .receive(new_replica_id)
2915 .into_iter()
2916 .map(|op| proto::deserialize_operation(op).unwrap());
2917 if ops.len() > 0 {
2918 log::info!(
2919 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2920 new_replica_id,
2921 buffer.read(cx).version(),
2922 ops.len(),
2923 ops
2924 );
2925 new_buffer.update(cx, |new_buffer, cx| {
2926 new_buffer.apply_ops(ops, cx);
2927 });
2928 }
2929 }
2930 buffers[new_replica_id as usize] = new_buffer;
2931 }
2932 }
2933 60..=69 if mutation_count != 0 => {
2934 buffer.update(cx, |buffer, cx| {
2935 buffer.randomly_undo_redo(&mut rng, cx);
2936 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2937 });
2938 mutation_count -= 1;
2939 }
2940 _ if network.lock().has_unreceived(replica_id) => {
2941 let ops = network
2942 .lock()
2943 .receive(replica_id)
2944 .into_iter()
2945 .map(|op| proto::deserialize_operation(op).unwrap());
2946 if ops.len() > 0 {
2947 log::info!(
2948 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2949 replica_id,
2950 buffer.read(cx).version(),
2951 ops.len(),
2952 ops
2953 );
2954 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
2955 }
2956 }
2957 _ => {}
2958 }
2959
2960 now += Duration::from_millis(rng.gen_range(0..=200));
2961 buffers.extend(new_buffer);
2962
2963 for buffer in &buffers {
2964 buffer.read(cx).check_invariants();
2965 }
2966
2967 if mutation_count == 0 && network.lock().is_idle() {
2968 break;
2969 }
2970 }
2971
2972 let first_buffer = buffers[0].read(cx).snapshot();
2973 for buffer in &buffers[1..] {
2974 let buffer = buffer.read(cx).snapshot();
2975 assert_eq!(
2976 buffer.version(),
2977 first_buffer.version(),
2978 "Replica {} version != Replica 0 version",
2979 buffer.replica_id()
2980 );
2981 assert_eq!(
2982 buffer.text(),
2983 first_buffer.text(),
2984 "Replica {} text != Replica 0 text",
2985 buffer.replica_id()
2986 );
2987 assert_eq!(
2988 buffer
2989 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
2990 .collect::<Vec<_>>(),
2991 first_buffer
2992 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
2993 .collect::<Vec<_>>(),
2994 "Replica {} diagnostics != Replica 0 diagnostics",
2995 buffer.replica_id()
2996 );
2997 }
2998
2999 for buffer in &buffers {
3000 let buffer = buffer.read(cx).snapshot();
3001 let actual_remote_selections = buffer
3002 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
3003 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3004 .collect::<Vec<_>>();
3005 let expected_remote_selections = active_selections
3006 .iter()
3007 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3008 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3009 .collect::<Vec<_>>();
3010 assert_eq!(
3011 actual_remote_selections,
3012 expected_remote_selections,
3013 "Replica {} remote selections != expected selections",
3014 buffer.replica_id()
3015 );
3016 }
3017}
3018
3019#[test]
3020fn test_contiguous_ranges() {
3021 assert_eq!(
3022 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3023 &[1..4, 5..7, 9..13]
3024 );
3025
3026 // Respects the `max_len` parameter
3027 assert_eq!(
3028 contiguous_ranges(
3029 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3030 3
3031 )
3032 .collect::<Vec<_>>(),
3033 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3034 );
3035}
3036
3037#[gpui::test(iterations = 500)]
3038fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3039 // Generate a random multi-line string containing
3040 // some lines with trailing whitespace.
3041 let mut text = String::new();
3042 for _ in 0..rng.gen_range(0..16) {
3043 for _ in 0..rng.gen_range(0..36) {
3044 text.push(match rng.gen_range(0..10) {
3045 0..=1 => ' ',
3046 3 => '\t',
3047 _ => rng.gen_range('a'..='z'),
3048 });
3049 }
3050 text.push('\n');
3051 }
3052
3053 match rng.gen_range(0..10) {
3054 // sometimes remove the last newline
3055 0..=1 => drop(text.pop()), //
3056
3057 // sometimes add extra newlines
3058 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
3059 _ => {}
3060 }
3061
3062 let rope = Rope::from(text.as_str());
3063 let actual_ranges = trailing_whitespace_ranges(&rope);
3064 let expected_ranges = TRAILING_WHITESPACE_REGEX
3065 .find_iter(&text)
3066 .map(|m| m.range())
3067 .collect::<Vec<_>>();
3068 assert_eq!(
3069 actual_ranges,
3070 expected_ranges,
3071 "wrong ranges for text lines:\n{:?}",
3072 text.split('\n').collect::<Vec<_>>()
3073 );
3074}
3075
3076fn ruby_lang() -> Language {
3077 Language::new(
3078 LanguageConfig {
3079 name: "Ruby".into(),
3080 matcher: LanguageMatcher {
3081 path_suffixes: vec!["rb".to_string()],
3082 ..Default::default()
3083 },
3084 line_comments: vec!["# ".into()],
3085 ..Default::default()
3086 },
3087 Some(tree_sitter_ruby::LANGUAGE.into()),
3088 )
3089 .with_indents_query(
3090 r#"
3091 (class "end" @end) @indent
3092 (method "end" @end) @indent
3093 (rescue) @outdent
3094 (then) @indent
3095 "#,
3096 )
3097 .unwrap()
3098}
3099
3100fn html_lang() -> Language {
3101 Language::new(
3102 LanguageConfig {
3103 name: LanguageName::new("HTML"),
3104 block_comment: Some(("<!--".into(), "-->".into())),
3105 ..Default::default()
3106 },
3107 Some(tree_sitter_html::language()),
3108 )
3109 .with_indents_query(
3110 "
3111 (element
3112 (start_tag) @start
3113 (end_tag)? @end) @indent
3114 ",
3115 )
3116 .unwrap()
3117 .with_injection_query(
3118 r#"
3119 (script_element
3120 (raw_text) @content
3121 (#set! "language" "javascript"))
3122 "#,
3123 )
3124 .unwrap()
3125}
3126
3127fn erb_lang() -> Language {
3128 Language::new(
3129 LanguageConfig {
3130 name: "ERB".into(),
3131 matcher: LanguageMatcher {
3132 path_suffixes: vec!["erb".to_string()],
3133 ..Default::default()
3134 },
3135 block_comment: Some(("<%#".into(), "%>".into())),
3136 ..Default::default()
3137 },
3138 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3139 )
3140 .with_injection_query(
3141 r#"
3142 (
3143 (code) @content
3144 (#set! "language" "ruby")
3145 (#set! "combined")
3146 )
3147
3148 (
3149 (content) @content
3150 (#set! "language" "html")
3151 (#set! "combined")
3152 )
3153 "#,
3154 )
3155 .unwrap()
3156}
3157
3158fn rust_lang() -> Language {
3159 Language::new(
3160 LanguageConfig {
3161 name: "Rust".into(),
3162 matcher: LanguageMatcher {
3163 path_suffixes: vec!["rs".to_string()],
3164 ..Default::default()
3165 },
3166 ..Default::default()
3167 },
3168 Some(tree_sitter_rust::LANGUAGE.into()),
3169 )
3170 .with_indents_query(
3171 r#"
3172 (call_expression) @indent
3173 (field_expression) @indent
3174 (_ "(" ")" @end) @indent
3175 (_ "{" "}" @end) @indent
3176 "#,
3177 )
3178 .unwrap()
3179 .with_brackets_query(
3180 r#"
3181 ("{" @open "}" @close)
3182 "#,
3183 )
3184 .unwrap()
3185 .with_outline_query(
3186 r#"
3187 (line_comment) @annotation
3188
3189 (struct_item
3190 "struct" @context
3191 name: (_) @name) @item
3192 (enum_item
3193 "enum" @context
3194 name: (_) @name) @item
3195 (enum_variant
3196 name: (_) @name) @item
3197 (field_declaration
3198 name: (_) @name) @item
3199 (impl_item
3200 "impl" @context
3201 trait: (_)? @name
3202 "for"? @context
3203 type: (_) @name
3204 body: (_ "{" (_)* "}")) @item
3205 (function_item
3206 "fn" @context
3207 name: (_) @name) @item
3208 (mod_item
3209 "mod" @context
3210 name: (_) @name) @item
3211 "#,
3212 )
3213 .unwrap()
3214}
3215
3216fn json_lang() -> Language {
3217 Language::new(
3218 LanguageConfig {
3219 name: "Json".into(),
3220 matcher: LanguageMatcher {
3221 path_suffixes: vec!["js".to_string()],
3222 ..Default::default()
3223 },
3224 ..Default::default()
3225 },
3226 Some(tree_sitter_json::LANGUAGE.into()),
3227 )
3228}
3229
3230fn javascript_lang() -> Language {
3231 Language::new(
3232 LanguageConfig {
3233 name: "JavaScript".into(),
3234 ..Default::default()
3235 },
3236 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3237 )
3238 .with_brackets_query(
3239 r#"
3240 ("{" @open "}" @close)
3241 ("(" @open ")" @close)
3242 "#,
3243 )
3244 .unwrap()
3245 .with_indents_query(
3246 r#"
3247 (object "}" @end) @indent
3248 "#,
3249 )
3250 .unwrap()
3251}
3252
3253pub fn markdown_lang() -> Language {
3254 Language::new(
3255 LanguageConfig {
3256 name: "Markdown".into(),
3257 matcher: LanguageMatcher {
3258 path_suffixes: vec!["md".into()],
3259 ..Default::default()
3260 },
3261 ..Default::default()
3262 },
3263 Some(tree_sitter_md::LANGUAGE.into()),
3264 )
3265 .with_injection_query(
3266 r#"
3267 (fenced_code_block
3268 (info_string
3269 (language) @language)
3270 (code_fence_content) @content)
3271
3272 ((inline) @content
3273 (#set! "language" "markdown-inline"))
3274 "#,
3275 )
3276 .unwrap()
3277}
3278
3279pub fn markdown_inline_lang() -> Language {
3280 Language::new(
3281 LanguageConfig {
3282 name: "Markdown-Inline".into(),
3283 hidden: true,
3284 ..LanguageConfig::default()
3285 },
3286 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3287 )
3288 .with_highlights_query("(emphasis) @emphasis")
3289 .unwrap()
3290}
3291
3292fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3293 buffer.update(cx, |buffer, _| {
3294 let snapshot = buffer.snapshot();
3295 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3296 layers[0].node().to_sexp()
3297 })
3298}
3299
3300// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3301fn assert_bracket_pairs(
3302 selection_text: &'static str,
3303 bracket_pair_texts: Vec<&'static str>,
3304 language: Language,
3305 cx: &mut AppContext,
3306) {
3307 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3308 let buffer = cx.new_model(|cx| {
3309 Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
3310 });
3311 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3312
3313 let selection_range = selection_ranges[0].clone();
3314
3315 let bracket_pairs = bracket_pair_texts
3316 .into_iter()
3317 .map(|pair_text| {
3318 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3319 assert_eq!(bracket_text, expected_text);
3320 (ranges[0].clone(), ranges[1].clone())
3321 })
3322 .collect::<Vec<_>>();
3323
3324 assert_set_eq!(
3325 buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
3326 bracket_pairs
3327 );
3328}
3329
3330fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
3331 let settings_store = SettingsStore::test(cx);
3332 cx.set_global(settings_store);
3333 crate::init(cx);
3334 cx.update_global::<SettingsStore, _>(|settings, cx| {
3335 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3336 });
3337}