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