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 block_comment: Some(("/*".into(), "*/".into())),
2220 brackets: BracketPairConfig {
2221 pairs: vec![
2222 BracketPair {
2223 start: "{".into(),
2224 end: "}".into(),
2225 close: true,
2226 surround: true,
2227 newline: false,
2228 },
2229 BracketPair {
2230 start: "'".into(),
2231 end: "'".into(),
2232 close: true,
2233 surround: true,
2234 newline: false,
2235 },
2236 ],
2237 disabled_scopes_by_bracket_ix: vec![
2238 Vec::new(), //
2239 vec!["string".into(), "comment".into()], // single quotes disabled
2240 ],
2241 },
2242 overrides: [(
2243 "element".into(),
2244 LanguageConfigOverride {
2245 line_comments: Override::Remove { remove: true },
2246 block_comment: Override::Set(("{/*".into(), "*/}".into())),
2247 ..Default::default()
2248 },
2249 )]
2250 .into_iter()
2251 .collect(),
2252 ..Default::default()
2253 },
2254 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2255 )
2256 .with_override_query(
2257 r#"
2258 (jsx_element) @element
2259 (string) @string
2260 (comment) @comment.inclusive
2261 [
2262 (jsx_opening_element)
2263 (jsx_closing_element)
2264 (jsx_expression)
2265 ] @default
2266 "#,
2267 )
2268 .unwrap();
2269
2270 let text = r#"
2271 a["b"] = <C d="e">
2272 <F></F>
2273 { g() }
2274 </C>; // a comment
2275 "#
2276 .unindent();
2277
2278 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2279 let snapshot = buffer.snapshot();
2280
2281 let config = snapshot.language_scope_at(0).unwrap();
2282 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2283 assert_eq!(
2284 config.block_comment_delimiters(),
2285 Some((&"/*".into(), &"*/".into()))
2286 );
2287 // Both bracket pairs are enabled
2288 assert_eq!(
2289 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2290 &[true, true]
2291 );
2292
2293 let comment_config = snapshot
2294 .language_scope_at(text.find("comment").unwrap() + "comment".len())
2295 .unwrap();
2296 assert_eq!(
2297 comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2298 &[true, false]
2299 );
2300
2301 let string_config = snapshot
2302 .language_scope_at(text.find("b\"").unwrap())
2303 .unwrap();
2304 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2305 assert_eq!(
2306 string_config.block_comment_delimiters(),
2307 Some((&"/*".into(), &"*/".into()))
2308 );
2309 // Second bracket pair is disabled
2310 assert_eq!(
2311 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2312 &[true, false]
2313 );
2314
2315 // In between JSX tags: use the `element` override.
2316 let element_config = snapshot
2317 .language_scope_at(text.find("<F>").unwrap())
2318 .unwrap();
2319 // TODO nested blocks after newlines are captured with all whitespaces
2320 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2321 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2322 // assert_eq!(
2323 // element_config.block_comment_delimiters(),
2324 // Some((&"{/*".into(), &"*/}".into()))
2325 // );
2326 assert_eq!(
2327 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2328 &[true, true]
2329 );
2330
2331 // Within a JSX tag: use the default config.
2332 let tag_config = snapshot
2333 .language_scope_at(text.find(" d=").unwrap() + 1)
2334 .unwrap();
2335 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2336 assert_eq!(
2337 tag_config.block_comment_delimiters(),
2338 Some((&"/*".into(), &"*/".into()))
2339 );
2340 assert_eq!(
2341 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2342 &[true, true]
2343 );
2344
2345 // In a JSX expression: use the default config.
2346 let expression_in_element_config = snapshot
2347 .language_scope_at(text.find('{').unwrap() + 1)
2348 .unwrap();
2349 assert_eq!(
2350 expression_in_element_config.line_comment_prefixes(),
2351 &[Arc::from("// ")]
2352 );
2353 assert_eq!(
2354 expression_in_element_config.block_comment_delimiters(),
2355 Some((&"/*".into(), &"*/".into()))
2356 );
2357 assert_eq!(
2358 expression_in_element_config
2359 .brackets()
2360 .map(|e| e.1)
2361 .collect::<Vec<_>>(),
2362 &[true, true]
2363 );
2364
2365 buffer
2366 });
2367}
2368
2369#[gpui::test]
2370fn test_language_scope_at_with_rust(cx: &mut App) {
2371 init_settings(cx, |_| {});
2372
2373 cx.new(|cx| {
2374 let language = Language::new(
2375 LanguageConfig {
2376 name: "Rust".into(),
2377 brackets: BracketPairConfig {
2378 pairs: vec![
2379 BracketPair {
2380 start: "{".into(),
2381 end: "}".into(),
2382 close: true,
2383 surround: true,
2384 newline: false,
2385 },
2386 BracketPair {
2387 start: "'".into(),
2388 end: "'".into(),
2389 close: true,
2390 surround: true,
2391 newline: false,
2392 },
2393 ],
2394 disabled_scopes_by_bracket_ix: vec![
2395 Vec::new(), //
2396 vec!["string".into()],
2397 ],
2398 },
2399 ..Default::default()
2400 },
2401 Some(tree_sitter_rust::LANGUAGE.into()),
2402 )
2403 .with_override_query(
2404 r#"
2405 (string_literal) @string
2406 "#,
2407 )
2408 .unwrap();
2409
2410 let text = r#"
2411 const S: &'static str = "hello";
2412 "#
2413 .unindent();
2414
2415 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2416 let snapshot = buffer.snapshot();
2417
2418 // By default, all brackets are enabled
2419 let config = snapshot.language_scope_at(0).unwrap();
2420 assert_eq!(
2421 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2422 &[true, true]
2423 );
2424
2425 // Within a string, the quotation brackets are disabled.
2426 let string_config = snapshot
2427 .language_scope_at(text.find("ello").unwrap())
2428 .unwrap();
2429 assert_eq!(
2430 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2431 &[true, false]
2432 );
2433
2434 buffer
2435 });
2436}
2437
2438#[gpui::test]
2439fn test_language_scope_at_with_combined_injections(cx: &mut App) {
2440 init_settings(cx, |_| {});
2441
2442 cx.new(|cx| {
2443 let text = r#"
2444 <ol>
2445 <% people.each do |person| %>
2446 <li>
2447 <%= person.name %>
2448 </li>
2449 <% end %>
2450 </ol>
2451 "#
2452 .unindent();
2453
2454 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2455 language_registry.add(Arc::new(ruby_lang()));
2456 language_registry.add(Arc::new(html_lang()));
2457 language_registry.add(Arc::new(erb_lang()));
2458
2459 let mut buffer = Buffer::local(text, cx);
2460 buffer.set_language_registry(language_registry.clone());
2461 buffer.set_language(
2462 language_registry
2463 .language_for_name("ERB")
2464 .now_or_never()
2465 .unwrap()
2466 .ok(),
2467 cx,
2468 );
2469
2470 let snapshot = buffer.snapshot();
2471 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2472 assert_eq!(html_config.line_comment_prefixes(), &[]);
2473 assert_eq!(
2474 html_config.block_comment_delimiters(),
2475 Some((&"<!--".into(), &"-->".into()))
2476 );
2477
2478 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2479 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2480 assert_eq!(ruby_config.block_comment_delimiters(), None);
2481
2482 buffer
2483 });
2484}
2485
2486#[gpui::test]
2487fn test_language_at_with_hidden_languages(cx: &mut App) {
2488 init_settings(cx, |_| {});
2489
2490 cx.new(|cx| {
2491 let text = r#"
2492 this is an *emphasized* word.
2493 "#
2494 .unindent();
2495
2496 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2497 language_registry.add(Arc::new(markdown_lang()));
2498 language_registry.add(Arc::new(markdown_inline_lang()));
2499
2500 let mut buffer = Buffer::local(text, cx);
2501 buffer.set_language_registry(language_registry.clone());
2502 buffer.set_language(
2503 language_registry
2504 .language_for_name("Markdown")
2505 .now_or_never()
2506 .unwrap()
2507 .ok(),
2508 cx,
2509 );
2510
2511 let snapshot = buffer.snapshot();
2512
2513 for point in [Point::new(0, 4), Point::new(0, 16)] {
2514 let config = snapshot.language_scope_at(point).unwrap();
2515 assert_eq!(config.language_name(), "Markdown".into());
2516
2517 let language = snapshot.language_at(point).unwrap();
2518 assert_eq!(language.name().as_ref(), "Markdown");
2519 }
2520
2521 buffer
2522 });
2523}
2524
2525#[gpui::test]
2526fn test_language_at_for_markdown_code_block(cx: &mut App) {
2527 init_settings(cx, |_| {});
2528
2529 cx.new(|cx| {
2530 let text = r#"
2531 ```rs
2532 let a = 2;
2533 // let b = 3;
2534 ```
2535 "#
2536 .unindent();
2537
2538 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2539 language_registry.add(Arc::new(markdown_lang()));
2540 language_registry.add(Arc::new(markdown_inline_lang()));
2541 language_registry.add(Arc::new(rust_lang()));
2542
2543 let mut buffer = Buffer::local(text, cx);
2544 buffer.set_language_registry(language_registry.clone());
2545 buffer.set_language(
2546 language_registry
2547 .language_for_name("Markdown")
2548 .now_or_never()
2549 .unwrap()
2550 .ok(),
2551 cx,
2552 );
2553
2554 let snapshot = buffer.snapshot();
2555
2556 // Test points in the code line
2557 for point in [Point::new(1, 4), Point::new(1, 6)] {
2558 let config = snapshot.language_scope_at(point).unwrap();
2559 assert_eq!(config.language_name(), "Rust".into());
2560
2561 let language = snapshot.language_at(point).unwrap();
2562 assert_eq!(language.name().as_ref(), "Rust");
2563 }
2564
2565 // Test points in the comment line to verify it's still detected as Rust
2566 for point in [Point::new(2, 4), Point::new(2, 6)] {
2567 let config = snapshot.language_scope_at(point).unwrap();
2568 assert_eq!(config.language_name(), "Rust".into());
2569
2570 let language = snapshot.language_at(point).unwrap();
2571 assert_eq!(language.name().as_ref(), "Rust");
2572 }
2573
2574 buffer
2575 });
2576}
2577
2578#[gpui::test]
2579fn test_serialization(cx: &mut gpui::App) {
2580 let mut now = Instant::now();
2581
2582 let buffer1 = cx.new(|cx| {
2583 let mut buffer = Buffer::local("abc", cx);
2584 buffer.edit([(3..3, "D")], None, cx);
2585
2586 now += Duration::from_secs(1);
2587 buffer.start_transaction_at(now);
2588 buffer.edit([(4..4, "E")], None, cx);
2589 buffer.end_transaction_at(now, cx);
2590 assert_eq!(buffer.text(), "abcDE");
2591
2592 buffer.undo(cx);
2593 assert_eq!(buffer.text(), "abcD");
2594
2595 buffer.edit([(4..4, "F")], None, cx);
2596 assert_eq!(buffer.text(), "abcDF");
2597 buffer
2598 });
2599 assert_eq!(buffer1.read(cx).text(), "abcDF");
2600
2601 let state = buffer1.read(cx).to_proto(cx);
2602 let ops = cx
2603 .background_executor()
2604 .block(buffer1.read(cx).serialize_ops(None, cx));
2605 let buffer2 = cx.new(|cx| {
2606 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2607 buffer.apply_ops(
2608 ops.into_iter()
2609 .map(|op| proto::deserialize_operation(op).unwrap()),
2610 cx,
2611 );
2612 buffer
2613 });
2614 assert_eq!(buffer2.read(cx).text(), "abcDF");
2615}
2616
2617#[gpui::test]
2618fn test_branch_and_merge(cx: &mut TestAppContext) {
2619 cx.update(|cx| init_settings(cx, |_| {}));
2620
2621 let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2622
2623 // Create a remote replica of the base buffer.
2624 let base_replica = cx.new(|cx| {
2625 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2626 });
2627 base.update(cx, |_buffer, cx| {
2628 cx.subscribe(&base_replica, |this, _, event, cx| {
2629 if let BufferEvent::Operation {
2630 operation,
2631 is_local: true,
2632 } = event
2633 {
2634 this.apply_ops([operation.clone()], cx);
2635 }
2636 })
2637 .detach();
2638 });
2639
2640 // Create a branch, which initially has the same state as the base buffer.
2641 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2642 branch.read_with(cx, |buffer, _| {
2643 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2644 });
2645
2646 // Edits to the branch are not applied to the base.
2647 branch.update(cx, |buffer, cx| {
2648 buffer.edit(
2649 [
2650 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2651 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2652 ],
2653 None,
2654 cx,
2655 )
2656 });
2657 branch.read_with(cx, |buffer, cx| {
2658 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2659 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2660 });
2661
2662 // Convert from branch buffer ranges to the corresponding ranges in the
2663 // base buffer.
2664 branch.read_with(cx, |buffer, cx| {
2665 assert_eq!(
2666 buffer.range_to_version(4..7, &base.read(cx).version()),
2667 4..4
2668 );
2669 assert_eq!(
2670 buffer.range_to_version(2..9, &base.read(cx).version()),
2671 2..5
2672 );
2673 });
2674
2675 // Edits to the base are applied to the branch.
2676 base.update(cx, |buffer, cx| {
2677 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2678 });
2679 branch.read_with(cx, |buffer, cx| {
2680 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2681 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2682 });
2683
2684 // Edits to any replica of the base are applied to the branch.
2685 base_replica.update(cx, |buffer, cx| {
2686 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2687 });
2688 branch.read_with(cx, |buffer, cx| {
2689 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2690 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2691 });
2692
2693 // Merging the branch applies all of its changes to the base.
2694 branch.update(cx, |buffer, cx| {
2695 buffer.merge_into_base(Vec::new(), cx);
2696 });
2697
2698 branch.update(cx, |buffer, cx| {
2699 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2700 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2701 });
2702}
2703
2704#[gpui::test]
2705fn test_merge_into_base(cx: &mut TestAppContext) {
2706 cx.update(|cx| init_settings(cx, |_| {}));
2707
2708 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2709 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2710
2711 // Make 3 edits, merge one into the base.
2712 branch.update(cx, |branch, cx| {
2713 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2714 branch.merge_into_base(vec![5..8], cx);
2715 });
2716
2717 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2718 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2719
2720 // Undo the one already-merged edit. Merge that into the base.
2721 branch.update(cx, |branch, cx| {
2722 branch.edit([(7..9, "hi")], None, cx);
2723 branch.merge_into_base(vec![5..8], cx);
2724 });
2725 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2726
2727 // Merge an insertion into the base.
2728 branch.update(cx, |branch, cx| {
2729 branch.merge_into_base(vec![11..11], cx);
2730 });
2731
2732 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2733 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2734
2735 // Deleted the inserted text and merge that into the base.
2736 branch.update(cx, |branch, cx| {
2737 branch.edit([(11..14, "")], None, cx);
2738 branch.merge_into_base(vec![10..11], cx);
2739 });
2740
2741 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2742}
2743
2744#[gpui::test]
2745fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2746 cx.update(|cx| init_settings(cx, |_| {}));
2747
2748 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2749 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2750
2751 // Make 2 edits, merge one into the base.
2752 branch.update(cx, |branch, cx| {
2753 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2754 branch.merge_into_base(vec![7..7], cx);
2755 });
2756 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2757 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2758
2759 // Undo the merge in the base buffer.
2760 base.update(cx, |base, cx| {
2761 base.undo(cx);
2762 });
2763 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2764 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2765
2766 // Merge that operation into the base again.
2767 branch.update(cx, |branch, cx| {
2768 branch.merge_into_base(vec![7..7], cx);
2769 });
2770 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2771 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2772}
2773
2774#[gpui::test]
2775async fn test_preview_edits(cx: &mut TestAppContext) {
2776 cx.update(|cx| {
2777 init_settings(cx, |_| {});
2778 theme::init(theme::LoadThemes::JustBase, cx);
2779 });
2780
2781 let insertion_style = HighlightStyle {
2782 background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
2783 ..Default::default()
2784 };
2785 let deletion_style = HighlightStyle {
2786 background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
2787 ..Default::default()
2788 };
2789
2790 // no edits
2791 assert_preview_edits(
2792 indoc! {"
2793 fn test_empty() -> bool {
2794 false
2795 }"
2796 },
2797 vec![],
2798 true,
2799 cx,
2800 |hl| {
2801 assert!(hl.text.is_empty());
2802 assert!(hl.highlights.is_empty());
2803 },
2804 )
2805 .await;
2806
2807 // only insertions
2808 assert_preview_edits(
2809 indoc! {"
2810 fn calculate_area(: f64) -> f64 {
2811 std::f64::consts::PI * .powi(2)
2812 }"
2813 },
2814 vec![
2815 (Point::new(0, 18)..Point::new(0, 18), "radius"),
2816 (Point::new(1, 27)..Point::new(1, 27), "radius"),
2817 ],
2818 true,
2819 cx,
2820 |hl| {
2821 assert_eq!(
2822 hl.text,
2823 indoc! {"
2824 fn calculate_area(radius: f64) -> f64 {
2825 std::f64::consts::PI * radius.powi(2)"
2826 }
2827 );
2828
2829 assert_eq!(hl.highlights.len(), 2);
2830 assert_eq!(hl.highlights[0], ((18..24), insertion_style));
2831 assert_eq!(hl.highlights[1], ((67..73), insertion_style));
2832 },
2833 )
2834 .await;
2835
2836 // insertions & deletions
2837 assert_preview_edits(
2838 indoc! {"
2839 struct Person {
2840 first_name: String,
2841 }
2842
2843 impl Person {
2844 fn first_name(&self) -> &String {
2845 &self.first_name
2846 }
2847 }"
2848 },
2849 vec![
2850 (Point::new(1, 4)..Point::new(1, 9), "last"),
2851 (Point::new(5, 7)..Point::new(5, 12), "last"),
2852 (Point::new(6, 14)..Point::new(6, 19), "last"),
2853 ],
2854 true,
2855 cx,
2856 |hl| {
2857 assert_eq!(
2858 hl.text,
2859 indoc! {"
2860 firstlast_name: String,
2861 }
2862
2863 impl Person {
2864 fn firstlast_name(&self) -> &String {
2865 &self.firstlast_name"
2866 }
2867 );
2868
2869 assert_eq!(hl.highlights.len(), 6);
2870 assert_eq!(hl.highlights[0], ((4..9), deletion_style));
2871 assert_eq!(hl.highlights[1], ((9..13), insertion_style));
2872 assert_eq!(hl.highlights[2], ((52..57), deletion_style));
2873 assert_eq!(hl.highlights[3], ((57..61), insertion_style));
2874 assert_eq!(hl.highlights[4], ((101..106), deletion_style));
2875 assert_eq!(hl.highlights[5], ((106..110), insertion_style));
2876 },
2877 )
2878 .await;
2879
2880 async fn assert_preview_edits(
2881 text: &str,
2882 edits: Vec<(Range<Point>, &str)>,
2883 include_deletions: bool,
2884 cx: &mut TestAppContext,
2885 assert_fn: impl Fn(HighlightedText),
2886 ) {
2887 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
2888 let edits = buffer.read_with(cx, |buffer, _| {
2889 edits
2890 .into_iter()
2891 .map(|(range, text)| {
2892 (
2893 buffer.anchor_before(range.start)..buffer.anchor_after(range.end),
2894 text.to_string(),
2895 )
2896 })
2897 .collect::<Vec<_>>()
2898 });
2899 let edit_preview = buffer
2900 .read_with(cx, |buffer, cx| {
2901 buffer.preview_edits(edits.clone().into(), cx)
2902 })
2903 .await;
2904 let highlighted_edits = cx.read(|cx| {
2905 edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
2906 });
2907 assert_fn(highlighted_edits);
2908 }
2909}
2910
2911#[gpui::test(iterations = 100)]
2912fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
2913 let min_peers = env::var("MIN_PEERS")
2914 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
2915 .unwrap_or(1);
2916 let max_peers = env::var("MAX_PEERS")
2917 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
2918 .unwrap_or(5);
2919 let operations = env::var("OPERATIONS")
2920 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2921 .unwrap_or(10);
2922
2923 let base_text_len = rng.gen_range(0..10);
2924 let base_text = RandomCharIter::new(&mut rng)
2925 .take(base_text_len)
2926 .collect::<String>();
2927 let mut replica_ids = Vec::new();
2928 let mut buffers = Vec::new();
2929 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
2930 let base_buffer = cx.new(|cx| Buffer::local(base_text.as_str(), cx));
2931
2932 for i in 0..rng.gen_range(min_peers..=max_peers) {
2933 let buffer = cx.new(|cx| {
2934 let state = base_buffer.read(cx).to_proto(cx);
2935 let ops = cx
2936 .background_executor()
2937 .block(base_buffer.read(cx).serialize_ops(None, cx));
2938 let mut buffer =
2939 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
2940 buffer.apply_ops(
2941 ops.into_iter()
2942 .map(|op| proto::deserialize_operation(op).unwrap()),
2943 cx,
2944 );
2945 buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
2946 let network = network.clone();
2947 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
2948 if let BufferEvent::Operation {
2949 operation,
2950 is_local: true,
2951 } = event
2952 {
2953 network.lock().broadcast(
2954 buffer.replica_id(),
2955 vec![proto::serialize_operation(operation)],
2956 );
2957 }
2958 })
2959 .detach();
2960 buffer
2961 });
2962
2963 buffers.push(buffer);
2964 replica_ids.push(i as ReplicaId);
2965 network.lock().add_peer(i as ReplicaId);
2966 log::info!("Adding initial peer with replica id {}", i);
2967 }
2968
2969 log::info!("initial text: {:?}", base_text);
2970
2971 let mut now = Instant::now();
2972 let mut mutation_count = operations;
2973 let mut next_diagnostic_id = 0;
2974 let mut active_selections = BTreeMap::default();
2975 loop {
2976 let replica_index = rng.gen_range(0..replica_ids.len());
2977 let replica_id = replica_ids[replica_index];
2978 let buffer = &mut buffers[replica_index];
2979 let mut new_buffer = None;
2980 match rng.gen_range(0..100) {
2981 0..=29 if mutation_count != 0 => {
2982 buffer.update(cx, |buffer, cx| {
2983 buffer.start_transaction_at(now);
2984 buffer.randomly_edit(&mut rng, 5, cx);
2985 buffer.end_transaction_at(now, cx);
2986 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
2987 });
2988 mutation_count -= 1;
2989 }
2990 30..=39 if mutation_count != 0 => {
2991 buffer.update(cx, |buffer, cx| {
2992 if rng.gen_bool(0.2) {
2993 log::info!("peer {} clearing active selections", replica_id);
2994 active_selections.remove(&replica_id);
2995 buffer.remove_active_selections(cx);
2996 } else {
2997 let mut selections = Vec::new();
2998 for id in 0..rng.gen_range(1..=5) {
2999 let range = buffer.random_byte_range(0, &mut rng);
3000 selections.push(Selection {
3001 id,
3002 start: buffer.anchor_before(range.start),
3003 end: buffer.anchor_before(range.end),
3004 reversed: false,
3005 goal: SelectionGoal::None,
3006 });
3007 }
3008 let selections: Arc<[Selection<Anchor>]> = selections.into();
3009 log::info!(
3010 "peer {} setting active selections: {:?}",
3011 replica_id,
3012 selections
3013 );
3014 active_selections.insert(replica_id, selections.clone());
3015 buffer.set_active_selections(selections, false, Default::default(), cx);
3016 }
3017 });
3018 mutation_count -= 1;
3019 }
3020 40..=49 if mutation_count != 0 && replica_id == 0 => {
3021 let entry_count = rng.gen_range(1..=5);
3022 buffer.update(cx, |buffer, cx| {
3023 let diagnostics = DiagnosticSet::new(
3024 (0..entry_count).map(|_| {
3025 let range = buffer.random_byte_range(0, &mut rng);
3026 let range = range.to_point_utf16(buffer);
3027 let range = range.start..range.end;
3028 DiagnosticEntry {
3029 range,
3030 diagnostic: Diagnostic {
3031 message: post_inc(&mut next_diagnostic_id).to_string(),
3032 ..Default::default()
3033 },
3034 }
3035 }),
3036 buffer,
3037 );
3038 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
3039 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
3040 });
3041 mutation_count -= 1;
3042 }
3043 50..=59 if replica_ids.len() < max_peers => {
3044 let old_buffer_state = buffer.read(cx).to_proto(cx);
3045 let old_buffer_ops = cx
3046 .background_executor()
3047 .block(buffer.read(cx).serialize_ops(None, cx));
3048 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
3049 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
3050 .choose(&mut rng)
3051 .unwrap();
3052 log::info!(
3053 "Adding new replica {} (replicating from {})",
3054 new_replica_id,
3055 replica_id
3056 );
3057 new_buffer = Some(cx.new(|cx| {
3058 let mut new_buffer = Buffer::from_proto(
3059 new_replica_id,
3060 Capability::ReadWrite,
3061 old_buffer_state,
3062 None,
3063 )
3064 .unwrap();
3065 new_buffer.apply_ops(
3066 old_buffer_ops
3067 .into_iter()
3068 .map(|op| deserialize_operation(op).unwrap()),
3069 cx,
3070 );
3071 log::info!(
3072 "New replica {} text: {:?}",
3073 new_buffer.replica_id(),
3074 new_buffer.text()
3075 );
3076 new_buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200)));
3077 let network = network.clone();
3078 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3079 if let BufferEvent::Operation {
3080 operation,
3081 is_local: true,
3082 } = event
3083 {
3084 network.lock().broadcast(
3085 buffer.replica_id(),
3086 vec![proto::serialize_operation(operation)],
3087 );
3088 }
3089 })
3090 .detach();
3091 new_buffer
3092 }));
3093 network.lock().replicate(replica_id, new_replica_id);
3094
3095 if new_replica_id as usize == replica_ids.len() {
3096 replica_ids.push(new_replica_id);
3097 } else {
3098 let new_buffer = new_buffer.take().unwrap();
3099 while network.lock().has_unreceived(new_replica_id) {
3100 let ops = network
3101 .lock()
3102 .receive(new_replica_id)
3103 .into_iter()
3104 .map(|op| proto::deserialize_operation(op).unwrap());
3105 if ops.len() > 0 {
3106 log::info!(
3107 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3108 new_replica_id,
3109 buffer.read(cx).version(),
3110 ops.len(),
3111 ops
3112 );
3113 new_buffer.update(cx, |new_buffer, cx| {
3114 new_buffer.apply_ops(ops, cx);
3115 });
3116 }
3117 }
3118 buffers[new_replica_id as usize] = new_buffer;
3119 }
3120 }
3121 60..=69 if mutation_count != 0 => {
3122 buffer.update(cx, |buffer, cx| {
3123 buffer.randomly_undo_redo(&mut rng, cx);
3124 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
3125 });
3126 mutation_count -= 1;
3127 }
3128 _ if network.lock().has_unreceived(replica_id) => {
3129 let ops = network
3130 .lock()
3131 .receive(replica_id)
3132 .into_iter()
3133 .map(|op| proto::deserialize_operation(op).unwrap());
3134 if ops.len() > 0 {
3135 log::info!(
3136 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3137 replica_id,
3138 buffer.read(cx).version(),
3139 ops.len(),
3140 ops
3141 );
3142 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
3143 }
3144 }
3145 _ => {}
3146 }
3147
3148 now += Duration::from_millis(rng.gen_range(0..=200));
3149 buffers.extend(new_buffer);
3150
3151 for buffer in &buffers {
3152 buffer.read(cx).check_invariants();
3153 }
3154
3155 if mutation_count == 0 && network.lock().is_idle() {
3156 break;
3157 }
3158 }
3159
3160 let first_buffer = buffers[0].read(cx).snapshot();
3161 for buffer in &buffers[1..] {
3162 let buffer = buffer.read(cx).snapshot();
3163 assert_eq!(
3164 buffer.version(),
3165 first_buffer.version(),
3166 "Replica {} version != Replica 0 version",
3167 buffer.replica_id()
3168 );
3169 assert_eq!(
3170 buffer.text(),
3171 first_buffer.text(),
3172 "Replica {} text != Replica 0 text",
3173 buffer.replica_id()
3174 );
3175 assert_eq!(
3176 buffer
3177 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
3178 .collect::<Vec<_>>(),
3179 first_buffer
3180 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
3181 .collect::<Vec<_>>(),
3182 "Replica {} diagnostics != Replica 0 diagnostics",
3183 buffer.replica_id()
3184 );
3185 }
3186
3187 for buffer in &buffers {
3188 let buffer = buffer.read(cx).snapshot();
3189 let actual_remote_selections = buffer
3190 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
3191 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3192 .collect::<Vec<_>>();
3193 let expected_remote_selections = active_selections
3194 .iter()
3195 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3196 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3197 .collect::<Vec<_>>();
3198 assert_eq!(
3199 actual_remote_selections,
3200 expected_remote_selections,
3201 "Replica {} remote selections != expected selections",
3202 buffer.replica_id()
3203 );
3204 }
3205}
3206
3207#[test]
3208fn test_contiguous_ranges() {
3209 assert_eq!(
3210 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3211 &[1..4, 5..7, 9..13]
3212 );
3213
3214 // Respects the `max_len` parameter
3215 assert_eq!(
3216 contiguous_ranges(
3217 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3218 3
3219 )
3220 .collect::<Vec<_>>(),
3221 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3222 );
3223}
3224
3225#[gpui::test(iterations = 500)]
3226fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3227 // Generate a random multi-line string containing
3228 // some lines with trailing whitespace.
3229 let mut text = String::new();
3230 for _ in 0..rng.gen_range(0..16) {
3231 for _ in 0..rng.gen_range(0..36) {
3232 text.push(match rng.gen_range(0..10) {
3233 0..=1 => ' ',
3234 3 => '\t',
3235 _ => rng.gen_range('a'..='z'),
3236 });
3237 }
3238 text.push('\n');
3239 }
3240
3241 match rng.gen_range(0..10) {
3242 // sometimes remove the last newline
3243 0..=1 => drop(text.pop()), //
3244
3245 // sometimes add extra newlines
3246 2..=3 => text.push_str(&"\n".repeat(rng.gen_range(1..5))),
3247 _ => {}
3248 }
3249
3250 let rope = Rope::from(text.as_str());
3251 let actual_ranges = trailing_whitespace_ranges(&rope);
3252 let expected_ranges = TRAILING_WHITESPACE_REGEX
3253 .find_iter(&text)
3254 .map(|m| m.range())
3255 .collect::<Vec<_>>();
3256 assert_eq!(
3257 actual_ranges,
3258 expected_ranges,
3259 "wrong ranges for text lines:\n{:?}",
3260 text.split('\n').collect::<Vec<_>>()
3261 );
3262}
3263
3264#[gpui::test]
3265fn test_words_in_range(cx: &mut gpui::App) {
3266 init_settings(cx, |_| {});
3267
3268 // The first line are words excluded from the results with heuristics, we do not expect them in the test assertions.
3269 let contents = r#"
32700_isize 123 3.4 4
3271let word=öäpple.bar你 Öäpple word2-öÄpPlE-Pizza-word ÖÄPPLE word
3272 "#;
3273
3274 let buffer = cx.new(|cx| {
3275 let buffer = Buffer::local(contents, cx).with_language(Arc::new(rust_lang()), cx);
3276 assert_eq!(buffer.text(), contents);
3277 buffer.check_invariants();
3278 buffer
3279 });
3280
3281 buffer.update(cx, |buffer, _| {
3282 let snapshot = buffer.snapshot();
3283 assert_eq!(
3284 BTreeSet::from_iter(["Pizza".to_string()]),
3285 snapshot
3286 .words_in_range(WordsQuery {
3287 fuzzy_contents: Some("piz"),
3288 skip_digits: true,
3289 range: 0..snapshot.len(),
3290 })
3291 .into_keys()
3292 .collect::<BTreeSet<_>>()
3293 );
3294 assert_eq!(
3295 BTreeSet::from_iter([
3296 "öäpple".to_string(),
3297 "Öäpple".to_string(),
3298 "öÄpPlE".to_string(),
3299 "ÖÄPPLE".to_string(),
3300 ]),
3301 snapshot
3302 .words_in_range(WordsQuery {
3303 fuzzy_contents: Some("öp"),
3304 skip_digits: true,
3305 range: 0..snapshot.len(),
3306 })
3307 .into_keys()
3308 .collect::<BTreeSet<_>>()
3309 );
3310 assert_eq!(
3311 BTreeSet::from_iter([
3312 "öÄpPlE".to_string(),
3313 "Öäpple".to_string(),
3314 "ÖÄPPLE".to_string(),
3315 "öäpple".to_string(),
3316 ]),
3317 snapshot
3318 .words_in_range(WordsQuery {
3319 fuzzy_contents: Some("öÄ"),
3320 skip_digits: true,
3321 range: 0..snapshot.len(),
3322 })
3323 .into_keys()
3324 .collect::<BTreeSet<_>>()
3325 );
3326 assert_eq!(
3327 BTreeSet::default(),
3328 snapshot
3329 .words_in_range(WordsQuery {
3330 fuzzy_contents: Some("öÄ好"),
3331 skip_digits: true,
3332 range: 0..snapshot.len(),
3333 })
3334 .into_keys()
3335 .collect::<BTreeSet<_>>()
3336 );
3337 assert_eq!(
3338 BTreeSet::from_iter(["bar你".to_string(),]),
3339 snapshot
3340 .words_in_range(WordsQuery {
3341 fuzzy_contents: Some("你"),
3342 skip_digits: true,
3343 range: 0..snapshot.len(),
3344 })
3345 .into_keys()
3346 .collect::<BTreeSet<_>>()
3347 );
3348 assert_eq!(
3349 BTreeSet::default(),
3350 snapshot
3351 .words_in_range(WordsQuery {
3352 fuzzy_contents: Some(""),
3353 skip_digits: true,
3354 range: 0..snapshot.len(),
3355 },)
3356 .into_keys()
3357 .collect::<BTreeSet<_>>()
3358 );
3359 assert_eq!(
3360 BTreeSet::from_iter([
3361 "bar你".to_string(),
3362 "öÄpPlE".to_string(),
3363 "Öäpple".to_string(),
3364 "ÖÄPPLE".to_string(),
3365 "öäpple".to_string(),
3366 "let".to_string(),
3367 "Pizza".to_string(),
3368 "word".to_string(),
3369 "word2".to_string(),
3370 ]),
3371 snapshot
3372 .words_in_range(WordsQuery {
3373 fuzzy_contents: None,
3374 skip_digits: true,
3375 range: 0..snapshot.len(),
3376 })
3377 .into_keys()
3378 .collect::<BTreeSet<_>>()
3379 );
3380 assert_eq!(
3381 BTreeSet::from_iter([
3382 "0_isize".to_string(),
3383 "123".to_string(),
3384 "3".to_string(),
3385 "4".to_string(),
3386 "bar你".to_string(),
3387 "öÄpPlE".to_string(),
3388 "Öäpple".to_string(),
3389 "ÖÄPPLE".to_string(),
3390 "öäpple".to_string(),
3391 "let".to_string(),
3392 "Pizza".to_string(),
3393 "word".to_string(),
3394 "word2".to_string(),
3395 ]),
3396 snapshot
3397 .words_in_range(WordsQuery {
3398 fuzzy_contents: None,
3399 skip_digits: false,
3400 range: 0..snapshot.len(),
3401 })
3402 .into_keys()
3403 .collect::<BTreeSet<_>>()
3404 );
3405 });
3406}
3407
3408fn ruby_lang() -> Language {
3409 Language::new(
3410 LanguageConfig {
3411 name: "Ruby".into(),
3412 matcher: LanguageMatcher {
3413 path_suffixes: vec!["rb".to_string()],
3414 ..Default::default()
3415 },
3416 line_comments: vec!["# ".into()],
3417 ..Default::default()
3418 },
3419 Some(tree_sitter_ruby::LANGUAGE.into()),
3420 )
3421 .with_indents_query(
3422 r#"
3423 (class "end" @end) @indent
3424 (method "end" @end) @indent
3425 (rescue) @outdent
3426 (then) @indent
3427 "#,
3428 )
3429 .unwrap()
3430}
3431
3432fn html_lang() -> Language {
3433 Language::new(
3434 LanguageConfig {
3435 name: LanguageName::new("HTML"),
3436 block_comment: Some(("<!--".into(), "-->".into())),
3437 ..Default::default()
3438 },
3439 Some(tree_sitter_html::LANGUAGE.into()),
3440 )
3441 .with_indents_query(
3442 "
3443 (element
3444 (start_tag) @start
3445 (end_tag)? @end) @indent
3446 ",
3447 )
3448 .unwrap()
3449 .with_injection_query(
3450 r#"
3451 (script_element
3452 (raw_text) @injection.content
3453 (#set! injection.language "javascript"))
3454 "#,
3455 )
3456 .unwrap()
3457}
3458
3459fn erb_lang() -> Language {
3460 Language::new(
3461 LanguageConfig {
3462 name: "ERB".into(),
3463 matcher: LanguageMatcher {
3464 path_suffixes: vec!["erb".to_string()],
3465 ..Default::default()
3466 },
3467 block_comment: Some(("<%#".into(), "%>".into())),
3468 ..Default::default()
3469 },
3470 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3471 )
3472 .with_injection_query(
3473 r#"
3474 (
3475 (code) @injection.content
3476 (#set! injection.language "ruby")
3477 (#set! injection.combined)
3478 )
3479
3480 (
3481 (content) @injection.content
3482 (#set! injection.language "html")
3483 (#set! injection.combined)
3484 )
3485 "#,
3486 )
3487 .unwrap()
3488}
3489
3490fn rust_lang() -> Language {
3491 Language::new(
3492 LanguageConfig {
3493 name: "Rust".into(),
3494 matcher: LanguageMatcher {
3495 path_suffixes: vec!["rs".to_string()],
3496 ..Default::default()
3497 },
3498 ..Default::default()
3499 },
3500 Some(tree_sitter_rust::LANGUAGE.into()),
3501 )
3502 .with_indents_query(
3503 r#"
3504 (call_expression) @indent
3505 (field_expression) @indent
3506 (_ "(" ")" @end) @indent
3507 (_ "{" "}" @end) @indent
3508 "#,
3509 )
3510 .unwrap()
3511 .with_brackets_query(
3512 r#"
3513 ("{" @open "}" @close)
3514 "#,
3515 )
3516 .unwrap()
3517 .with_text_object_query(
3518 r#"
3519 (function_item
3520 body: (_
3521 "{"
3522 (_)* @function.inside
3523 "}" )) @function.around
3524
3525 (line_comment)+ @comment.around
3526
3527 (block_comment) @comment.around
3528 "#,
3529 )
3530 .unwrap()
3531 .with_outline_query(
3532 r#"
3533 (line_comment) @annotation
3534
3535 (struct_item
3536 "struct" @context
3537 name: (_) @name) @item
3538 (enum_item
3539 "enum" @context
3540 name: (_) @name) @item
3541 (enum_variant
3542 name: (_) @name) @item
3543 (field_declaration
3544 name: (_) @name) @item
3545 (impl_item
3546 "impl" @context
3547 trait: (_)? @name
3548 "for"? @context
3549 type: (_) @name
3550 body: (_ "{" (_)* "}")) @item
3551 (function_item
3552 "fn" @context
3553 name: (_) @name) @item
3554 (mod_item
3555 "mod" @context
3556 name: (_) @name) @item
3557 "#,
3558 )
3559 .unwrap()
3560}
3561
3562fn json_lang() -> Language {
3563 Language::new(
3564 LanguageConfig {
3565 name: "Json".into(),
3566 matcher: LanguageMatcher {
3567 path_suffixes: vec!["js".to_string()],
3568 ..Default::default()
3569 },
3570 ..Default::default()
3571 },
3572 Some(tree_sitter_json::LANGUAGE.into()),
3573 )
3574}
3575
3576fn javascript_lang() -> Language {
3577 Language::new(
3578 LanguageConfig {
3579 name: "JavaScript".into(),
3580 ..Default::default()
3581 },
3582 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3583 )
3584 .with_brackets_query(
3585 r#"
3586 ("{" @open "}" @close)
3587 ("(" @open ")" @close)
3588 "#,
3589 )
3590 .unwrap()
3591 .with_indents_query(
3592 r#"
3593 (object "}" @end) @indent
3594 "#,
3595 )
3596 .unwrap()
3597}
3598
3599pub fn markdown_lang() -> Language {
3600 Language::new(
3601 LanguageConfig {
3602 name: "Markdown".into(),
3603 matcher: LanguageMatcher {
3604 path_suffixes: vec!["md".into()],
3605 ..Default::default()
3606 },
3607 ..Default::default()
3608 },
3609 Some(tree_sitter_md::LANGUAGE.into()),
3610 )
3611 .with_injection_query(
3612 r#"
3613 (fenced_code_block
3614 (info_string
3615 (language) @injection.language)
3616 (code_fence_content) @injection.content)
3617
3618 ((inline) @injection.content
3619 (#set! injection.language "markdown-inline"))
3620 "#,
3621 )
3622 .unwrap()
3623}
3624
3625pub fn markdown_inline_lang() -> Language {
3626 Language::new(
3627 LanguageConfig {
3628 name: "Markdown-Inline".into(),
3629 hidden: true,
3630 ..LanguageConfig::default()
3631 },
3632 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3633 )
3634 .with_highlights_query("(emphasis) @emphasis")
3635 .unwrap()
3636}
3637
3638fn get_tree_sexp(buffer: &Entity<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3639 buffer.update(cx, |buffer, _| {
3640 let snapshot = buffer.snapshot();
3641 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3642 layers[0].node().to_sexp()
3643 })
3644}
3645
3646// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3647fn assert_bracket_pairs(
3648 selection_text: &'static str,
3649 bracket_pair_texts: Vec<&'static str>,
3650 language: Language,
3651 cx: &mut App,
3652) {
3653 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3654 let buffer =
3655 cx.new(|cx| Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx));
3656 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3657
3658 let selection_range = selection_ranges[0].clone();
3659
3660 let bracket_pairs = bracket_pair_texts
3661 .into_iter()
3662 .map(|pair_text| {
3663 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3664 assert_eq!(bracket_text, expected_text);
3665 (ranges[0].clone(), ranges[1].clone())
3666 })
3667 .collect::<Vec<_>>();
3668
3669 assert_set_eq!(
3670 buffer
3671 .bracket_ranges(selection_range)
3672 .map(|pair| (pair.open_range, pair.close_range))
3673 .collect::<Vec<_>>(),
3674 bracket_pairs
3675 );
3676}
3677
3678fn init_settings(cx: &mut App, f: fn(&mut AllLanguageSettingsContent)) {
3679 let settings_store = SettingsStore::test(cx);
3680 cx.set_global(settings_store);
3681 crate::init(cx);
3682 cx.update_global::<SettingsStore, _>(|settings, cx| {
3683 settings.update_user_settings::<AllLanguageSettings>(cx, f);
3684 });
3685}