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