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