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