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