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