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