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