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_without_original_indent_columns(cx: &mut App) {
1717 init_settings(cx, |_| {});
1718
1719 cx.new(|cx| {
1720 let text = r#"
1721 fn a() {
1722 if b() {
1723
1724 }
1725 }
1726 "#
1727 .unindent();
1728 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1729
1730 // The original indent columns are not known, so this text is
1731 // auto-indented in a block as if the first line was copied in
1732 // its entirety.
1733 let original_indent_columns = Vec::new();
1734 let inserted_text = " c\n .d()\n .e();";
1735
1736 // Insert the block at column zero. The entire block is indented
1737 // so that the first line matches the previous line's indentation.
1738 buffer.edit(
1739 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1740 Some(AutoindentMode::Block {
1741 original_indent_columns: original_indent_columns.clone(),
1742 }),
1743 cx,
1744 );
1745 assert_eq!(
1746 buffer.text(),
1747 r#"
1748 fn a() {
1749 if b() {
1750 c
1751 .d()
1752 .e();
1753 }
1754 }
1755 "#
1756 .unindent()
1757 );
1758
1759 // Grouping is disabled in tests, so we need 2 undos
1760 buffer.undo(cx); // Undo the auto-indent
1761 buffer.undo(cx); // Undo the original edit
1762
1763 // Insert the block at a deeper indent level. The entire block is outdented.
1764 buffer.edit(
1765 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
1766 None,
1767 cx,
1768 );
1769 buffer.edit(
1770 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
1771 Some(AutoindentMode::Block {
1772 original_indent_columns: Vec::new(),
1773 }),
1774 cx,
1775 );
1776 assert_eq!(
1777 buffer.text(),
1778 r#"
1779 fn a() {
1780 if b() {
1781 c
1782 .d()
1783 .e();
1784 }
1785 }
1786 "#
1787 .unindent()
1788 );
1789
1790 buffer
1791 });
1792}
1793
1794#[gpui::test]
1795fn test_autoindent_block_mode_multiple_adjacent_ranges(cx: &mut App) {
1796 init_settings(cx, |_| {});
1797
1798 cx.new(|cx| {
1799 let (text, ranges_to_replace) = marked_text_ranges(
1800 &"
1801 mod numbers {
1802 «fn one() {
1803 1
1804 }
1805 »
1806 «fn two() {
1807 2
1808 }
1809 »
1810 «fn three() {
1811 3
1812 }
1813 »}
1814 "
1815 .unindent(),
1816 false,
1817 );
1818
1819 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1820
1821 buffer.edit(
1822 [
1823 (ranges_to_replace[0].clone(), "fn one() {\n 101\n}\n"),
1824 (ranges_to_replace[1].clone(), "fn two() {\n 102\n}\n"),
1825 (ranges_to_replace[2].clone(), "fn three() {\n 103\n}\n"),
1826 ],
1827 Some(AutoindentMode::Block {
1828 original_indent_columns: vec![Some(0), Some(0), Some(0)],
1829 }),
1830 cx,
1831 );
1832
1833 pretty_assertions::assert_eq!(
1834 buffer.text(),
1835 "
1836 mod numbers {
1837 fn one() {
1838 101
1839 }
1840
1841 fn two() {
1842 102
1843 }
1844
1845 fn three() {
1846 103
1847 }
1848 }
1849 "
1850 .unindent()
1851 );
1852
1853 buffer
1854 });
1855}
1856
1857#[gpui::test]
1858fn test_autoindent_language_without_indents_query(cx: &mut App) {
1859 init_settings(cx, |_| {});
1860
1861 cx.new(|cx| {
1862 let text = "
1863 * one
1864 - a
1865 - b
1866 * two
1867 "
1868 .unindent();
1869
1870 let mut buffer = Buffer::local(text, cx).with_language(
1871 Arc::new(Language::new(
1872 LanguageConfig {
1873 name: "Markdown".into(),
1874 auto_indent_using_last_non_empty_line: false,
1875 ..Default::default()
1876 },
1877 Some(tree_sitter_json::LANGUAGE.into()),
1878 )),
1879 cx,
1880 );
1881 buffer.edit(
1882 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
1883 Some(AutoindentMode::EachLine),
1884 cx,
1885 );
1886 assert_eq!(
1887 buffer.text(),
1888 "
1889 * one
1890 - a
1891 - b
1892
1893 * two
1894 "
1895 .unindent()
1896 );
1897 buffer
1898 });
1899}
1900
1901#[gpui::test]
1902fn test_autoindent_with_injected_languages(cx: &mut App) {
1903 init_settings(cx, |settings| {
1904 settings.languages.extend([
1905 (
1906 "HTML".into(),
1907 LanguageSettingsContent {
1908 tab_size: Some(2.try_into().unwrap()),
1909 ..Default::default()
1910 },
1911 ),
1912 (
1913 "JavaScript".into(),
1914 LanguageSettingsContent {
1915 tab_size: Some(8.try_into().unwrap()),
1916 ..Default::default()
1917 },
1918 ),
1919 ])
1920 });
1921
1922 let html_language = Arc::new(html_lang());
1923
1924 let javascript_language = Arc::new(javascript_lang());
1925
1926 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1927 language_registry.add(html_language.clone());
1928 language_registry.add(javascript_language.clone());
1929
1930 cx.new(|cx| {
1931 let (text, ranges) = marked_text_ranges(
1932 &"
1933 <div>ˇ
1934 </div>
1935 <script>
1936 init({ˇ
1937 })
1938 </script>
1939 <span>ˇ
1940 </span>
1941 "
1942 .unindent(),
1943 false,
1944 );
1945
1946 let mut buffer = Buffer::local(text, cx);
1947 buffer.set_language_registry(language_registry);
1948 buffer.set_language(Some(html_language), cx);
1949 buffer.edit(
1950 ranges.into_iter().map(|range| (range, "\na")),
1951 Some(AutoindentMode::EachLine),
1952 cx,
1953 );
1954 assert_eq!(
1955 buffer.text(),
1956 "
1957 <div>
1958 a
1959 </div>
1960 <script>
1961 init({
1962 a
1963 })
1964 </script>
1965 <span>
1966 a
1967 </span>
1968 "
1969 .unindent()
1970 );
1971 buffer
1972 });
1973}
1974
1975#[gpui::test]
1976fn test_autoindent_query_with_outdent_captures(cx: &mut App) {
1977 init_settings(cx, |settings| {
1978 settings.defaults.tab_size = Some(2.try_into().unwrap());
1979 });
1980
1981 cx.new(|cx| {
1982 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
1983
1984 let text = r#"
1985 class C
1986 def a(b, c)
1987 puts b
1988 puts c
1989 rescue
1990 puts "errored"
1991 exit 1
1992 end
1993 end
1994 "#
1995 .unindent();
1996
1997 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
1998
1999 assert_eq!(
2000 buffer.text(),
2001 r#"
2002 class C
2003 def a(b, c)
2004 puts b
2005 puts c
2006 rescue
2007 puts "errored"
2008 exit 1
2009 end
2010 end
2011 "#
2012 .unindent()
2013 );
2014
2015 buffer
2016 });
2017}
2018
2019#[gpui::test]
2020async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
2021 cx.update(|cx| init_settings(cx, |_| {}));
2022
2023 // First we insert some newlines to request an auto-indent (asynchronously).
2024 // Then we request that a preview tab be preserved for the new version, even though it's edited.
2025 let buffer = cx.new(|cx| {
2026 let text = "fn a() {}";
2027 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
2028
2029 // This causes autoindent to be async.
2030 buffer.set_sync_parse_timeout(Duration::ZERO);
2031
2032 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
2033 buffer.refresh_preview();
2034
2035 // Synchronously, we haven't auto-indented and we're still preserving the preview.
2036 assert_eq!(buffer.text(), "fn a() {\n\n}");
2037 assert!(buffer.preserve_preview());
2038 buffer
2039 });
2040
2041 // Now let the autoindent finish
2042 cx.executor().run_until_parked();
2043
2044 // The auto-indent applied, but didn't dismiss our preview
2045 buffer.update(cx, |buffer, cx| {
2046 assert_eq!(buffer.text(), "fn a() {\n \n}");
2047 assert!(buffer.preserve_preview());
2048
2049 // Edit inserting another line. It will autoindent async.
2050 // Then refresh the preview version.
2051 buffer.edit(
2052 [(Point::new(1, 4)..Point::new(1, 4), "\n")],
2053 Some(AutoindentMode::EachLine),
2054 cx,
2055 );
2056 buffer.refresh_preview();
2057 assert_eq!(buffer.text(), "fn a() {\n \n\n}");
2058 assert!(buffer.preserve_preview());
2059
2060 // Then perform another edit, this time without refreshing the preview version.
2061 buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
2062 // This causes the preview to not be preserved.
2063 assert!(!buffer.preserve_preview());
2064 });
2065
2066 // Let the async autoindent from the first edit finish.
2067 cx.executor().run_until_parked();
2068
2069 // The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
2070 buffer.update(cx, |buffer, _| {
2071 assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
2072 assert!(!buffer.preserve_preview());
2073 });
2074}
2075
2076#[gpui::test]
2077fn test_insert_empty_line(cx: &mut App) {
2078 init_settings(cx, |_| {});
2079
2080 // Insert empty line at the beginning, requesting an empty line above
2081 cx.new(|cx| {
2082 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2083 let point = buffer.insert_empty_line(Point::new(0, 0), true, false, cx);
2084 assert_eq!(buffer.text(), "\nabc\ndef\nghi");
2085 assert_eq!(point, Point::new(0, 0));
2086 buffer
2087 });
2088
2089 // Insert empty line at the beginning, requesting an empty line above and below
2090 cx.new(|cx| {
2091 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2092 let point = buffer.insert_empty_line(Point::new(0, 0), true, true, cx);
2093 assert_eq!(buffer.text(), "\n\nabc\ndef\nghi");
2094 assert_eq!(point, Point::new(0, 0));
2095 buffer
2096 });
2097
2098 // Insert empty line at the start of a line, requesting empty lines above and below
2099 cx.new(|cx| {
2100 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2101 let point = buffer.insert_empty_line(Point::new(2, 0), true, true, cx);
2102 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi");
2103 assert_eq!(point, Point::new(3, 0));
2104 buffer
2105 });
2106
2107 // Insert empty line in the middle of a line, requesting empty lines above and below
2108 cx.new(|cx| {
2109 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2110 let point = buffer.insert_empty_line(Point::new(1, 3), true, true, cx);
2111 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi\njkl");
2112 assert_eq!(point, Point::new(3, 0));
2113 buffer
2114 });
2115
2116 // Insert empty line in the middle of a line, requesting empty line above only
2117 cx.new(|cx| {
2118 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2119 let point = buffer.insert_empty_line(Point::new(1, 3), true, false, cx);
2120 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2121 assert_eq!(point, Point::new(3, 0));
2122 buffer
2123 });
2124
2125 // Insert empty line in the middle of a line, requesting empty line below only
2126 cx.new(|cx| {
2127 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2128 let point = buffer.insert_empty_line(Point::new(1, 3), false, true, cx);
2129 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2130 assert_eq!(point, Point::new(2, 0));
2131 buffer
2132 });
2133
2134 // Insert empty line at the end, requesting empty lines above and below
2135 cx.new(|cx| {
2136 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2137 let point = buffer.insert_empty_line(Point::new(2, 3), true, true, cx);
2138 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n\n");
2139 assert_eq!(point, Point::new(4, 0));
2140 buffer
2141 });
2142
2143 // Insert empty line at the end, requesting empty line above only
2144 cx.new(|cx| {
2145 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2146 let point = buffer.insert_empty_line(Point::new(2, 3), true, false, cx);
2147 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2148 assert_eq!(point, Point::new(4, 0));
2149 buffer
2150 });
2151
2152 // Insert empty line at the end, requesting empty line below only
2153 cx.new(|cx| {
2154 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2155 let point = buffer.insert_empty_line(Point::new(2, 3), false, true, cx);
2156 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2157 assert_eq!(point, Point::new(3, 0));
2158 buffer
2159 });
2160}
2161
2162#[gpui::test]
2163fn test_language_scope_at_with_javascript(cx: &mut App) {
2164 init_settings(cx, |_| {});
2165
2166 cx.new(|cx| {
2167 let language = Language::new(
2168 LanguageConfig {
2169 name: "JavaScript".into(),
2170 line_comments: vec!["// ".into()],
2171 brackets: BracketPairConfig {
2172 pairs: vec![
2173 BracketPair {
2174 start: "{".into(),
2175 end: "}".into(),
2176 close: true,
2177 surround: true,
2178 newline: false,
2179 },
2180 BracketPair {
2181 start: "'".into(),
2182 end: "'".into(),
2183 close: true,
2184 surround: true,
2185 newline: false,
2186 },
2187 ],
2188 disabled_scopes_by_bracket_ix: vec![
2189 Vec::new(), //
2190 vec!["string".into(), "comment".into()], // single quotes disabled
2191 ],
2192 },
2193 overrides: [(
2194 "element".into(),
2195 LanguageConfigOverride {
2196 line_comments: Override::Remove { remove: true },
2197 block_comment: Override::Set(("{/*".into(), "*/}".into())),
2198 ..Default::default()
2199 },
2200 )]
2201 .into_iter()
2202 .collect(),
2203 ..Default::default()
2204 },
2205 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2206 )
2207 .with_override_query(
2208 r#"
2209 (jsx_element) @element
2210 (string) @string
2211 (comment) @comment.inclusive
2212 [
2213 (jsx_opening_element)
2214 (jsx_closing_element)
2215 (jsx_expression)
2216 ] @default
2217 "#,
2218 )
2219 .unwrap();
2220
2221 let text = r#"
2222 a["b"] = <C d="e">
2223 <F></F>
2224 { g() }
2225 </C>; // a comment
2226 "#
2227 .unindent();
2228
2229 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2230 let snapshot = buffer.snapshot();
2231
2232 let config = snapshot.language_scope_at(0).unwrap();
2233 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2234 // Both bracket pairs are enabled
2235 assert_eq!(
2236 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2237 &[true, true]
2238 );
2239
2240 let comment_config = snapshot
2241 .language_scope_at(text.find("comment").unwrap() + "comment".len())
2242 .unwrap();
2243 assert_eq!(
2244 comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2245 &[true, false]
2246 );
2247
2248 let string_config = snapshot
2249 .language_scope_at(text.find("b\"").unwrap())
2250 .unwrap();
2251 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2252 // Second bracket pair is disabled
2253 assert_eq!(
2254 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2255 &[true, false]
2256 );
2257
2258 // In between JSX tags: use the `element` override.
2259 let element_config = snapshot
2260 .language_scope_at(text.find("<F>").unwrap())
2261 .unwrap();
2262 // TODO nested blocks after newlines are captured with all whitespaces
2263 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2264 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2265 // assert_eq!(
2266 // element_config.block_comment_delimiters(),
2267 // Some((&"{/*".into(), &"*/}".into()))
2268 // );
2269 assert_eq!(
2270 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2271 &[true, true]
2272 );
2273
2274 // Within a JSX tag: use the default config.
2275 let tag_config = snapshot
2276 .language_scope_at(text.find(" d=").unwrap() + 1)
2277 .unwrap();
2278 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2279 assert_eq!(
2280 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2281 &[true, true]
2282 );
2283
2284 // In a JSX expression: use the default config.
2285 let expression_in_element_config = snapshot
2286 .language_scope_at(text.find('{').unwrap() + 1)
2287 .unwrap();
2288 assert_eq!(
2289 expression_in_element_config.line_comment_prefixes(),
2290 &[Arc::from("// ")]
2291 );
2292 assert_eq!(
2293 expression_in_element_config
2294 .brackets()
2295 .map(|e| e.1)
2296 .collect::<Vec<_>>(),
2297 &[true, true]
2298 );
2299
2300 buffer
2301 });
2302}
2303
2304#[gpui::test]
2305fn test_language_scope_at_with_rust(cx: &mut App) {
2306 init_settings(cx, |_| {});
2307
2308 cx.new(|cx| {
2309 let language = Language::new(
2310 LanguageConfig {
2311 name: "Rust".into(),
2312 brackets: BracketPairConfig {
2313 pairs: vec![
2314 BracketPair {
2315 start: "{".into(),
2316 end: "}".into(),
2317 close: true,
2318 surround: true,
2319 newline: false,
2320 },
2321 BracketPair {
2322 start: "'".into(),
2323 end: "'".into(),
2324 close: true,
2325 surround: true,
2326 newline: false,
2327 },
2328 ],
2329 disabled_scopes_by_bracket_ix: vec![
2330 Vec::new(), //
2331 vec!["string".into()],
2332 ],
2333 },
2334 ..Default::default()
2335 },
2336 Some(tree_sitter_rust::LANGUAGE.into()),
2337 )
2338 .with_override_query(
2339 r#"
2340 (string_literal) @string
2341 "#,
2342 )
2343 .unwrap();
2344
2345 let text = r#"
2346 const S: &'static str = "hello";
2347 "#
2348 .unindent();
2349
2350 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2351 let snapshot = buffer.snapshot();
2352
2353 // By default, all brackets are enabled
2354 let config = snapshot.language_scope_at(0).unwrap();
2355 assert_eq!(
2356 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2357 &[true, true]
2358 );
2359
2360 // Within a string, the quotation brackets are disabled.
2361 let string_config = snapshot
2362 .language_scope_at(text.find("ello").unwrap())
2363 .unwrap();
2364 assert_eq!(
2365 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2366 &[true, false]
2367 );
2368
2369 buffer
2370 });
2371}
2372
2373#[gpui::test]
2374fn test_language_scope_at_with_combined_injections(cx: &mut App) {
2375 init_settings(cx, |_| {});
2376
2377 cx.new(|cx| {
2378 let text = r#"
2379 <ol>
2380 <% people.each do |person| %>
2381 <li>
2382 <%= person.name %>
2383 </li>
2384 <% end %>
2385 </ol>
2386 "#
2387 .unindent();
2388
2389 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2390 language_registry.add(Arc::new(ruby_lang()));
2391 language_registry.add(Arc::new(html_lang()));
2392 language_registry.add(Arc::new(erb_lang()));
2393
2394 let mut buffer = Buffer::local(text, cx);
2395 buffer.set_language_registry(language_registry.clone());
2396 buffer.set_language(
2397 language_registry
2398 .language_for_name("ERB")
2399 .now_or_never()
2400 .unwrap()
2401 .ok(),
2402 cx,
2403 );
2404
2405 let snapshot = buffer.snapshot();
2406 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2407 assert_eq!(html_config.line_comment_prefixes(), &[]);
2408 assert_eq!(
2409 html_config.block_comment_delimiters(),
2410 Some((&"<!--".into(), &"-->".into()))
2411 );
2412
2413 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2414 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2415 assert_eq!(ruby_config.block_comment_delimiters(), None);
2416
2417 buffer
2418 });
2419}
2420
2421#[gpui::test]
2422fn test_language_at_with_hidden_languages(cx: &mut App) {
2423 init_settings(cx, |_| {});
2424
2425 cx.new(|cx| {
2426 let text = r#"
2427 this is an *emphasized* word.
2428 "#
2429 .unindent();
2430
2431 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2432 language_registry.add(Arc::new(markdown_lang()));
2433 language_registry.add(Arc::new(markdown_inline_lang()));
2434
2435 let mut buffer = Buffer::local(text, cx);
2436 buffer.set_language_registry(language_registry.clone());
2437 buffer.set_language(
2438 language_registry
2439 .language_for_name("Markdown")
2440 .now_or_never()
2441 .unwrap()
2442 .ok(),
2443 cx,
2444 );
2445
2446 let snapshot = buffer.snapshot();
2447
2448 for point in [Point::new(0, 4), Point::new(0, 16)] {
2449 let config = snapshot.language_scope_at(point).unwrap();
2450 assert_eq!(config.language_name(), "Markdown".into());
2451
2452 let language = snapshot.language_at(point).unwrap();
2453 assert_eq!(language.name().as_ref(), "Markdown");
2454 }
2455
2456 buffer
2457 });
2458}
2459
2460#[gpui::test]
2461fn test_serialization(cx: &mut gpui::App) {
2462 let mut now = Instant::now();
2463
2464 let buffer1 = cx.new(|cx| {
2465 let mut buffer = Buffer::local("abc", cx);
2466 buffer.edit([(3..3, "D")], None, cx);
2467
2468 now += Duration::from_secs(1);
2469 buffer.start_transaction_at(now);
2470 buffer.edit([(4..4, "E")], None, cx);
2471 buffer.end_transaction_at(now, cx);
2472 assert_eq!(buffer.text(), "abcDE");
2473
2474 buffer.undo(cx);
2475 assert_eq!(buffer.text(), "abcD");
2476
2477 buffer.edit([(4..4, "F")], None, cx);
2478 assert_eq!(buffer.text(), "abcDF");
2479 buffer
2480 });
2481 assert_eq!(buffer1.read(cx).text(), "abcDF");
2482
2483 let state = buffer1.read(cx).to_proto(cx);
2484 let ops = cx
2485 .background_executor()
2486 .block(buffer1.read(cx).serialize_ops(None, cx));
2487 let buffer2 = cx.new(|cx| {
2488 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2489 buffer.apply_ops(
2490 ops.into_iter()
2491 .map(|op| proto::deserialize_operation(op).unwrap()),
2492 cx,
2493 );
2494 buffer
2495 });
2496 assert_eq!(buffer2.read(cx).text(), "abcDF");
2497}
2498
2499#[gpui::test]
2500fn test_branch_and_merge(cx: &mut TestAppContext) {
2501 cx.update(|cx| init_settings(cx, |_| {}));
2502
2503 let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2504
2505 // Create a remote replica of the base buffer.
2506 let base_replica = cx.new(|cx| {
2507 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2508 });
2509 base.update(cx, |_buffer, cx| {
2510 cx.subscribe(&base_replica, |this, _, event, cx| {
2511 if let BufferEvent::Operation {
2512 operation,
2513 is_local: true,
2514 } = event
2515 {
2516 this.apply_ops([operation.clone()], cx);
2517 }
2518 })
2519 .detach();
2520 });
2521
2522 // Create a branch, which initially has the same state as the base buffer.
2523 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2524 branch.read_with(cx, |buffer, _| {
2525 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2526 });
2527
2528 // Edits to the branch are not applied to the base.
2529 branch.update(cx, |buffer, cx| {
2530 buffer.edit(
2531 [
2532 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2533 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2534 ],
2535 None,
2536 cx,
2537 )
2538 });
2539 branch.read_with(cx, |buffer, cx| {
2540 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2541 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2542 });
2543
2544 // Convert from branch buffer ranges to the corresponding ranges in the
2545 // base buffer.
2546 branch.read_with(cx, |buffer, cx| {
2547 assert_eq!(
2548 buffer.range_to_version(4..7, &base.read(cx).version()),
2549 4..4
2550 );
2551 assert_eq!(
2552 buffer.range_to_version(2..9, &base.read(cx).version()),
2553 2..5
2554 );
2555 });
2556
2557 // Edits to the base are applied to the branch.
2558 base.update(cx, |buffer, cx| {
2559 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2560 });
2561 branch.read_with(cx, |buffer, cx| {
2562 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2563 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2564 });
2565
2566 // Edits to any replica of the base are applied to the branch.
2567 base_replica.update(cx, |buffer, cx| {
2568 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2569 });
2570 branch.read_with(cx, |buffer, cx| {
2571 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2572 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2573 });
2574
2575 // Merging the branch applies all of its changes to the base.
2576 branch.update(cx, |buffer, cx| {
2577 buffer.merge_into_base(Vec::new(), cx);
2578 });
2579
2580 branch.update(cx, |buffer, cx| {
2581 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2582 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2583 });
2584}
2585
2586#[gpui::test]
2587fn test_merge_into_base(cx: &mut TestAppContext) {
2588 cx.update(|cx| init_settings(cx, |_| {}));
2589
2590 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2591 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2592
2593 // Make 3 edits, merge one into the base.
2594 branch.update(cx, |branch, cx| {
2595 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2596 branch.merge_into_base(vec![5..8], cx);
2597 });
2598
2599 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2600 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2601
2602 // Undo the one already-merged edit. Merge that into the base.
2603 branch.update(cx, |branch, cx| {
2604 branch.edit([(7..9, "hi")], None, cx);
2605 branch.merge_into_base(vec![5..8], cx);
2606 });
2607 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2608
2609 // Merge an insertion into the base.
2610 branch.update(cx, |branch, cx| {
2611 branch.merge_into_base(vec![11..11], cx);
2612 });
2613
2614 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2615 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2616
2617 // Deleted the inserted text and merge that into the base.
2618 branch.update(cx, |branch, cx| {
2619 branch.edit([(11..14, "")], None, cx);
2620 branch.merge_into_base(vec![10..11], cx);
2621 });
2622
2623 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2624}
2625
2626#[gpui::test]
2627fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2628 cx.update(|cx| init_settings(cx, |_| {}));
2629
2630 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2631 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2632
2633 // Make 2 edits, merge one into the base.
2634 branch.update(cx, |branch, cx| {
2635 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2636 branch.merge_into_base(vec![7..7], cx);
2637 });
2638 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2639 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2640
2641 // Undo the merge in the base buffer.
2642 base.update(cx, |base, cx| {
2643 base.undo(cx);
2644 });
2645 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2646 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2647
2648 // Merge that operation into the base again.
2649 branch.update(cx, |branch, cx| {
2650 branch.merge_into_base(vec![7..7], cx);
2651 });
2652 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2653 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2654}
2655
2656#[gpui::test]
2657async fn test_preview_edits(cx: &mut TestAppContext) {
2658 cx.update(|cx| {
2659 init_settings(cx, |_| {});
2660 theme::init(theme::LoadThemes::JustBase, cx);
2661 });
2662
2663 let insertion_style = HighlightStyle {
2664 background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
2665 ..Default::default()
2666 };
2667 let deletion_style = HighlightStyle {
2668 background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
2669 ..Default::default()
2670 };
2671
2672 // no edits
2673 assert_preview_edits(
2674 indoc! {"
2675 fn test_empty() -> bool {
2676 false
2677 }"
2678 },
2679 vec![],
2680 true,
2681 cx,
2682 |hl| {
2683 assert!(hl.text.is_empty());
2684 assert!(hl.highlights.is_empty());
2685 },
2686 )
2687 .await;
2688
2689 // only insertions
2690 assert_preview_edits(
2691 indoc! {"
2692 fn calculate_area(: f64) -> f64 {
2693 std::f64::consts::PI * .powi(2)
2694 }"
2695 },
2696 vec![
2697 (Point::new(0, 18)..Point::new(0, 18), "radius"),
2698 (Point::new(1, 27)..Point::new(1, 27), "radius"),
2699 ],
2700 true,
2701 cx,
2702 |hl| {
2703 assert_eq!(
2704 hl.text,
2705 indoc! {"
2706 fn calculate_area(radius: f64) -> f64 {
2707 std::f64::consts::PI * radius.powi(2)"
2708 }
2709 );
2710
2711 assert_eq!(hl.highlights.len(), 2);
2712 assert_eq!(hl.highlights[0], ((18..24), insertion_style));
2713 assert_eq!(hl.highlights[1], ((67..73), insertion_style));
2714 },
2715 )
2716 .await;
2717
2718 // insertions & deletions
2719 assert_preview_edits(
2720 indoc! {"
2721 struct Person {
2722 first_name: String,
2723 }
2724
2725 impl Person {
2726 fn first_name(&self) -> &String {
2727 &self.first_name
2728 }
2729 }"
2730 },
2731 vec![
2732 (Point::new(1, 4)..Point::new(1, 9), "last"),
2733 (Point::new(5, 7)..Point::new(5, 12), "last"),
2734 (Point::new(6, 14)..Point::new(6, 19), "last"),
2735 ],
2736 true,
2737 cx,
2738 |hl| {
2739 assert_eq!(
2740 hl.text,
2741 indoc! {"
2742 firstlast_name: String,
2743 }
2744
2745 impl Person {
2746 fn firstlast_name(&self) -> &String {
2747 &self.firstlast_name"
2748 }
2749 );
2750
2751 assert_eq!(hl.highlights.len(), 6);
2752 assert_eq!(hl.highlights[0], ((4..9), deletion_style));
2753 assert_eq!(hl.highlights[1], ((9..13), insertion_style));
2754 assert_eq!(hl.highlights[2], ((52..57), deletion_style));
2755 assert_eq!(hl.highlights[3], ((57..61), insertion_style));
2756 assert_eq!(hl.highlights[4], ((101..106), deletion_style));
2757 assert_eq!(hl.highlights[5], ((106..110), insertion_style));
2758 },
2759 )
2760 .await;
2761
2762 async fn assert_preview_edits(
2763 text: &str,
2764 edits: Vec<(Range<Point>, &str)>,
2765 include_deletions: bool,
2766 cx: &mut TestAppContext,
2767 assert_fn: impl Fn(HighlightedText),
2768 ) {
2769 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
2770 let edits = buffer.read_with(cx, |buffer, _| {
2771 edits
2772 .into_iter()
2773 .map(|(range, text)| {
2774 (
2775 buffer.anchor_before(range.start)..buffer.anchor_after(range.end),
2776 text.to_string(),
2777 )
2778 })
2779 .collect::<Vec<_>>()
2780 });
2781 let edit_preview = buffer
2782 .read_with(cx, |buffer, cx| {
2783 buffer.preview_edits(edits.clone().into(), cx)
2784 })
2785 .await;
2786 let highlighted_edits = cx.read(|cx| {
2787 edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
2788 });
2789 assert_fn(highlighted_edits);
2790 }
2791}
2792
2793#[gpui::test(iterations = 100)]
2794fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
2795 let min_peers = env::var("MIN_PEERS")
2796 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2797 .unwrap_or(1);
2798 let max_peers = env::var("MAX_PEERS")
2799 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2800 .unwrap_or(5);
2801 let operations = env::var("OPERATIONS")
2802 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2803 .unwrap_or(10);
2804
2805 let base_text_len = rng.gen_range(0..10);
2806 let base_text = RandomCharIter::new(&mut rng)
2807 .take(base_text_len)
2808 .collect::<String>();
2809 let mut replica_ids = Vec::new();
2810 let mut buffers = Vec::new();
2811 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2812 let base_buffer = cx.new(|cx| Buffer::local(base_text.as_str(), cx));
2813
2814 for i in 0..rng.gen_range(min_peers..=max_peers) {
2815 let buffer = cx.new(|cx| {
2816 let state = base_buffer.read(cx).to_proto(cx);
2817 let ops = cx
2818 .background_executor()
2819 .block(base_buffer.read(cx).serialize_ops(None, cx));
2820 let mut buffer =
2821 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2822 buffer.apply_ops(
2823 ops.into_iter()
2824 .map(|op| proto::deserialize_operation(op).unwrap()),
2825 cx,
2826 );
2827 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2828 let network = network.clone();
2829 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
2830 if let BufferEvent::Operation {
2831 operation,
2832 is_local: true,
2833 } = event
2834 {
2835 network.lock().broadcast(
2836 buffer.replica_id(),
2837 vec![proto::serialize_operation(operation)],
2838 );
2839 }
2840 })
2841 .detach();
2842 buffer
2843 });
2844
2845 buffers.push(buffer);
2846 replica_ids.push(i as ReplicaId);
2847 network.lock().add_peer(i as ReplicaId);
2848 log::info!("Adding initial peer with replica id {}", i);
2849 }
2850
2851 log::info!("initial text: {:?}", base_text);
2852
2853 let mut now = Instant::now();
2854 let mut mutation_count = operations;
2855 let mut next_diagnostic_id = 0;
2856 let mut active_selections = BTreeMap::default();
2857 loop {
2858 let replica_index = rng.gen_range(0..replica_ids.len());
2859 let replica_id = replica_ids[replica_index];
2860 let buffer = &mut buffers[replica_index];
2861 let mut new_buffer = None;
2862 match rng.gen_range(0..100) {
2863 0..=29 if mutation_count != 0 => {
2864 buffer.update(cx, |buffer, cx| {
2865 buffer.start_transaction_at(now);
2866 buffer.randomly_edit(&mut rng, 5, cx);
2867 buffer.end_transaction_at(now, cx);
2868 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2869 });
2870 mutation_count -= 1;
2871 }
2872 30..=39 if mutation_count != 0 => {
2873 buffer.update(cx, |buffer, cx| {
2874 if rng.gen_bool(0.2) {
2875 log::info!("peer {} clearing active selections", replica_id);
2876 active_selections.remove(&replica_id);
2877 buffer.remove_active_selections(cx);
2878 } else {
2879 let mut selections = Vec::new();
2880 for id in 0..rng.gen_range(1..=5) {
2881 let range = buffer.random_byte_range(0, &mut rng);
2882 selections.push(Selection {
2883 id,
2884 start: buffer.anchor_before(range.start),
2885 end: buffer.anchor_before(range.end),
2886 reversed: false,
2887 goal: SelectionGoal::None,
2888 });
2889 }
2890 let selections: Arc<[Selection<Anchor>]> = selections.into();
2891 log::info!(
2892 "peer {} setting active selections: {:?}",
2893 replica_id,
2894 selections
2895 );
2896 active_selections.insert(replica_id, selections.clone());
2897 buffer.set_active_selections(selections, false, Default::default(), cx);
2898 }
2899 });
2900 mutation_count -= 1;
2901 }
2902 40..=49 if mutation_count != 0 && replica_id == 0 => {
2903 let entry_count = rng.gen_range(1..=5);
2904 buffer.update(cx, |buffer, cx| {
2905 let diagnostics = DiagnosticSet::new(
2906 (0..entry_count).map(|_| {
2907 let range = buffer.random_byte_range(0, &mut rng);
2908 let range = range.to_point_utf16(buffer);
2909 let range = range.start..range.end;
2910 DiagnosticEntry {
2911 range,
2912 diagnostic: Diagnostic {
2913 message: post_inc(&mut next_diagnostic_id).to_string(),
2914 ..Default::default()
2915 },
2916 }
2917 }),
2918 buffer,
2919 );
2920 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
2921 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
2922 });
2923 mutation_count -= 1;
2924 }
2925 50..=59 if replica_ids.len() < max_peers => {
2926 let old_buffer_state = buffer.read(cx).to_proto(cx);
2927 let old_buffer_ops = cx
2928 .background_executor()
2929 .block(buffer.read(cx).serialize_ops(None, cx));
2930 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
2931 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
2932 .choose(&mut rng)
2933 .unwrap();
2934 log::info!(
2935 "Adding new replica {} (replicating from {})",
2936 new_replica_id,
2937 replica_id
2938 );
2939 new_buffer = Some(cx.new(|cx| {
2940 let mut new_buffer = Buffer::from_proto(
2941 new_replica_id,
2942 Capability::ReadWrite,
2943 old_buffer_state,
2944 None,
2945 )
2946 .unwrap();
2947 new_buffer.apply_ops(
2948 old_buffer_ops
2949 .into_iter()
2950 .map(|op| deserialize_operation(op).unwrap()),
2951 cx,
2952 );
2953 log::info!(
2954 "New replica {} text: {:?}",
2955 new_buffer.replica_id(),
2956 new_buffer.text()
2957 );
2958 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2959 let network = network.clone();
2960 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
2961 if let BufferEvent::Operation {
2962 operation,
2963 is_local: true,
2964 } = event
2965 {
2966 network.lock().broadcast(
2967 buffer.replica_id(),
2968 vec![proto::serialize_operation(operation)],
2969 );
2970 }
2971 })
2972 .detach();
2973 new_buffer
2974 }));
2975 network.lock().replicate(replica_id, new_replica_id);
2976
2977 if new_replica_id as usize == replica_ids.len() {
2978 replica_ids.push(new_replica_id);
2979 } else {
2980 let new_buffer = new_buffer.take().unwrap();
2981 while network.lock().has_unreceived(new_replica_id) {
2982 let ops = network
2983 .lock()
2984 .receive(new_replica_id)
2985 .into_iter()
2986 .map(|op| proto::deserialize_operation(op).unwrap());
2987 if ops.len() > 0 {
2988 log::info!(
2989 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
2990 new_replica_id,
2991 buffer.read(cx).version(),
2992 ops.len(),
2993 ops
2994 );
2995 new_buffer.update(cx, |new_buffer, cx| {
2996 new_buffer.apply_ops(ops, cx);
2997 });
2998 }
2999 }
3000 buffers[new_replica_id as usize] = new_buffer;
3001 }
3002 }
3003 60..=69 if mutation_count != 0 => {
3004 buffer.update(cx, |buffer, cx| {
3005 buffer.randomly_undo_redo(&mut rng, cx);
3006 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
3007 });
3008 mutation_count -= 1;
3009 }
3010 _ if network.lock().has_unreceived(replica_id) => {
3011 let ops = network
3012 .lock()
3013 .receive(replica_id)
3014 .into_iter()
3015 .map(|op| proto::deserialize_operation(op).unwrap());
3016 if ops.len() > 0 {
3017 log::info!(
3018 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3019 replica_id,
3020 buffer.read(cx).version(),
3021 ops.len(),
3022 ops
3023 );
3024 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
3025 }
3026 }
3027 _ => {}
3028 }
3029
3030 now += Duration::from_millis(rng.gen_range(0..=200));
3031 buffers.extend(new_buffer);
3032
3033 for buffer in &buffers {
3034 buffer.read(cx).check_invariants();
3035 }
3036
3037 if mutation_count == 0 && network.lock().is_idle() {
3038 break;
3039 }
3040 }
3041
3042 let first_buffer = buffers[0].read(cx).snapshot();
3043 for buffer in &buffers[1..] {
3044 let buffer = buffer.read(cx).snapshot();
3045 assert_eq!(
3046 buffer.version(),
3047 first_buffer.version(),
3048 "Replica {} version != Replica 0 version",
3049 buffer.replica_id()
3050 );
3051 assert_eq!(
3052 buffer.text(),
3053 first_buffer.text(),
3054 "Replica {} text != Replica 0 text",
3055 buffer.replica_id()
3056 );
3057 assert_eq!(
3058 buffer
3059 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
3060 .collect::<Vec<_>>(),
3061 first_buffer
3062 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
3063 .collect::<Vec<_>>(),
3064 "Replica {} diagnostics != Replica 0 diagnostics",
3065 buffer.replica_id()
3066 );
3067 }
3068
3069 for buffer in &buffers {
3070 let buffer = buffer.read(cx).snapshot();
3071 let actual_remote_selections = buffer
3072 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
3073 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3074 .collect::<Vec<_>>();
3075 let expected_remote_selections = active_selections
3076 .iter()
3077 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3078 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3079 .collect::<Vec<_>>();
3080 assert_eq!(
3081 actual_remote_selections,
3082 expected_remote_selections,
3083 "Replica {} remote selections != expected selections",
3084 buffer.replica_id()
3085 );
3086 }
3087}
3088
3089#[test]
3090fn test_contiguous_ranges() {
3091 assert_eq!(
3092 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3093 &[1..4, 5..7, 9..13]
3094 );
3095
3096 // Respects the `max_len` parameter
3097 assert_eq!(
3098 contiguous_ranges(
3099 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3100 3
3101 )
3102 .collect::<Vec<_>>(),
3103 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3104 );
3105}
3106
3107#[gpui::test(iterations = 500)]
3108fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3109 // Generate a random multi-line string containing
3110 // some lines with trailing whitespace.
3111 let mut text = String::new();
3112 for _ in 0..rng.gen_range(0..16) {
3113 for _ in 0..rng.gen_range(0..36) {
3114 text.push(match rng.gen_range(0..10) {
3115 0..=1 => ' ',
3116 3 => '\t',
3117 _ => rng.gen_range('a'..='z'),
3118 });
3119 }
3120 text.push('\n');
3121 }
3122
3123 match rng.gen_range(0..10) {
3124 // sometimes remove the last newline
3125 0..=1 => drop(text.pop()), //
3126
3127 // sometimes add extra newlines
3128 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
3129 _ => {}
3130 }
3131
3132 let rope = Rope::from(text.as_str());
3133 let actual_ranges = trailing_whitespace_ranges(&rope);
3134 let expected_ranges = TRAILING_WHITESPACE_REGEX
3135 .find_iter(&text)
3136 .map(|m| m.range())
3137 .collect::<Vec<_>>();
3138 assert_eq!(
3139 actual_ranges,
3140 expected_ranges,
3141 "wrong ranges for text lines:\n{:?}",
3142 text.split('\n').collect::<Vec<_>>()
3143 );
3144}
3145
3146#[gpui::test]
3147fn test_words_in_range(cx: &mut gpui::App) {
3148 init_settings(cx, |_| {});
3149
3150 // The first line are words excluded from the results with heuristics, we do not expect them in the test assertions.
3151 let contents = r#"
31520_isize 123 3.4 4
3153let word=öäpple.bar你 Öäpple word2-öÄpPlE-Pizza-word ÖÄPPLE word
3154 "#;
3155
3156 let buffer = cx.new(|cx| {
3157 let buffer = Buffer::local(contents, cx).with_language(Arc::new(rust_lang()), cx);
3158 assert_eq!(buffer.text(), contents);
3159 buffer.check_invariants();
3160 buffer
3161 });
3162
3163 buffer.update(cx, |buffer, _| {
3164 let snapshot = buffer.snapshot();
3165 assert_eq!(
3166 BTreeSet::from_iter(["Pizza".to_string()]),
3167 snapshot
3168 .words_in_range(WordsQuery {
3169 fuzzy_contents: Some("piz"),
3170 skip_digits: true,
3171 range: 0..snapshot.len(),
3172 })
3173 .into_keys()
3174 .collect::<BTreeSet<_>>()
3175 );
3176 assert_eq!(
3177 BTreeSet::from_iter([
3178 "öäpple".to_string(),
3179 "Öäpple".to_string(),
3180 "öÄpPlE".to_string(),
3181 "ÖÄPPLE".to_string(),
3182 ]),
3183 snapshot
3184 .words_in_range(WordsQuery {
3185 fuzzy_contents: Some("öp"),
3186 skip_digits: true,
3187 range: 0..snapshot.len(),
3188 })
3189 .into_keys()
3190 .collect::<BTreeSet<_>>()
3191 );
3192 assert_eq!(
3193 BTreeSet::from_iter([
3194 "öÄpPlE".to_string(),
3195 "Öäpple".to_string(),
3196 "ÖÄPPLE".to_string(),
3197 "öäpple".to_string(),
3198 ]),
3199 snapshot
3200 .words_in_range(WordsQuery {
3201 fuzzy_contents: Some("öÄ"),
3202 skip_digits: true,
3203 range: 0..snapshot.len(),
3204 })
3205 .into_keys()
3206 .collect::<BTreeSet<_>>()
3207 );
3208 assert_eq!(
3209 BTreeSet::default(),
3210 snapshot
3211 .words_in_range(WordsQuery {
3212 fuzzy_contents: Some("öÄ好"),
3213 skip_digits: true,
3214 range: 0..snapshot.len(),
3215 })
3216 .into_keys()
3217 .collect::<BTreeSet<_>>()
3218 );
3219 assert_eq!(
3220 BTreeSet::from_iter(["bar你".to_string(),]),
3221 snapshot
3222 .words_in_range(WordsQuery {
3223 fuzzy_contents: Some("你"),
3224 skip_digits: true,
3225 range: 0..snapshot.len(),
3226 })
3227 .into_keys()
3228 .collect::<BTreeSet<_>>()
3229 );
3230 assert_eq!(
3231 BTreeSet::default(),
3232 snapshot
3233 .words_in_range(WordsQuery {
3234 fuzzy_contents: Some(""),
3235 skip_digits: true,
3236 range: 0..snapshot.len(),
3237 },)
3238 .into_keys()
3239 .collect::<BTreeSet<_>>()
3240 );
3241 assert_eq!(
3242 BTreeSet::from_iter([
3243 "bar你".to_string(),
3244 "öÄpPlE".to_string(),
3245 "Öäpple".to_string(),
3246 "ÖÄPPLE".to_string(),
3247 "öäpple".to_string(),
3248 "let".to_string(),
3249 "Pizza".to_string(),
3250 "word".to_string(),
3251 "word2".to_string(),
3252 ]),
3253 snapshot
3254 .words_in_range(WordsQuery {
3255 fuzzy_contents: None,
3256 skip_digits: true,
3257 range: 0..snapshot.len(),
3258 })
3259 .into_keys()
3260 .collect::<BTreeSet<_>>()
3261 );
3262 assert_eq!(
3263 BTreeSet::from_iter([
3264 "0_isize".to_string(),
3265 "123".to_string(),
3266 "3".to_string(),
3267 "4".to_string(),
3268 "bar你".to_string(),
3269 "öÄpPlE".to_string(),
3270 "Öäpple".to_string(),
3271 "ÖÄPPLE".to_string(),
3272 "öäpple".to_string(),
3273 "let".to_string(),
3274 "Pizza".to_string(),
3275 "word".to_string(),
3276 "word2".to_string(),
3277 ]),
3278 snapshot
3279 .words_in_range(WordsQuery {
3280 fuzzy_contents: None,
3281 skip_digits: false,
3282 range: 0..snapshot.len(),
3283 })
3284 .into_keys()
3285 .collect::<BTreeSet<_>>()
3286 );
3287 });
3288}
3289
3290fn ruby_lang() -> Language {
3291 Language::new(
3292 LanguageConfig {
3293 name: "Ruby".into(),
3294 matcher: LanguageMatcher {
3295 path_suffixes: vec!["rb".to_string()],
3296 ..Default::default()
3297 },
3298 line_comments: vec!["# ".into()],
3299 ..Default::default()
3300 },
3301 Some(tree_sitter_ruby::LANGUAGE.into()),
3302 )
3303 .with_indents_query(
3304 r#"
3305 (class "end" @end) @indent
3306 (method "end" @end) @indent
3307 (rescue) @outdent
3308 (then) @indent
3309 "#,
3310 )
3311 .unwrap()
3312}
3313
3314fn html_lang() -> Language {
3315 Language::new(
3316 LanguageConfig {
3317 name: LanguageName::new("HTML"),
3318 block_comment: Some(("<!--".into(), "-->".into())),
3319 ..Default::default()
3320 },
3321 Some(tree_sitter_html::LANGUAGE.into()),
3322 )
3323 .with_indents_query(
3324 "
3325 (element
3326 (start_tag) @start
3327 (end_tag)? @end) @indent
3328 ",
3329 )
3330 .unwrap()
3331 .with_injection_query(
3332 r#"
3333 (script_element
3334 (raw_text) @injection.content
3335 (#set! injection.language "javascript"))
3336 "#,
3337 )
3338 .unwrap()
3339}
3340
3341fn erb_lang() -> Language {
3342 Language::new(
3343 LanguageConfig {
3344 name: "ERB".into(),
3345 matcher: LanguageMatcher {
3346 path_suffixes: vec!["erb".to_string()],
3347 ..Default::default()
3348 },
3349 block_comment: Some(("<%#".into(), "%>".into())),
3350 ..Default::default()
3351 },
3352 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3353 )
3354 .with_injection_query(
3355 r#"
3356 (
3357 (code) @injection.content
3358 (#set! injection.language "ruby")
3359 (#set! injection.combined)
3360 )
3361
3362 (
3363 (content) @injection.content
3364 (#set! injection.language "html")
3365 (#set! injection.combined)
3366 )
3367 "#,
3368 )
3369 .unwrap()
3370}
3371
3372fn rust_lang() -> Language {
3373 Language::new(
3374 LanguageConfig {
3375 name: "Rust".into(),
3376 matcher: LanguageMatcher {
3377 path_suffixes: vec!["rs".to_string()],
3378 ..Default::default()
3379 },
3380 ..Default::default()
3381 },
3382 Some(tree_sitter_rust::LANGUAGE.into()),
3383 )
3384 .with_indents_query(
3385 r#"
3386 (call_expression) @indent
3387 (field_expression) @indent
3388 (_ "(" ")" @end) @indent
3389 (_ "{" "}" @end) @indent
3390 "#,
3391 )
3392 .unwrap()
3393 .with_brackets_query(
3394 r#"
3395 ("{" @open "}" @close)
3396 "#,
3397 )
3398 .unwrap()
3399 .with_text_object_query(
3400 r#"
3401 (function_item
3402 body: (_
3403 "{"
3404 (_)* @function.inside
3405 "}" )) @function.around
3406
3407 (line_comment)+ @comment.around
3408
3409 (block_comment) @comment.around
3410 "#,
3411 )
3412 .unwrap()
3413 .with_outline_query(
3414 r#"
3415 (line_comment) @annotation
3416
3417 (struct_item
3418 "struct" @context
3419 name: (_) @name) @item
3420 (enum_item
3421 "enum" @context
3422 name: (_) @name) @item
3423 (enum_variant
3424 name: (_) @name) @item
3425 (field_declaration
3426 name: (_) @name) @item
3427 (impl_item
3428 "impl" @context
3429 trait: (_)? @name
3430 "for"? @context
3431 type: (_) @name
3432 body: (_ "{" (_)* "}")) @item
3433 (function_item
3434 "fn" @context
3435 name: (_) @name) @item
3436 (mod_item
3437 "mod" @context
3438 name: (_) @name) @item
3439 "#,
3440 )
3441 .unwrap()
3442}
3443
3444fn json_lang() -> Language {
3445 Language::new(
3446 LanguageConfig {
3447 name: "Json".into(),
3448 matcher: LanguageMatcher {
3449 path_suffixes: vec!["js".to_string()],
3450 ..Default::default()
3451 },
3452 ..Default::default()
3453 },
3454 Some(tree_sitter_json::LANGUAGE.into()),
3455 )
3456}
3457
3458fn javascript_lang() -> Language {
3459 Language::new(
3460 LanguageConfig {
3461 name: "JavaScript".into(),
3462 ..Default::default()
3463 },
3464 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3465 )
3466 .with_brackets_query(
3467 r#"
3468 ("{" @open "}" @close)
3469 ("(" @open ")" @close)
3470 "#,
3471 )
3472 .unwrap()
3473 .with_indents_query(
3474 r#"
3475 (object "}" @end) @indent
3476 "#,
3477 )
3478 .unwrap()
3479}
3480
3481pub fn markdown_lang() -> Language {
3482 Language::new(
3483 LanguageConfig {
3484 name: "Markdown".into(),
3485 matcher: LanguageMatcher {
3486 path_suffixes: vec!["md".into()],
3487 ..Default::default()
3488 },
3489 ..Default::default()
3490 },
3491 Some(tree_sitter_md::LANGUAGE.into()),
3492 )
3493 .with_injection_query(
3494 r#"
3495 (fenced_code_block
3496 (info_string
3497 (language) @injection.language)
3498 (code_fence_content) @injection.content)
3499
3500 ((inline) @injection.content
3501 (#set! injection.language "markdown-inline"))
3502 "#,
3503 )
3504 .unwrap()
3505}
3506
3507pub fn markdown_inline_lang() -> Language {
3508 Language::new(
3509 LanguageConfig {
3510 name: "Markdown-Inline".into(),
3511 hidden: true,
3512 ..LanguageConfig::default()
3513 },
3514 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3515 )
3516 .with_highlights_query("(emphasis) @emphasis")
3517 .unwrap()
3518}
3519
3520fn get_tree_sexp(buffer: &Entity<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3521 buffer.update(cx, |buffer, _| {
3522 let snapshot = buffer.snapshot();
3523 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3524 layers[0].node().to_sexp()
3525 })
3526}
3527
3528// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3529fn assert_bracket_pairs(
3530 selection_text: &'static str,
3531 bracket_pair_texts: Vec<&'static str>,
3532 language: Language,
3533 cx: &mut App,
3534) {
3535 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3536 let buffer =
3537 cx.new(|cx| Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx));
3538 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3539
3540 let selection_range = selection_ranges[0].clone();
3541
3542 let bracket_pairs = bracket_pair_texts
3543 .into_iter()
3544 .map(|pair_text| {
3545 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3546 assert_eq!(bracket_text, expected_text);
3547 (ranges[0].clone(), ranges[1].clone())
3548 })
3549 .collect::<Vec<_>>();
3550
3551 assert_set_eq!(
3552 buffer
3553 .bracket_ranges(selection_range)
3554 .map(|pair| (pair.open_range, pair.close_range))
3555 .collect::<Vec<_>>(),
3556 bracket_pairs
3557 );
3558}
3559
3560fn init_settings(cx: &mut App, f: fn(&mut AllLanguageSettingsContent)) {
3561 let settings_store = SettingsStore::test(cx);
3562 cx.set_global(settings_store);
3563 crate::init(cx);
3564 cx.update_global::<SettingsStore, _>(|settings, cx| {
3565 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3566 });
3567}