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