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_serialization(cx: &mut gpui::App) {
2512 let mut now = Instant::now();
2513
2514 let buffer1 = cx.new(|cx| {
2515 let mut buffer = Buffer::local("abc", cx);
2516 buffer.edit([(3..3, "D")], None, cx);
2517
2518 now += Duration::from_secs(1);
2519 buffer.start_transaction_at(now);
2520 buffer.edit([(4..4, "E")], None, cx);
2521 buffer.end_transaction_at(now, cx);
2522 assert_eq!(buffer.text(), "abcDE");
2523
2524 buffer.undo(cx);
2525 assert_eq!(buffer.text(), "abcD");
2526
2527 buffer.edit([(4..4, "F")], None, cx);
2528 assert_eq!(buffer.text(), "abcDF");
2529 buffer
2530 });
2531 assert_eq!(buffer1.read(cx).text(), "abcDF");
2532
2533 let state = buffer1.read(cx).to_proto(cx);
2534 let ops = cx
2535 .background_executor()
2536 .block(buffer1.read(cx).serialize_ops(None, cx));
2537 let buffer2 = cx.new(|cx| {
2538 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2539 buffer.apply_ops(
2540 ops.into_iter()
2541 .map(|op| proto::deserialize_operation(op).unwrap()),
2542 cx,
2543 );
2544 buffer
2545 });
2546 assert_eq!(buffer2.read(cx).text(), "abcDF");
2547}
2548
2549#[gpui::test]
2550fn test_branch_and_merge(cx: &mut TestAppContext) {
2551 cx.update(|cx| init_settings(cx, |_| {}));
2552
2553 let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2554
2555 // Create a remote replica of the base buffer.
2556 let base_replica = cx.new(|cx| {
2557 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2558 });
2559 base.update(cx, |_buffer, cx| {
2560 cx.subscribe(&base_replica, |this, _, event, cx| {
2561 if let BufferEvent::Operation {
2562 operation,
2563 is_local: true,
2564 } = event
2565 {
2566 this.apply_ops([operation.clone()], cx);
2567 }
2568 })
2569 .detach();
2570 });
2571
2572 // Create a branch, which initially has the same state as the base buffer.
2573 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2574 branch.read_with(cx, |buffer, _| {
2575 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2576 });
2577
2578 // Edits to the branch are not applied to the base.
2579 branch.update(cx, |buffer, cx| {
2580 buffer.edit(
2581 [
2582 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2583 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2584 ],
2585 None,
2586 cx,
2587 )
2588 });
2589 branch.read_with(cx, |buffer, cx| {
2590 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2591 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2592 });
2593
2594 // Convert from branch buffer ranges to the corresponding ranges in the
2595 // base buffer.
2596 branch.read_with(cx, |buffer, cx| {
2597 assert_eq!(
2598 buffer.range_to_version(4..7, &base.read(cx).version()),
2599 4..4
2600 );
2601 assert_eq!(
2602 buffer.range_to_version(2..9, &base.read(cx).version()),
2603 2..5
2604 );
2605 });
2606
2607 // Edits to the base are applied to the branch.
2608 base.update(cx, |buffer, cx| {
2609 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2610 });
2611 branch.read_with(cx, |buffer, cx| {
2612 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2613 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2614 });
2615
2616 // Edits to any replica of the base are applied to the branch.
2617 base_replica.update(cx, |buffer, cx| {
2618 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2619 });
2620 branch.read_with(cx, |buffer, cx| {
2621 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2622 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2623 });
2624
2625 // Merging the branch applies all of its changes to the base.
2626 branch.update(cx, |buffer, cx| {
2627 buffer.merge_into_base(Vec::new(), cx);
2628 });
2629
2630 branch.update(cx, |buffer, cx| {
2631 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2632 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2633 });
2634}
2635
2636#[gpui::test]
2637fn test_merge_into_base(cx: &mut TestAppContext) {
2638 cx.update(|cx| init_settings(cx, |_| {}));
2639
2640 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2641 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2642
2643 // Make 3 edits, merge one into the base.
2644 branch.update(cx, |branch, cx| {
2645 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2646 branch.merge_into_base(vec![5..8], cx);
2647 });
2648
2649 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2650 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2651
2652 // Undo the one already-merged edit. Merge that into the base.
2653 branch.update(cx, |branch, cx| {
2654 branch.edit([(7..9, "hi")], None, cx);
2655 branch.merge_into_base(vec![5..8], cx);
2656 });
2657 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2658
2659 // Merge an insertion into the base.
2660 branch.update(cx, |branch, cx| {
2661 branch.merge_into_base(vec![11..11], cx);
2662 });
2663
2664 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2665 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2666
2667 // Deleted the inserted text and merge that into the base.
2668 branch.update(cx, |branch, cx| {
2669 branch.edit([(11..14, "")], None, cx);
2670 branch.merge_into_base(vec![10..11], cx);
2671 });
2672
2673 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2674}
2675
2676#[gpui::test]
2677fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2678 cx.update(|cx| init_settings(cx, |_| {}));
2679
2680 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2681 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2682
2683 // Make 2 edits, merge one into the base.
2684 branch.update(cx, |branch, cx| {
2685 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2686 branch.merge_into_base(vec![7..7], cx);
2687 });
2688 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2689 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2690
2691 // Undo the merge in the base buffer.
2692 base.update(cx, |base, cx| {
2693 base.undo(cx);
2694 });
2695 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2696 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2697
2698 // Merge that operation into the base again.
2699 branch.update(cx, |branch, cx| {
2700 branch.merge_into_base(vec![7..7], cx);
2701 });
2702 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2703 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2704}
2705
2706#[gpui::test]
2707async fn test_preview_edits(cx: &mut TestAppContext) {
2708 cx.update(|cx| {
2709 init_settings(cx, |_| {});
2710 theme::init(theme::LoadThemes::JustBase, cx);
2711 });
2712
2713 let insertion_style = HighlightStyle {
2714 background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
2715 ..Default::default()
2716 };
2717 let deletion_style = HighlightStyle {
2718 background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
2719 ..Default::default()
2720 };
2721
2722 // no edits
2723 assert_preview_edits(
2724 indoc! {"
2725 fn test_empty() -> bool {
2726 false
2727 }"
2728 },
2729 vec![],
2730 true,
2731 cx,
2732 |hl| {
2733 assert!(hl.text.is_empty());
2734 assert!(hl.highlights.is_empty());
2735 },
2736 )
2737 .await;
2738
2739 // only insertions
2740 assert_preview_edits(
2741 indoc! {"
2742 fn calculate_area(: f64) -> f64 {
2743 std::f64::consts::PI * .powi(2)
2744 }"
2745 },
2746 vec![
2747 (Point::new(0, 18)..Point::new(0, 18), "radius"),
2748 (Point::new(1, 27)..Point::new(1, 27), "radius"),
2749 ],
2750 true,
2751 cx,
2752 |hl| {
2753 assert_eq!(
2754 hl.text,
2755 indoc! {"
2756 fn calculate_area(radius: f64) -> f64 {
2757 std::f64::consts::PI * radius.powi(2)"
2758 }
2759 );
2760
2761 assert_eq!(hl.highlights.len(), 2);
2762 assert_eq!(hl.highlights[0], ((18..24), insertion_style));
2763 assert_eq!(hl.highlights[1], ((67..73), insertion_style));
2764 },
2765 )
2766 .await;
2767
2768 // insertions & deletions
2769 assert_preview_edits(
2770 indoc! {"
2771 struct Person {
2772 first_name: String,
2773 }
2774
2775 impl Person {
2776 fn first_name(&self) -> &String {
2777 &self.first_name
2778 }
2779 }"
2780 },
2781 vec![
2782 (Point::new(1, 4)..Point::new(1, 9), "last"),
2783 (Point::new(5, 7)..Point::new(5, 12), "last"),
2784 (Point::new(6, 14)..Point::new(6, 19), "last"),
2785 ],
2786 true,
2787 cx,
2788 |hl| {
2789 assert_eq!(
2790 hl.text,
2791 indoc! {"
2792 firstlast_name: String,
2793 }
2794
2795 impl Person {
2796 fn firstlast_name(&self) -> &String {
2797 &self.firstlast_name"
2798 }
2799 );
2800
2801 assert_eq!(hl.highlights.len(), 6);
2802 assert_eq!(hl.highlights[0], ((4..9), deletion_style));
2803 assert_eq!(hl.highlights[1], ((9..13), insertion_style));
2804 assert_eq!(hl.highlights[2], ((52..57), deletion_style));
2805 assert_eq!(hl.highlights[3], ((57..61), insertion_style));
2806 assert_eq!(hl.highlights[4], ((101..106), deletion_style));
2807 assert_eq!(hl.highlights[5], ((106..110), insertion_style));
2808 },
2809 )
2810 .await;
2811
2812 async fn assert_preview_edits(
2813 text: &str,
2814 edits: Vec<(Range<Point>, &str)>,
2815 include_deletions: bool,
2816 cx: &mut TestAppContext,
2817 assert_fn: impl Fn(HighlightedText),
2818 ) {
2819 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
2820 let edits = buffer.read_with(cx, |buffer, _| {
2821 edits
2822 .into_iter()
2823 .map(|(range, text)| {
2824 (
2825 buffer.anchor_before(range.start)..buffer.anchor_after(range.end),
2826 text.to_string(),
2827 )
2828 })
2829 .collect::<Vec<_>>()
2830 });
2831 let edit_preview = buffer
2832 .read_with(cx, |buffer, cx| {
2833 buffer.preview_edits(edits.clone().into(), cx)
2834 })
2835 .await;
2836 let highlighted_edits = cx.read(|cx| {
2837 edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
2838 });
2839 assert_fn(highlighted_edits);
2840 }
2841}
2842
2843#[gpui::test(iterations = 100)]
2844fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
2845 let min_peers = env::var("MIN_PEERS")
2846 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2847 .unwrap_or(1);
2848 let max_peers = env::var("MAX_PEERS")
2849 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2850 .unwrap_or(5);
2851 let operations = env::var("OPERATIONS")
2852 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2853 .unwrap_or(10);
2854
2855 let base_text_len = rng.gen_range(0..10);
2856 let base_text = RandomCharIter::new(&mut rng)
2857 .take(base_text_len)
2858 .collect::<String>();
2859 let mut replica_ids = Vec::new();
2860 let mut buffers = Vec::new();
2861 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2862 let base_buffer = cx.new(|cx| Buffer::local(base_text.as_str(), cx));
2863
2864 for i in 0..rng.gen_range(min_peers..=max_peers) {
2865 let buffer = cx.new(|cx| {
2866 let state = base_buffer.read(cx).to_proto(cx);
2867 let ops = cx
2868 .background_executor()
2869 .block(base_buffer.read(cx).serialize_ops(None, cx));
2870 let mut buffer =
2871 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2872 buffer.apply_ops(
2873 ops.into_iter()
2874 .map(|op| proto::deserialize_operation(op).unwrap()),
2875 cx,
2876 );
2877 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2878 let network = network.clone();
2879 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
2880 if let BufferEvent::Operation {
2881 operation,
2882 is_local: true,
2883 } = event
2884 {
2885 network.lock().broadcast(
2886 buffer.replica_id(),
2887 vec![proto::serialize_operation(operation)],
2888 );
2889 }
2890 })
2891 .detach();
2892 buffer
2893 });
2894
2895 buffers.push(buffer);
2896 replica_ids.push(i as ReplicaId);
2897 network.lock().add_peer(i as ReplicaId);
2898 log::info!("Adding initial peer with replica id {}", i);
2899 }
2900
2901 log::info!("initial text: {:?}", base_text);
2902
2903 let mut now = Instant::now();
2904 let mut mutation_count = operations;
2905 let mut next_diagnostic_id = 0;
2906 let mut active_selections = BTreeMap::default();
2907 loop {
2908 let replica_index = rng.gen_range(0..replica_ids.len());
2909 let replica_id = replica_ids[replica_index];
2910 let buffer = &mut buffers[replica_index];
2911 let mut new_buffer = None;
2912 match rng.gen_range(0..100) {
2913 0..=29 if mutation_count != 0 => {
2914 buffer.update(cx, |buffer, cx| {
2915 buffer.start_transaction_at(now);
2916 buffer.randomly_edit(&mut rng, 5, cx);
2917 buffer.end_transaction_at(now, cx);
2918 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2919 });
2920 mutation_count -= 1;
2921 }
2922 30..=39 if mutation_count != 0 => {
2923 buffer.update(cx, |buffer, cx| {
2924 if rng.gen_bool(0.2) {
2925 log::info!("peer {} clearing active selections", replica_id);
2926 active_selections.remove(&replica_id);
2927 buffer.remove_active_selections(cx);
2928 } else {
2929 let mut selections = Vec::new();
2930 for id in 0..rng.gen_range(1..=5) {
2931 let range = buffer.random_byte_range(0, &mut rng);
2932 selections.push(Selection {
2933 id,
2934 start: buffer.anchor_before(range.start),
2935 end: buffer.anchor_before(range.end),
2936 reversed: false,
2937 goal: SelectionGoal::None,
2938 });
2939 }
2940 let selections: Arc<[Selection<Anchor>]> = selections.into();
2941 log::info!(
2942 "peer {} setting active selections: {:?}",
2943 replica_id,
2944 selections
2945 );
2946 active_selections.insert(replica_id, selections.clone());
2947 buffer.set_active_selections(selections, false, Default::default(), cx);
2948 }
2949 });
2950 mutation_count -= 1;
2951 }
2952 40..=49 if mutation_count != 0 && replica_id == 0 => {
2953 let entry_count = rng.gen_range(1..=5);
2954 buffer.update(cx, |buffer, cx| {
2955 let diagnostics = DiagnosticSet::new(
2956 (0..entry_count).map(|_| {
2957 let range = buffer.random_byte_range(0, &mut rng);
2958 let range = range.to_point_utf16(buffer);
2959 let range = range.start..range.end;
2960 DiagnosticEntry {
2961 range,
2962 diagnostic: Diagnostic {
2963 message: post_inc(&mut next_diagnostic_id).to_string(),
2964 ..Default::default()
2965 },
2966 }
2967 }),
2968 buffer,
2969 );
2970 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2971 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2972 });
2973 mutation_count -= 1;
2974 }
2975 50..=59 if replica_ids.len() < max_peers => {
2976 let old_buffer_state = buffer.read(cx).to_proto(cx);
2977 let old_buffer_ops = cx
2978 .background_executor()
2979 .block(buffer.read(cx).serialize_ops(None, cx));
2980 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2981 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2982 .choose(&mut rng)
2983 .unwrap();
2984 log::info!(
2985 "Adding new replica {} (replicating from {})",
2986 new_replica_id,
2987 replica_id
2988 );
2989 new_buffer = Some(cx.new(|cx| {
2990 let mut new_buffer = Buffer::from_proto(
2991 new_replica_id,
2992 Capability::ReadWrite,
2993 old_buffer_state,
2994 None,
2995 )
2996 .unwrap();
2997 new_buffer.apply_ops(
2998 old_buffer_ops
2999 .into_iter()
3000 .map(|op| deserialize_operation(op).unwrap()),
3001 cx,
3002 );
3003 log::info!(
3004 "New replica {} text: {:?}",
3005 new_buffer.replica_id(),
3006 new_buffer.text()
3007 );
3008 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
3009 let network = network.clone();
3010 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3011 if let BufferEvent::Operation {
3012 operation,
3013 is_local: true,
3014 } = event
3015 {
3016 network.lock().broadcast(
3017 buffer.replica_id(),
3018 vec![proto::serialize_operation(operation)],
3019 );
3020 }
3021 })
3022 .detach();
3023 new_buffer
3024 }));
3025 network.lock().replicate(replica_id, new_replica_id);
3026
3027 if new_replica_id as usize == replica_ids.len() {
3028 replica_ids.push(new_replica_id);
3029 } else {
3030 let new_buffer = new_buffer.take().unwrap();
3031 while network.lock().has_unreceived(new_replica_id) {
3032 let ops = network
3033 .lock()
3034 .receive(new_replica_id)
3035 .into_iter()
3036 .map(|op| proto::deserialize_operation(op).unwrap());
3037 if ops.len() > 0 {
3038 log::info!(
3039 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3040 new_replica_id,
3041 buffer.read(cx).version(),
3042 ops.len(),
3043 ops
3044 );
3045 new_buffer.update(cx, |new_buffer, cx| {
3046 new_buffer.apply_ops(ops, cx);
3047 });
3048 }
3049 }
3050 buffers[new_replica_id as usize] = new_buffer;
3051 }
3052 }
3053 60..=69 if mutation_count != 0 => {
3054 buffer.update(cx, |buffer, cx| {
3055 buffer.randomly_undo_redo(&mut rng, cx);
3056 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
3057 });
3058 mutation_count -= 1;
3059 }
3060 _ if network.lock().has_unreceived(replica_id) => {
3061 let ops = network
3062 .lock()
3063 .receive(replica_id)
3064 .into_iter()
3065 .map(|op| proto::deserialize_operation(op).unwrap());
3066 if ops.len() > 0 {
3067 log::info!(
3068 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3069 replica_id,
3070 buffer.read(cx).version(),
3071 ops.len(),
3072 ops
3073 );
3074 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
3075 }
3076 }
3077 _ => {}
3078 }
3079
3080 now += Duration::from_millis(rng.gen_range(0..=200));
3081 buffers.extend(new_buffer);
3082
3083 for buffer in &buffers {
3084 buffer.read(cx).check_invariants();
3085 }
3086
3087 if mutation_count == 0 && network.lock().is_idle() {
3088 break;
3089 }
3090 }
3091
3092 let first_buffer = buffers[0].read(cx).snapshot();
3093 for buffer in &buffers[1..] {
3094 let buffer = buffer.read(cx).snapshot();
3095 assert_eq!(
3096 buffer.version(),
3097 first_buffer.version(),
3098 "Replica {} version != Replica 0 version",
3099 buffer.replica_id()
3100 );
3101 assert_eq!(
3102 buffer.text(),
3103 first_buffer.text(),
3104 "Replica {} text != Replica 0 text",
3105 buffer.replica_id()
3106 );
3107 assert_eq!(
3108 buffer
3109 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
3110 .collect::<Vec<_>>(),
3111 first_buffer
3112 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
3113 .collect::<Vec<_>>(),
3114 "Replica {} diagnostics != Replica 0 diagnostics",
3115 buffer.replica_id()
3116 );
3117 }
3118
3119 for buffer in &buffers {
3120 let buffer = buffer.read(cx).snapshot();
3121 let actual_remote_selections = buffer
3122 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
3123 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3124 .collect::<Vec<_>>();
3125 let expected_remote_selections = active_selections
3126 .iter()
3127 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3128 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3129 .collect::<Vec<_>>();
3130 assert_eq!(
3131 actual_remote_selections,
3132 expected_remote_selections,
3133 "Replica {} remote selections != expected selections",
3134 buffer.replica_id()
3135 );
3136 }
3137}
3138
3139#[test]
3140fn test_contiguous_ranges() {
3141 assert_eq!(
3142 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3143 &[1..4, 5..7, 9..13]
3144 );
3145
3146 // Respects the `max_len` parameter
3147 assert_eq!(
3148 contiguous_ranges(
3149 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3150 3
3151 )
3152 .collect::<Vec<_>>(),
3153 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3154 );
3155}
3156
3157#[gpui::test(iterations = 500)]
3158fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3159 // Generate a random multi-line string containing
3160 // some lines with trailing whitespace.
3161 let mut text = String::new();
3162 for _ in 0..rng.gen_range(0..16) {
3163 for _ in 0..rng.gen_range(0..36) {
3164 text.push(match rng.gen_range(0..10) {
3165 0..=1 => ' ',
3166 3 => '\t',
3167 _ => rng.gen_range('a'..='z'),
3168 });
3169 }
3170 text.push('\n');
3171 }
3172
3173 match rng.gen_range(0..10) {
3174 // sometimes remove the last newline
3175 0..=1 => drop(text.pop()), //
3176
3177 // sometimes add extra newlines
3178 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
3179 _ => {}
3180 }
3181
3182 let rope = Rope::from(text.as_str());
3183 let actual_ranges = trailing_whitespace_ranges(&rope);
3184 let expected_ranges = TRAILING_WHITESPACE_REGEX
3185 .find_iter(&text)
3186 .map(|m| m.range())
3187 .collect::<Vec<_>>();
3188 assert_eq!(
3189 actual_ranges,
3190 expected_ranges,
3191 "wrong ranges for text lines:\n{:?}",
3192 text.split('\n').collect::<Vec<_>>()
3193 );
3194}
3195
3196#[gpui::test]
3197fn test_words_in_range(cx: &mut gpui::App) {
3198 init_settings(cx, |_| {});
3199
3200 // The first line are words excluded from the results with heuristics, we do not expect them in the test assertions.
3201 let contents = r#"
32020_isize 123 3.4 4
3203let word=öäpple.bar你 Öäpple word2-öÄpPlE-Pizza-word ÖÄPPLE word
3204 "#;
3205
3206 let buffer = cx.new(|cx| {
3207 let buffer = Buffer::local(contents, cx).with_language(Arc::new(rust_lang()), cx);
3208 assert_eq!(buffer.text(), contents);
3209 buffer.check_invariants();
3210 buffer
3211 });
3212
3213 buffer.update(cx, |buffer, _| {
3214 let snapshot = buffer.snapshot();
3215 assert_eq!(
3216 BTreeSet::from_iter(["Pizza".to_string()]),
3217 snapshot
3218 .words_in_range(WordsQuery {
3219 fuzzy_contents: Some("piz"),
3220 skip_digits: true,
3221 range: 0..snapshot.len(),
3222 })
3223 .into_keys()
3224 .collect::<BTreeSet<_>>()
3225 );
3226 assert_eq!(
3227 BTreeSet::from_iter([
3228 "öäpple".to_string(),
3229 "Öäpple".to_string(),
3230 "öÄpPlE".to_string(),
3231 "ÖÄPPLE".to_string(),
3232 ]),
3233 snapshot
3234 .words_in_range(WordsQuery {
3235 fuzzy_contents: Some("öp"),
3236 skip_digits: true,
3237 range: 0..snapshot.len(),
3238 })
3239 .into_keys()
3240 .collect::<BTreeSet<_>>()
3241 );
3242 assert_eq!(
3243 BTreeSet::from_iter([
3244 "öÄpPlE".to_string(),
3245 "Öäpple".to_string(),
3246 "ÖÄPPLE".to_string(),
3247 "öäpple".to_string(),
3248 ]),
3249 snapshot
3250 .words_in_range(WordsQuery {
3251 fuzzy_contents: Some("öÄ"),
3252 skip_digits: true,
3253 range: 0..snapshot.len(),
3254 })
3255 .into_keys()
3256 .collect::<BTreeSet<_>>()
3257 );
3258 assert_eq!(
3259 BTreeSet::default(),
3260 snapshot
3261 .words_in_range(WordsQuery {
3262 fuzzy_contents: Some("öÄ好"),
3263 skip_digits: true,
3264 range: 0..snapshot.len(),
3265 })
3266 .into_keys()
3267 .collect::<BTreeSet<_>>()
3268 );
3269 assert_eq!(
3270 BTreeSet::from_iter(["bar你".to_string(),]),
3271 snapshot
3272 .words_in_range(WordsQuery {
3273 fuzzy_contents: Some("你"),
3274 skip_digits: true,
3275 range: 0..snapshot.len(),
3276 })
3277 .into_keys()
3278 .collect::<BTreeSet<_>>()
3279 );
3280 assert_eq!(
3281 BTreeSet::default(),
3282 snapshot
3283 .words_in_range(WordsQuery {
3284 fuzzy_contents: Some(""),
3285 skip_digits: true,
3286 range: 0..snapshot.len(),
3287 },)
3288 .into_keys()
3289 .collect::<BTreeSet<_>>()
3290 );
3291 assert_eq!(
3292 BTreeSet::from_iter([
3293 "bar你".to_string(),
3294 "öÄpPlE".to_string(),
3295 "Öäpple".to_string(),
3296 "ÖÄPPLE".to_string(),
3297 "öäpple".to_string(),
3298 "let".to_string(),
3299 "Pizza".to_string(),
3300 "word".to_string(),
3301 "word2".to_string(),
3302 ]),
3303 snapshot
3304 .words_in_range(WordsQuery {
3305 fuzzy_contents: None,
3306 skip_digits: true,
3307 range: 0..snapshot.len(),
3308 })
3309 .into_keys()
3310 .collect::<BTreeSet<_>>()
3311 );
3312 assert_eq!(
3313 BTreeSet::from_iter([
3314 "0_isize".to_string(),
3315 "123".to_string(),
3316 "3".to_string(),
3317 "4".to_string(),
3318 "bar你".to_string(),
3319 "öÄpPlE".to_string(),
3320 "Öäpple".to_string(),
3321 "ÖÄPPLE".to_string(),
3322 "öäpple".to_string(),
3323 "let".to_string(),
3324 "Pizza".to_string(),
3325 "word".to_string(),
3326 "word2".to_string(),
3327 ]),
3328 snapshot
3329 .words_in_range(WordsQuery {
3330 fuzzy_contents: None,
3331 skip_digits: false,
3332 range: 0..snapshot.len(),
3333 })
3334 .into_keys()
3335 .collect::<BTreeSet<_>>()
3336 );
3337 });
3338}
3339
3340fn ruby_lang() -> Language {
3341 Language::new(
3342 LanguageConfig {
3343 name: "Ruby".into(),
3344 matcher: LanguageMatcher {
3345 path_suffixes: vec!["rb".to_string()],
3346 ..Default::default()
3347 },
3348 line_comments: vec!["# ".into()],
3349 ..Default::default()
3350 },
3351 Some(tree_sitter_ruby::LANGUAGE.into()),
3352 )
3353 .with_indents_query(
3354 r#"
3355 (class "end" @end) @indent
3356 (method "end" @end) @indent
3357 (rescue) @outdent
3358 (then) @indent
3359 "#,
3360 )
3361 .unwrap()
3362}
3363
3364fn html_lang() -> Language {
3365 Language::new(
3366 LanguageConfig {
3367 name: LanguageName::new("HTML"),
3368 block_comment: Some(("<!--".into(), "-->".into())),
3369 ..Default::default()
3370 },
3371 Some(tree_sitter_html::LANGUAGE.into()),
3372 )
3373 .with_indents_query(
3374 "
3375 (element
3376 (start_tag) @start
3377 (end_tag)? @end) @indent
3378 ",
3379 )
3380 .unwrap()
3381 .with_injection_query(
3382 r#"
3383 (script_element
3384 (raw_text) @injection.content
3385 (#set! injection.language "javascript"))
3386 "#,
3387 )
3388 .unwrap()
3389}
3390
3391fn erb_lang() -> Language {
3392 Language::new(
3393 LanguageConfig {
3394 name: "ERB".into(),
3395 matcher: LanguageMatcher {
3396 path_suffixes: vec!["erb".to_string()],
3397 ..Default::default()
3398 },
3399 block_comment: Some(("<%#".into(), "%>".into())),
3400 ..Default::default()
3401 },
3402 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3403 )
3404 .with_injection_query(
3405 r#"
3406 (
3407 (code) @injection.content
3408 (#set! injection.language "ruby")
3409 (#set! injection.combined)
3410 )
3411
3412 (
3413 (content) @injection.content
3414 (#set! injection.language "html")
3415 (#set! injection.combined)
3416 )
3417 "#,
3418 )
3419 .unwrap()
3420}
3421
3422fn rust_lang() -> Language {
3423 Language::new(
3424 LanguageConfig {
3425 name: "Rust".into(),
3426 matcher: LanguageMatcher {
3427 path_suffixes: vec!["rs".to_string()],
3428 ..Default::default()
3429 },
3430 ..Default::default()
3431 },
3432 Some(tree_sitter_rust::LANGUAGE.into()),
3433 )
3434 .with_indents_query(
3435 r#"
3436 (call_expression) @indent
3437 (field_expression) @indent
3438 (_ "(" ")" @end) @indent
3439 (_ "{" "}" @end) @indent
3440 "#,
3441 )
3442 .unwrap()
3443 .with_brackets_query(
3444 r#"
3445 ("{" @open "}" @close)
3446 "#,
3447 )
3448 .unwrap()
3449 .with_text_object_query(
3450 r#"
3451 (function_item
3452 body: (_
3453 "{"
3454 (_)* @function.inside
3455 "}" )) @function.around
3456
3457 (line_comment)+ @comment.around
3458
3459 (block_comment) @comment.around
3460 "#,
3461 )
3462 .unwrap()
3463 .with_outline_query(
3464 r#"
3465 (line_comment) @annotation
3466
3467 (struct_item
3468 "struct" @context
3469 name: (_) @name) @item
3470 (enum_item
3471 "enum" @context
3472 name: (_) @name) @item
3473 (enum_variant
3474 name: (_) @name) @item
3475 (field_declaration
3476 name: (_) @name) @item
3477 (impl_item
3478 "impl" @context
3479 trait: (_)? @name
3480 "for"? @context
3481 type: (_) @name
3482 body: (_ "{" (_)* "}")) @item
3483 (function_item
3484 "fn" @context
3485 name: (_) @name) @item
3486 (mod_item
3487 "mod" @context
3488 name: (_) @name) @item
3489 "#,
3490 )
3491 .unwrap()
3492}
3493
3494fn json_lang() -> Language {
3495 Language::new(
3496 LanguageConfig {
3497 name: "Json".into(),
3498 matcher: LanguageMatcher {
3499 path_suffixes: vec!["js".to_string()],
3500 ..Default::default()
3501 },
3502 ..Default::default()
3503 },
3504 Some(tree_sitter_json::LANGUAGE.into()),
3505 )
3506}
3507
3508fn javascript_lang() -> Language {
3509 Language::new(
3510 LanguageConfig {
3511 name: "JavaScript".into(),
3512 ..Default::default()
3513 },
3514 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3515 )
3516 .with_brackets_query(
3517 r#"
3518 ("{" @open "}" @close)
3519 ("(" @open ")" @close)
3520 "#,
3521 )
3522 .unwrap()
3523 .with_indents_query(
3524 r#"
3525 (object "}" @end) @indent
3526 "#,
3527 )
3528 .unwrap()
3529}
3530
3531pub fn markdown_lang() -> Language {
3532 Language::new(
3533 LanguageConfig {
3534 name: "Markdown".into(),
3535 matcher: LanguageMatcher {
3536 path_suffixes: vec!["md".into()],
3537 ..Default::default()
3538 },
3539 ..Default::default()
3540 },
3541 Some(tree_sitter_md::LANGUAGE.into()),
3542 )
3543 .with_injection_query(
3544 r#"
3545 (fenced_code_block
3546 (info_string
3547 (language) @injection.language)
3548 (code_fence_content) @injection.content)
3549
3550 ((inline) @injection.content
3551 (#set! injection.language "markdown-inline"))
3552 "#,
3553 )
3554 .unwrap()
3555}
3556
3557pub fn markdown_inline_lang() -> Language {
3558 Language::new(
3559 LanguageConfig {
3560 name: "Markdown-Inline".into(),
3561 hidden: true,
3562 ..LanguageConfig::default()
3563 },
3564 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3565 )
3566 .with_highlights_query("(emphasis) @emphasis")
3567 .unwrap()
3568}
3569
3570fn get_tree_sexp(buffer: &Entity<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3571 buffer.update(cx, |buffer, _| {
3572 let snapshot = buffer.snapshot();
3573 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3574 layers[0].node().to_sexp()
3575 })
3576}
3577
3578// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3579fn assert_bracket_pairs(
3580 selection_text: &'static str,
3581 bracket_pair_texts: Vec<&'static str>,
3582 language: Language,
3583 cx: &mut App,
3584) {
3585 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3586 let buffer =
3587 cx.new(|cx| Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx));
3588 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3589
3590 let selection_range = selection_ranges[0].clone();
3591
3592 let bracket_pairs = bracket_pair_texts
3593 .into_iter()
3594 .map(|pair_text| {
3595 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3596 assert_eq!(bracket_text, expected_text);
3597 (ranges[0].clone(), ranges[1].clone())
3598 })
3599 .collect::<Vec<_>>();
3600
3601 assert_set_eq!(
3602 buffer
3603 .bracket_ranges(selection_range)
3604 .map(|pair| (pair.open_range, pair.close_range))
3605 .collect::<Vec<_>>(),
3606 bracket_pairs
3607 );
3608}
3609
3610fn init_settings(cx: &mut App, f: fn(&mut AllLanguageSettingsContent)) {
3611 let settings_store = SettingsStore::test(cx);
3612 cx.set_global(settings_store);
3613 crate::init(cx);
3614 cx.update_global::<SettingsStore, _>(|settings, cx| {
3615 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3616 });
3617}