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_language_at_with_hidden_languages(cx: &mut AppContext) {
2221 init_settings(cx, |_| {});
2222
2223 cx.new_model(|cx| {
2224 let text = r#"
2225 this is an *emphasized* word.
2226 "#
2227 .unindent();
2228
2229 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2230 language_registry.add(Arc::new(markdown_lang()));
2231 language_registry.add(Arc::new(markdown_inline_lang()));
2232
2233 let mut buffer = Buffer::local(text, cx);
2234 buffer.set_language_registry(language_registry.clone());
2235 buffer.set_language(
2236 language_registry
2237 .language_for_name("Markdown")
2238 .now_or_never()
2239 .unwrap()
2240 .ok(),
2241 cx,
2242 );
2243
2244 let snapshot = buffer.snapshot();
2245
2246 for point in [Point::new(0, 4), Point::new(0, 16)] {
2247 let config = snapshot.language_scope_at(point).unwrap();
2248 assert_eq!(config.language_name().as_ref(), "Markdown");
2249
2250 let language = snapshot.language_at(point).unwrap();
2251 assert_eq!(language.name().as_ref(), "Markdown");
2252 }
2253
2254 buffer
2255 });
2256}
2257
2258#[gpui::test]
2259fn test_serialization(cx: &mut gpui::AppContext) {
2260 let mut now = Instant::now();
2261
2262 let buffer1 = cx.new_model(|cx| {
2263 let mut buffer = Buffer::local("abc", cx);
2264 buffer.edit([(3..3, "D")], None, cx);
2265
2266 now += Duration::from_secs(1);
2267 buffer.start_transaction_at(now);
2268 buffer.edit([(4..4, "E")], None, cx);
2269 buffer.end_transaction_at(now, cx);
2270 assert_eq!(buffer.text(), "abcDE");
2271
2272 buffer.undo(cx);
2273 assert_eq!(buffer.text(), "abcD");
2274
2275 buffer.edit([(4..4, "F")], None, cx);
2276 assert_eq!(buffer.text(), "abcDF");
2277 buffer
2278 });
2279 assert_eq!(buffer1.read(cx).text(), "abcDF");
2280
2281 let state = buffer1.read(cx).to_proto(cx);
2282 let ops = cx
2283 .background_executor()
2284 .block(buffer1.read(cx).serialize_ops(None, cx));
2285 let buffer2 = cx.new_model(|cx| {
2286 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2287 buffer
2288 .apply_ops(
2289 ops.into_iter()
2290 .map(|op| proto::deserialize_operation(op).unwrap()),
2291 cx,
2292 )
2293 .unwrap();
2294 buffer
2295 });
2296 assert_eq!(buffer2.read(cx).text(), "abcDF");
2297}
2298
2299#[gpui::test]
2300async fn test_find_matching_indent(cx: &mut TestAppContext) {
2301 cx.update(|cx| init_settings(cx, |_| {}));
2302
2303 async fn enclosing_indent(
2304 text: impl Into<String>,
2305 buffer_row: u32,
2306 cx: &mut TestAppContext,
2307 ) -> Option<(Range<u32>, LineIndent)> {
2308 let buffer = cx.new_model(|cx| Buffer::local(text, cx));
2309 let snapshot = cx.read(|cx| buffer.read(cx).snapshot());
2310 snapshot.enclosing_indent(buffer_row).await
2311 }
2312
2313 assert_eq!(
2314 enclosing_indent(
2315 "
2316 fn b() {
2317 if c {
2318 let d = 2;
2319 }
2320 }"
2321 .unindent(),
2322 1,
2323 cx,
2324 )
2325 .await,
2326 Some((
2327 1..2,
2328 LineIndent {
2329 tabs: 0,
2330 spaces: 4,
2331 line_blank: false,
2332 }
2333 ))
2334 );
2335
2336 assert_eq!(
2337 enclosing_indent(
2338 "
2339 fn b() {
2340 if c {
2341 let d = 2;
2342 }
2343 }"
2344 .unindent(),
2345 2,
2346 cx,
2347 )
2348 .await,
2349 Some((
2350 1..2,
2351 LineIndent {
2352 tabs: 0,
2353 spaces: 4,
2354 line_blank: false,
2355 }
2356 ))
2357 );
2358
2359 assert_eq!(
2360 enclosing_indent(
2361 "
2362 fn b() {
2363 if c {
2364 let d = 2;
2365
2366 let e = 5;
2367 }
2368 }"
2369 .unindent(),
2370 3,
2371 cx,
2372 )
2373 .await,
2374 Some((
2375 1..4,
2376 LineIndent {
2377 tabs: 0,
2378 spaces: 4,
2379 line_blank: false,
2380 }
2381 ))
2382 );
2383}
2384
2385#[gpui::test(iterations = 100)]
2386fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
2387 let min_peers = env::var("MIN_PEERS")
2388 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2389 .unwrap_or(1);
2390 let max_peers = env::var("MAX_PEERS")
2391 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2392 .unwrap_or(5);
2393 let operations = env::var("OPERATIONS")
2394 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2395 .unwrap_or(10);
2396
2397 let base_text_len = rng.gen_range(0..10);
2398 let base_text = RandomCharIter::new(&mut rng)
2399 .take(base_text_len)
2400 .collect::<String>();
2401 let mut replica_ids = Vec::new();
2402 let mut buffers = Vec::new();
2403 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2404 let base_buffer = cx.new_model(|cx| Buffer::local(base_text.as_str(), cx));
2405
2406 for i in 0..rng.gen_range(min_peers..=max_peers) {
2407 let buffer = cx.new_model(|cx| {
2408 let state = base_buffer.read(cx).to_proto(cx);
2409 let ops = cx
2410 .background_executor()
2411 .block(base_buffer.read(cx).serialize_ops(None, cx));
2412 let mut buffer =
2413 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2414 buffer
2415 .apply_ops(
2416 ops.into_iter()
2417 .map(|op| proto::deserialize_operation(op).unwrap()),
2418 cx,
2419 )
2420 .unwrap();
2421 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2422 let network = network.clone();
2423 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2424 if let Event::Operation(op) = event {
2425 network
2426 .lock()
2427 .broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
2428 }
2429 })
2430 .detach();
2431 buffer
2432 });
2433
2434 buffers.push(buffer);
2435 replica_ids.push(i as ReplicaId);
2436 network.lock().add_peer(i as ReplicaId);
2437 log::info!("Adding initial peer with replica id {}", i);
2438 }
2439
2440 log::info!("initial text: {:?}", base_text);
2441
2442 let mut now = Instant::now();
2443 let mut mutation_count = operations;
2444 let mut next_diagnostic_id = 0;
2445 let mut active_selections = BTreeMap::default();
2446 loop {
2447 let replica_index = rng.gen_range(0..replica_ids.len());
2448 let replica_id = replica_ids[replica_index];
2449 let buffer = &mut buffers[replica_index];
2450 let mut new_buffer = None;
2451 match rng.gen_range(0..100) {
2452 0..=29 if mutation_count != 0 => {
2453 buffer.update(cx, |buffer, cx| {
2454 buffer.start_transaction_at(now);
2455 buffer.randomly_edit(&mut rng, 5, cx);
2456 buffer.end_transaction_at(now, cx);
2457 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2458 });
2459 mutation_count -= 1;
2460 }
2461 30..=39 if mutation_count != 0 => {
2462 buffer.update(cx, |buffer, cx| {
2463 if rng.gen_bool(0.2) {
2464 log::info!("peer {} clearing active selections", replica_id);
2465 active_selections.remove(&replica_id);
2466 buffer.remove_active_selections(cx);
2467 } else {
2468 let mut selections = Vec::new();
2469 for id in 0..rng.gen_range(1..=5) {
2470 let range = buffer.random_byte_range(0, &mut rng);
2471 selections.push(Selection {
2472 id,
2473 start: buffer.anchor_before(range.start),
2474 end: buffer.anchor_before(range.end),
2475 reversed: false,
2476 goal: SelectionGoal::None,
2477 });
2478 }
2479 let selections: Arc<[Selection<Anchor>]> = selections.into();
2480 log::info!(
2481 "peer {} setting active selections: {:?}",
2482 replica_id,
2483 selections
2484 );
2485 active_selections.insert(replica_id, selections.clone());
2486 buffer.set_active_selections(selections, false, Default::default(), cx);
2487 }
2488 });
2489 mutation_count -= 1;
2490 }
2491 40..=49 if mutation_count != 0 && replica_id == 0 => {
2492 let entry_count = rng.gen_range(1..=5);
2493 buffer.update(cx, |buffer, cx| {
2494 let diagnostics = DiagnosticSet::new(
2495 (0..entry_count).map(|_| {
2496 let range = buffer.random_byte_range(0, &mut rng);
2497 let range = range.to_point_utf16(buffer);
2498 let range = range.start..range.end;
2499 DiagnosticEntry {
2500 range,
2501 diagnostic: Diagnostic {
2502 message: post_inc(&mut next_diagnostic_id).to_string(),
2503 ..Default::default()
2504 },
2505 }
2506 }),
2507 buffer,
2508 );
2509 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2510 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2511 });
2512 mutation_count -= 1;
2513 }
2514 50..=59 if replica_ids.len() < max_peers => {
2515 let old_buffer_state = buffer.read(cx).to_proto(cx);
2516 let old_buffer_ops = cx
2517 .background_executor()
2518 .block(buffer.read(cx).serialize_ops(None, cx));
2519 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2520 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2521 .choose(&mut rng)
2522 .unwrap();
2523 log::info!(
2524 "Adding new replica {} (replicating from {})",
2525 new_replica_id,
2526 replica_id
2527 );
2528 new_buffer = Some(cx.new_model(|cx| {
2529 let mut new_buffer = Buffer::from_proto(
2530 new_replica_id,
2531 Capability::ReadWrite,
2532 old_buffer_state,
2533 None,
2534 )
2535 .unwrap();
2536 new_buffer
2537 .apply_ops(
2538 old_buffer_ops
2539 .into_iter()
2540 .map(|op| deserialize_operation(op).unwrap()),
2541 cx,
2542 )
2543 .unwrap();
2544 log::info!(
2545 "New replica {} text: {:?}",
2546 new_buffer.replica_id(),
2547 new_buffer.text()
2548 );
2549 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2550 let network = network.clone();
2551 cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
2552 if let Event::Operation(op) = event {
2553 network.lock().broadcast(
2554 buffer.replica_id(),
2555 vec![proto::serialize_operation(op)],
2556 );
2557 }
2558 })
2559 .detach();
2560 new_buffer
2561 }));
2562 network.lock().replicate(replica_id, new_replica_id);
2563
2564 if new_replica_id as usize == replica_ids.len() {
2565 replica_ids.push(new_replica_id);
2566 } else {
2567 let new_buffer = new_buffer.take().unwrap();
2568 while network.lock().has_unreceived(new_replica_id) {
2569 let ops = network
2570 .lock()
2571 .receive(new_replica_id)
2572 .into_iter()
2573 .map(|op| proto::deserialize_operation(op).unwrap());
2574 if ops.len() > 0 {
2575 log::info!(
2576 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2577 new_replica_id,
2578 buffer.read(cx).version(),
2579 ops.len(),
2580 ops
2581 );
2582 new_buffer.update(cx, |new_buffer, cx| {
2583 new_buffer.apply_ops(ops, cx).unwrap();
2584 });
2585 }
2586 }
2587 buffers[new_replica_id as usize] = new_buffer;
2588 }
2589 }
2590 60..=69 if mutation_count != 0 => {
2591 buffer.update(cx, |buffer, cx| {
2592 buffer.randomly_undo_redo(&mut rng, cx);
2593 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2594 });
2595 mutation_count -= 1;
2596 }
2597 _ if network.lock().has_unreceived(replica_id) => {
2598 let ops = network
2599 .lock()
2600 .receive(replica_id)
2601 .into_iter()
2602 .map(|op| proto::deserialize_operation(op).unwrap());
2603 if ops.len() > 0 {
2604 log::info!(
2605 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2606 replica_id,
2607 buffer.read(cx).version(),
2608 ops.len(),
2609 ops
2610 );
2611 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx).unwrap());
2612 }
2613 }
2614 _ => {}
2615 }
2616
2617 now += Duration::from_millis(rng.gen_range(0..=200));
2618 buffers.extend(new_buffer);
2619
2620 for buffer in &buffers {
2621 buffer.read(cx).check_invariants();
2622 }
2623
2624 if mutation_count == 0 && network.lock().is_idle() {
2625 break;
2626 }
2627 }
2628
2629 let first_buffer = buffers[0].read(cx).snapshot();
2630 for buffer in &buffers[1..] {
2631 let buffer = buffer.read(cx).snapshot();
2632 assert_eq!(
2633 buffer.version(),
2634 first_buffer.version(),
2635 "Replica {} version != Replica 0 version",
2636 buffer.replica_id()
2637 );
2638 assert_eq!(
2639 buffer.text(),
2640 first_buffer.text(),
2641 "Replica {} text != Replica 0 text",
2642 buffer.replica_id()
2643 );
2644 assert_eq!(
2645 buffer
2646 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
2647 .collect::<Vec<_>>(),
2648 first_buffer
2649 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
2650 .collect::<Vec<_>>(),
2651 "Replica {} diagnostics != Replica 0 diagnostics",
2652 buffer.replica_id()
2653 );
2654 }
2655
2656 for buffer in &buffers {
2657 let buffer = buffer.read(cx).snapshot();
2658 let actual_remote_selections = buffer
2659 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
2660 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
2661 .collect::<Vec<_>>();
2662 let expected_remote_selections = active_selections
2663 .iter()
2664 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
2665 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
2666 .collect::<Vec<_>>();
2667 assert_eq!(
2668 actual_remote_selections,
2669 expected_remote_selections,
2670 "Replica {} remote selections != expected selections",
2671 buffer.replica_id()
2672 );
2673 }
2674}
2675
2676#[test]
2677fn test_contiguous_ranges() {
2678 assert_eq!(
2679 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
2680 &[1..4, 5..7, 9..13]
2681 );
2682
2683 // Respects the `max_len` parameter
2684 assert_eq!(
2685 contiguous_ranges(
2686 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
2687 3
2688 )
2689 .collect::<Vec<_>>(),
2690 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
2691 );
2692}
2693
2694#[gpui::test(iterations = 500)]
2695fn test_trailing_whitespace_ranges(mut rng: StdRng) {
2696 // Generate a random multi-line string containing
2697 // some lines with trailing whitespace.
2698 let mut text = String::new();
2699 for _ in 0..rng.gen_range(0..16) {
2700 for _ in 0..rng.gen_range(0..36) {
2701 text.push(match rng.gen_range(0..10) {
2702 0..=1 => ' ',
2703 3 => '\t',
2704 _ => rng.gen_range('a'..='z'),
2705 });
2706 }
2707 text.push('\n');
2708 }
2709
2710 match rng.gen_range(0..10) {
2711 // sometimes remove the last newline
2712 0..=1 => drop(text.pop()), //
2713
2714 // sometimes add extra newlines
2715 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
2716 _ => {}
2717 }
2718
2719 let rope = Rope::from(text.as_str());
2720 let actual_ranges = trailing_whitespace_ranges(&rope);
2721 let expected_ranges = TRAILING_WHITESPACE_REGEX
2722 .find_iter(&text)
2723 .map(|m| m.range())
2724 .collect::<Vec<_>>();
2725 assert_eq!(
2726 actual_ranges,
2727 expected_ranges,
2728 "wrong ranges for text lines:\n{:?}",
2729 text.split('\n').collect::<Vec<_>>()
2730 );
2731}
2732
2733fn ruby_lang() -> Language {
2734 Language::new(
2735 LanguageConfig {
2736 name: "Ruby".into(),
2737 matcher: LanguageMatcher {
2738 path_suffixes: vec!["rb".to_string()],
2739 ..Default::default()
2740 },
2741 line_comments: vec!["# ".into()],
2742 ..Default::default()
2743 },
2744 Some(tree_sitter_ruby::language()),
2745 )
2746 .with_indents_query(
2747 r#"
2748 (class "end" @end) @indent
2749 (method "end" @end) @indent
2750 (rescue) @outdent
2751 (then) @indent
2752 "#,
2753 )
2754 .unwrap()
2755}
2756
2757fn html_lang() -> Language {
2758 Language::new(
2759 LanguageConfig {
2760 name: "HTML".into(),
2761 block_comment: Some(("<!--".into(), "-->".into())),
2762 ..Default::default()
2763 },
2764 Some(tree_sitter_html::language()),
2765 )
2766 .with_indents_query(
2767 "
2768 (element
2769 (start_tag) @start
2770 (end_tag)? @end) @indent
2771 ",
2772 )
2773 .unwrap()
2774 .with_injection_query(
2775 r#"
2776 (script_element
2777 (raw_text) @content
2778 (#set! "language" "javascript"))
2779 "#,
2780 )
2781 .unwrap()
2782}
2783
2784fn erb_lang() -> Language {
2785 Language::new(
2786 LanguageConfig {
2787 name: "ERB".into(),
2788 matcher: LanguageMatcher {
2789 path_suffixes: vec!["erb".to_string()],
2790 ..Default::default()
2791 },
2792 block_comment: Some(("<%#".into(), "%>".into())),
2793 ..Default::default()
2794 },
2795 Some(tree_sitter_embedded_template::language()),
2796 )
2797 .with_injection_query(
2798 r#"
2799 (
2800 (code) @content
2801 (#set! "language" "ruby")
2802 (#set! "combined")
2803 )
2804
2805 (
2806 (content) @content
2807 (#set! "language" "html")
2808 (#set! "combined")
2809 )
2810 "#,
2811 )
2812 .unwrap()
2813}
2814
2815fn rust_lang() -> Language {
2816 Language::new(
2817 LanguageConfig {
2818 name: "Rust".into(),
2819 matcher: LanguageMatcher {
2820 path_suffixes: vec!["rs".to_string()],
2821 ..Default::default()
2822 },
2823 ..Default::default()
2824 },
2825 Some(tree_sitter_rust::language()),
2826 )
2827 .with_indents_query(
2828 r#"
2829 (call_expression) @indent
2830 (field_expression) @indent
2831 (_ "(" ")" @end) @indent
2832 (_ "{" "}" @end) @indent
2833 "#,
2834 )
2835 .unwrap()
2836 .with_brackets_query(
2837 r#"
2838 ("{" @open "}" @close)
2839 "#,
2840 )
2841 .unwrap()
2842 .with_outline_query(
2843 r#"
2844 (line_comment) @annotation
2845
2846 (struct_item
2847 "struct" @context
2848 name: (_) @name) @item
2849 (enum_item
2850 "enum" @context
2851 name: (_) @name) @item
2852 (enum_variant
2853 name: (_) @name) @item
2854 (field_declaration
2855 name: (_) @name) @item
2856 (impl_item
2857 "impl" @context
2858 trait: (_)? @name
2859 "for"? @context
2860 type: (_) @name
2861 body: (_ "{" (_)* "}")) @item
2862 (function_item
2863 "fn" @context
2864 name: (_) @name) @item
2865 (mod_item
2866 "mod" @context
2867 name: (_) @name) @item
2868 "#,
2869 )
2870 .unwrap()
2871}
2872
2873fn json_lang() -> Language {
2874 Language::new(
2875 LanguageConfig {
2876 name: "Json".into(),
2877 matcher: LanguageMatcher {
2878 path_suffixes: vec!["js".to_string()],
2879 ..Default::default()
2880 },
2881 ..Default::default()
2882 },
2883 Some(tree_sitter_json::language()),
2884 )
2885}
2886
2887fn javascript_lang() -> Language {
2888 Language::new(
2889 LanguageConfig {
2890 name: "JavaScript".into(),
2891 ..Default::default()
2892 },
2893 Some(tree_sitter_typescript::language_tsx()),
2894 )
2895 .with_brackets_query(
2896 r#"
2897 ("{" @open "}" @close)
2898 ("(" @open ")" @close)
2899 "#,
2900 )
2901 .unwrap()
2902 .with_indents_query(
2903 r#"
2904 (object "}" @end) @indent
2905 "#,
2906 )
2907 .unwrap()
2908}
2909
2910pub fn markdown_lang() -> Language {
2911 Language::new(
2912 LanguageConfig {
2913 name: "Markdown".into(),
2914 matcher: LanguageMatcher {
2915 path_suffixes: vec!["md".into()],
2916 ..Default::default()
2917 },
2918 ..Default::default()
2919 },
2920 Some(tree_sitter_md::language()),
2921 )
2922 .with_injection_query(
2923 r#"
2924 (fenced_code_block
2925 (info_string
2926 (language) @language)
2927 (code_fence_content) @content)
2928
2929 ((inline) @content
2930 (#set! "language" "markdown-inline"))
2931 "#,
2932 )
2933 .unwrap()
2934}
2935
2936pub fn markdown_inline_lang() -> Language {
2937 Language::new(
2938 LanguageConfig {
2939 name: "Markdown-Inline".into(),
2940 hidden: true,
2941 ..LanguageConfig::default()
2942 },
2943 Some(tree_sitter_md::inline_language()),
2944 )
2945 .with_highlights_query("(emphasis) @emphasis")
2946 .unwrap()
2947}
2948
2949fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
2950 buffer.update(cx, |buffer, _| {
2951 let snapshot = buffer.snapshot();
2952 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
2953 layers[0].node().to_sexp()
2954 })
2955}
2956
2957// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
2958fn assert_bracket_pairs(
2959 selection_text: &'static str,
2960 bracket_pair_texts: Vec<&'static str>,
2961 language: Language,
2962 cx: &mut AppContext,
2963) {
2964 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
2965 let buffer = cx.new_model(|cx| {
2966 Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx)
2967 });
2968 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
2969
2970 let selection_range = selection_ranges[0].clone();
2971
2972 let bracket_pairs = bracket_pair_texts
2973 .into_iter()
2974 .map(|pair_text| {
2975 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
2976 assert_eq!(bracket_text, expected_text);
2977 (ranges[0].clone(), ranges[1].clone())
2978 })
2979 .collect::<Vec<_>>();
2980
2981 assert_set_eq!(
2982 buffer.bracket_ranges(selection_range).collect::<Vec<_>>(),
2983 bracket_pairs
2984 );
2985}
2986
2987fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
2988 let settings_store = SettingsStore::test(cx);
2989 cx.set_global(settings_store);
2990 crate::init(cx);
2991 cx.update_global::<SettingsStore, _>(|settings, cx| {
2992 settings.update_user_settings::<AllLanguageSettings>(cx, f);
2993 });
2994}