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