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
1057#[gpui::test]
1058fn test_text_objects(cx: &mut App) {
1059 let (text, ranges) = marked_text_ranges(
1060 indoc! {r#"
1061 impl Hello {
1062 fn say() -> u8 { return /* ˇhi */ 1 }
1063 }"#
1064 },
1065 false,
1066 );
1067
1068 let buffer =
1069 cx.new(|cx| Buffer::local(text.clone(), cx).with_language(Arc::new(rust_lang()), cx));
1070 let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
1071
1072 let matches = snapshot
1073 .text_object_ranges(ranges[0].clone(), TreeSitterOptions::default())
1074 .map(|(range, text_object)| (&text[range], text_object))
1075 .collect::<Vec<_>>();
1076
1077 assert_eq!(
1078 matches,
1079 &[
1080 ("/* hi */", TextObject::AroundComment),
1081 ("return /* hi */ 1", TextObject::InsideFunction),
1082 (
1083 "fn say() -> u8 { return /* hi */ 1 }",
1084 TextObject::AroundFunction
1085 ),
1086 ],
1087 )
1088}
1089
1090#[gpui::test]
1091fn test_enclosing_bracket_ranges(cx: &mut App) {
1092 let mut assert = |selection_text, range_markers| {
1093 assert_bracket_pairs(selection_text, range_markers, rust_lang(), cx)
1094 };
1095
1096 assert(
1097 indoc! {"
1098 mod x {
1099 moˇd y {
1100
1101 }
1102 }
1103 let foo = 1;"},
1104 vec![indoc! {"
1105 mod x «{»
1106 mod y {
1107
1108 }
1109 «}»
1110 let foo = 1;"}],
1111 );
1112
1113 assert(
1114 indoc! {"
1115 mod x {
1116 mod y ˇ{
1117
1118 }
1119 }
1120 let foo = 1;"},
1121 vec![
1122 indoc! {"
1123 mod x «{»
1124 mod y {
1125
1126 }
1127 «}»
1128 let foo = 1;"},
1129 indoc! {"
1130 mod x {
1131 mod y «{»
1132
1133 «}»
1134 }
1135 let foo = 1;"},
1136 ],
1137 );
1138
1139 assert(
1140 indoc! {"
1141 mod x {
1142 mod y {
1143
1144 }ˇ
1145 }
1146 let foo = 1;"},
1147 vec![
1148 indoc! {"
1149 mod x «{»
1150 mod y {
1151
1152 }
1153 «}»
1154 let foo = 1;"},
1155 indoc! {"
1156 mod x {
1157 mod y «{»
1158
1159 «}»
1160 }
1161 let foo = 1;"},
1162 ],
1163 );
1164
1165 assert(
1166 indoc! {"
1167 mod x {
1168 mod y {
1169
1170 }
1171 ˇ}
1172 let foo = 1;"},
1173 vec![indoc! {"
1174 mod x «{»
1175 mod y {
1176
1177 }
1178 «}»
1179 let foo = 1;"}],
1180 );
1181
1182 assert(
1183 indoc! {"
1184 mod x {
1185 mod y {
1186
1187 }
1188 }
1189 let fˇoo = 1;"},
1190 vec![],
1191 );
1192
1193 // Regression test: avoid crash when querying at the end of the buffer.
1194 assert(
1195 indoc! {"
1196 mod x {
1197 mod y {
1198
1199 }
1200 }
1201 let foo = 1;ˇ"},
1202 vec![],
1203 );
1204}
1205
1206#[gpui::test]
1207fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut App) {
1208 let mut assert = |selection_text, bracket_pair_texts| {
1209 assert_bracket_pairs(selection_text, bracket_pair_texts, javascript_lang(), cx)
1210 };
1211
1212 assert(
1213 indoc! {"
1214 for (const a in b)ˇ {
1215 // a comment that's longer than the for-loop header
1216 }"},
1217 vec![indoc! {"
1218 for «(»const a in b«)» {
1219 // a comment that's longer than the for-loop header
1220 }"}],
1221 );
1222
1223 // Regression test: even though the parent node of the parentheses (the for loop) does
1224 // intersect the given range, the parentheses themselves do not contain the range, so
1225 // they should not be returned. Only the curly braces contain the range.
1226 assert(
1227 indoc! {"
1228 for (const a in b) {ˇ
1229 // a comment that's longer than the for-loop header
1230 }"},
1231 vec![indoc! {"
1232 for (const a in b) «{»
1233 // a comment that's longer than the for-loop header
1234 «}»"}],
1235 );
1236}
1237
1238#[gpui::test]
1239fn test_range_for_syntax_ancestor(cx: &mut App) {
1240 cx.new(|cx| {
1241 let text = "fn a() { b(|c| {}) }";
1242 let buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1243 let snapshot = buffer.snapshot();
1244
1245 assert_eq!(
1246 snapshot
1247 .syntax_ancestor(empty_range_at(text, "|"))
1248 .unwrap()
1249 .byte_range(),
1250 range_of(text, "|")
1251 );
1252 assert_eq!(
1253 snapshot
1254 .syntax_ancestor(range_of(text, "|"))
1255 .unwrap()
1256 .byte_range(),
1257 range_of(text, "|c|")
1258 );
1259 assert_eq!(
1260 snapshot
1261 .syntax_ancestor(range_of(text, "|c|"))
1262 .unwrap()
1263 .byte_range(),
1264 range_of(text, "|c| {}")
1265 );
1266 assert_eq!(
1267 snapshot
1268 .syntax_ancestor(range_of(text, "|c| {}"))
1269 .unwrap()
1270 .byte_range(),
1271 range_of(text, "(|c| {})")
1272 );
1273
1274 buffer
1275 });
1276
1277 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
1278 let start = text.find(part).unwrap();
1279 start..start
1280 }
1281
1282 fn range_of(text: &str, part: &str) -> Range<usize> {
1283 let start = text.find(part).unwrap();
1284 start..start + part.len()
1285 }
1286}
1287
1288#[gpui::test]
1289fn test_autoindent_with_soft_tabs(cx: &mut App) {
1290 init_settings(cx, |_| {});
1291
1292 cx.new(|cx| {
1293 let text = "fn a() {}";
1294 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1295
1296 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1297 assert_eq!(buffer.text(), "fn a() {\n \n}");
1298
1299 buffer.edit(
1300 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
1301 Some(AutoindentMode::EachLine),
1302 cx,
1303 );
1304 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
1305
1306 // Create a field expression on a new line, causing that line
1307 // to be indented.
1308 buffer.edit(
1309 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
1310 Some(AutoindentMode::EachLine),
1311 cx,
1312 );
1313 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
1314
1315 // Remove the dot so that the line is no longer a field expression,
1316 // causing the line to be outdented.
1317 buffer.edit(
1318 [(Point::new(2, 8)..Point::new(2, 9), "")],
1319 Some(AutoindentMode::EachLine),
1320 cx,
1321 );
1322 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
1323
1324 buffer
1325 });
1326}
1327
1328#[gpui::test]
1329fn test_autoindent_with_hard_tabs(cx: &mut App) {
1330 init_settings(cx, |settings| {
1331 settings.defaults.hard_tabs = Some(true);
1332 });
1333
1334 cx.new(|cx| {
1335 let text = "fn a() {}";
1336 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1337
1338 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1339 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
1340
1341 buffer.edit(
1342 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
1343 Some(AutoindentMode::EachLine),
1344 cx,
1345 );
1346 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
1347
1348 // Create a field expression on a new line, causing that line
1349 // to be indented.
1350 buffer.edit(
1351 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
1352 Some(AutoindentMode::EachLine),
1353 cx,
1354 );
1355 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
1356
1357 // Remove the dot so that the line is no longer a field expression,
1358 // causing the line to be outdented.
1359 buffer.edit(
1360 [(Point::new(2, 2)..Point::new(2, 3), "")],
1361 Some(AutoindentMode::EachLine),
1362 cx,
1363 );
1364 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
1365
1366 buffer
1367 });
1368}
1369
1370#[gpui::test]
1371fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut App) {
1372 init_settings(cx, |_| {});
1373
1374 cx.new(|cx| {
1375 let mut buffer = Buffer::local(
1376 "
1377 fn a() {
1378 c;
1379 d;
1380 }
1381 "
1382 .unindent(),
1383 cx,
1384 )
1385 .with_language(Arc::new(rust_lang()), cx);
1386
1387 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
1388 // their indentation is not adjusted.
1389 buffer.edit_via_marked_text(
1390 &"
1391 fn a() {
1392 c«()»;
1393 d«()»;
1394 }
1395 "
1396 .unindent(),
1397 Some(AutoindentMode::EachLine),
1398 cx,
1399 );
1400 assert_eq!(
1401 buffer.text(),
1402 "
1403 fn a() {
1404 c();
1405 d();
1406 }
1407 "
1408 .unindent()
1409 );
1410
1411 // When appending new content after these lines, the indentation is based on the
1412 // preceding lines' actual indentation.
1413 buffer.edit_via_marked_text(
1414 &"
1415 fn a() {
1416 c«
1417 .f
1418 .g()»;
1419 d«
1420 .f
1421 .g()»;
1422 }
1423 "
1424 .unindent(),
1425 Some(AutoindentMode::EachLine),
1426 cx,
1427 );
1428 assert_eq!(
1429 buffer.text(),
1430 "
1431 fn a() {
1432 c
1433 .f
1434 .g();
1435 d
1436 .f
1437 .g();
1438 }
1439 "
1440 .unindent()
1441 );
1442
1443 // Insert a newline after the open brace. It is auto-indented
1444 buffer.edit_via_marked_text(
1445 &"
1446 fn a() {«
1447 »
1448 c
1449 .f
1450 .g();
1451 d
1452 .f
1453 .g();
1454 }
1455 "
1456 .unindent(),
1457 Some(AutoindentMode::EachLine),
1458 cx,
1459 );
1460 assert_eq!(
1461 buffer.text(),
1462 "
1463 fn a() {
1464 ˇ
1465 c
1466 .f
1467 .g();
1468 d
1469 .f
1470 .g();
1471 }
1472 "
1473 .unindent()
1474 .replace("ˇ", "")
1475 );
1476
1477 // Manually outdent the line. It stays outdented.
1478 buffer.edit_via_marked_text(
1479 &"
1480 fn a() {
1481 «»
1482 c
1483 .f
1484 .g();
1485 d
1486 .f
1487 .g();
1488 }
1489 "
1490 .unindent(),
1491 Some(AutoindentMode::EachLine),
1492 cx,
1493 );
1494 assert_eq!(
1495 buffer.text(),
1496 "
1497 fn a() {
1498
1499 c
1500 .f
1501 .g();
1502 d
1503 .f
1504 .g();
1505 }
1506 "
1507 .unindent()
1508 );
1509
1510 buffer
1511 });
1512
1513 cx.new(|cx| {
1514 eprintln!("second buffer: {:?}", cx.entity_id());
1515
1516 let mut buffer = Buffer::local(
1517 "
1518 fn a() {
1519 b();
1520 |
1521 "
1522 .replace('|', "") // marker to preserve trailing whitespace
1523 .unindent(),
1524 cx,
1525 )
1526 .with_language(Arc::new(rust_lang()), cx);
1527
1528 // Insert a closing brace. It is outdented.
1529 buffer.edit_via_marked_text(
1530 &"
1531 fn a() {
1532 b();
1533 «}»
1534 "
1535 .unindent(),
1536 Some(AutoindentMode::EachLine),
1537 cx,
1538 );
1539 assert_eq!(
1540 buffer.text(),
1541 "
1542 fn a() {
1543 b();
1544 }
1545 "
1546 .unindent()
1547 );
1548
1549 // Manually edit the leading whitespace. The edit is preserved.
1550 buffer.edit_via_marked_text(
1551 &"
1552 fn a() {
1553 b();
1554 « »}
1555 "
1556 .unindent(),
1557 Some(AutoindentMode::EachLine),
1558 cx,
1559 );
1560 assert_eq!(
1561 buffer.text(),
1562 "
1563 fn a() {
1564 b();
1565 }
1566 "
1567 .unindent()
1568 );
1569 buffer
1570 });
1571
1572 eprintln!("DONE");
1573}
1574
1575#[gpui::test]
1576fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut App) {
1577 init_settings(cx, |_| {});
1578
1579 cx.new(|cx| {
1580 let mut buffer = Buffer::local(
1581 "
1582 fn a() {
1583 i
1584 }
1585 "
1586 .unindent(),
1587 cx,
1588 )
1589 .with_language(Arc::new(rust_lang()), cx);
1590
1591 // Regression test: line does not get outdented due to syntax error
1592 buffer.edit_via_marked_text(
1593 &"
1594 fn a() {
1595 i«f let Some(x) = y»
1596 }
1597 "
1598 .unindent(),
1599 Some(AutoindentMode::EachLine),
1600 cx,
1601 );
1602 assert_eq!(
1603 buffer.text(),
1604 "
1605 fn a() {
1606 if let Some(x) = y
1607 }
1608 "
1609 .unindent()
1610 );
1611
1612 buffer.edit_via_marked_text(
1613 &"
1614 fn a() {
1615 if let Some(x) = y« {»
1616 }
1617 "
1618 .unindent(),
1619 Some(AutoindentMode::EachLine),
1620 cx,
1621 );
1622 assert_eq!(
1623 buffer.text(),
1624 "
1625 fn a() {
1626 if let Some(x) = y {
1627 }
1628 "
1629 .unindent()
1630 );
1631
1632 buffer
1633 });
1634}
1635
1636#[gpui::test]
1637fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut App) {
1638 init_settings(cx, |_| {});
1639
1640 cx.new(|cx| {
1641 let mut buffer = Buffer::local(
1642 "
1643 fn a() {}
1644 "
1645 .unindent(),
1646 cx,
1647 )
1648 .with_language(Arc::new(rust_lang()), cx);
1649
1650 buffer.edit_via_marked_text(
1651 &"
1652 fn a(«
1653 b») {}
1654 "
1655 .unindent(),
1656 Some(AutoindentMode::EachLine),
1657 cx,
1658 );
1659 assert_eq!(
1660 buffer.text(),
1661 "
1662 fn a(
1663 b) {}
1664 "
1665 .unindent()
1666 );
1667
1668 // The indentation suggestion changed because `@end` node (a close paren)
1669 // is now at the beginning of the line.
1670 buffer.edit_via_marked_text(
1671 &"
1672 fn a(
1673 ˇ) {}
1674 "
1675 .unindent(),
1676 Some(AutoindentMode::EachLine),
1677 cx,
1678 );
1679 assert_eq!(
1680 buffer.text(),
1681 "
1682 fn a(
1683 ) {}
1684 "
1685 .unindent()
1686 );
1687
1688 buffer
1689 });
1690}
1691
1692#[gpui::test]
1693fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut App) {
1694 init_settings(cx, |_| {});
1695
1696 cx.new(|cx| {
1697 let text = "a\nb";
1698 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1699 buffer.edit(
1700 [(0..1, "\n"), (2..3, "\n")],
1701 Some(AutoindentMode::EachLine),
1702 cx,
1703 );
1704 assert_eq!(buffer.text(), "\n\n\n");
1705 buffer
1706 });
1707}
1708
1709#[gpui::test]
1710fn test_autoindent_multi_line_insertion(cx: &mut App) {
1711 init_settings(cx, |_| {});
1712
1713 cx.new(|cx| {
1714 let text = "
1715 const a: usize = 1;
1716 fn b() {
1717 if c {
1718 let d = 2;
1719 }
1720 }
1721 "
1722 .unindent();
1723
1724 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1725 buffer.edit(
1726 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
1727 Some(AutoindentMode::EachLine),
1728 cx,
1729 );
1730 assert_eq!(
1731 buffer.text(),
1732 "
1733 const a: usize = 1;
1734 fn b() {
1735 if c {
1736 e(
1737 f()
1738 );
1739 let d = 2;
1740 }
1741 }
1742 "
1743 .unindent()
1744 );
1745
1746 buffer
1747 });
1748}
1749
1750#[gpui::test]
1751fn test_autoindent_block_mode(cx: &mut App) {
1752 init_settings(cx, |_| {});
1753
1754 cx.new(|cx| {
1755 let text = r#"
1756 fn a() {
1757 b();
1758 }
1759 "#
1760 .unindent();
1761 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1762
1763 // When this text was copied, both of the quotation marks were at the same
1764 // indent level, but the indentation of the first line was not included in
1765 // the copied text. This information is retained in the
1766 // 'original_indent_columns' vector.
1767 let original_indent_columns = vec![Some(4)];
1768 let inserted_text = r#"
1769 "
1770 c
1771 d
1772 e
1773 "
1774 "#
1775 .unindent();
1776
1777 // Insert the block at column zero. The entire block is indented
1778 // so that the first line matches the previous line's indentation.
1779 buffer.edit(
1780 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
1781 Some(AutoindentMode::Block {
1782 original_indent_columns: original_indent_columns.clone(),
1783 }),
1784 cx,
1785 );
1786 assert_eq!(
1787 buffer.text(),
1788 r#"
1789 fn a() {
1790 b();
1791 "
1792 c
1793 d
1794 e
1795 "
1796 }
1797 "#
1798 .unindent()
1799 );
1800
1801 // Grouping is disabled in tests, so we need 2 undos
1802 buffer.undo(cx); // Undo the auto-indent
1803 buffer.undo(cx); // Undo the original edit
1804
1805 // Insert the block at a deeper indent level. The entire block is outdented.
1806 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
1807 buffer.edit(
1808 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
1809 Some(AutoindentMode::Block {
1810 original_indent_columns,
1811 }),
1812 cx,
1813 );
1814 assert_eq!(
1815 buffer.text(),
1816 r#"
1817 fn a() {
1818 b();
1819 "
1820 c
1821 d
1822 e
1823 "
1824 }
1825 "#
1826 .unindent()
1827 );
1828
1829 buffer
1830 });
1831}
1832
1833#[gpui::test]
1834fn test_autoindent_block_mode_with_newline(cx: &mut App) {
1835 init_settings(cx, |_| {});
1836
1837 cx.new(|cx| {
1838 let text = r#"
1839 fn a() {
1840 b();
1841 }
1842 "#
1843 .unindent();
1844 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1845
1846 // First line contains just '\n', it's indentation is stored in "original_indent_columns"
1847 let original_indent_columns = vec![Some(4)];
1848 let inserted_text = r#"
1849
1850 c();
1851 d();
1852 e();
1853 "#
1854 .unindent();
1855 buffer.edit(
1856 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1857 Some(AutoindentMode::Block {
1858 original_indent_columns,
1859 }),
1860 cx,
1861 );
1862
1863 // While making edit, we ignore first line as it only contains '\n'
1864 // hence second line indent is used to calculate delta
1865 assert_eq!(
1866 buffer.text(),
1867 r#"
1868 fn a() {
1869 b();
1870
1871 c();
1872 d();
1873 e();
1874 }
1875 "#
1876 .unindent()
1877 );
1878
1879 buffer
1880 });
1881}
1882
1883#[gpui::test]
1884fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut App) {
1885 init_settings(cx, |_| {});
1886
1887 cx.new(|cx| {
1888 let text = r#"
1889 fn a() {
1890 if b() {
1891
1892 }
1893 }
1894 "#
1895 .unindent();
1896 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1897
1898 // The original indent columns are not known, so this text is
1899 // auto-indented in a block as if the first line was copied in
1900 // its entirety.
1901 let original_indent_columns = Vec::new();
1902 let inserted_text = " c\n .d()\n .e();";
1903
1904 // Insert the block at column zero. The entire block is indented
1905 // so that the first line matches the previous line's indentation.
1906 buffer.edit(
1907 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
1908 Some(AutoindentMode::Block {
1909 original_indent_columns,
1910 }),
1911 cx,
1912 );
1913 assert_eq!(
1914 buffer.text(),
1915 r#"
1916 fn a() {
1917 if b() {
1918 c
1919 .d()
1920 .e();
1921 }
1922 }
1923 "#
1924 .unindent()
1925 );
1926
1927 // Grouping is disabled in tests, so we need 2 undos
1928 buffer.undo(cx); // Undo the auto-indent
1929 buffer.undo(cx); // Undo the original edit
1930
1931 // Insert the block at a deeper indent level. The entire block is outdented.
1932 buffer.edit(
1933 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
1934 None,
1935 cx,
1936 );
1937 buffer.edit(
1938 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
1939 Some(AutoindentMode::Block {
1940 original_indent_columns: Vec::new(),
1941 }),
1942 cx,
1943 );
1944 assert_eq!(
1945 buffer.text(),
1946 r#"
1947 fn a() {
1948 if b() {
1949 c
1950 .d()
1951 .e();
1952 }
1953 }
1954 "#
1955 .unindent()
1956 );
1957
1958 buffer
1959 });
1960}
1961
1962#[gpui::test]
1963fn test_autoindent_block_mode_multiple_adjacent_ranges(cx: &mut App) {
1964 init_settings(cx, |_| {});
1965
1966 cx.new(|cx| {
1967 let (text, ranges_to_replace) = marked_text_ranges(
1968 &"
1969 mod numbers {
1970 «fn one() {
1971 1
1972 }
1973 »
1974 «fn two() {
1975 2
1976 }
1977 »
1978 «fn three() {
1979 3
1980 }
1981 »}
1982 "
1983 .unindent(),
1984 false,
1985 );
1986
1987 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
1988
1989 buffer.edit(
1990 [
1991 (ranges_to_replace[0].clone(), "fn one() {\n 101\n}\n"),
1992 (ranges_to_replace[1].clone(), "fn two() {\n 102\n}\n"),
1993 (ranges_to_replace[2].clone(), "fn three() {\n 103\n}\n"),
1994 ],
1995 Some(AutoindentMode::Block {
1996 original_indent_columns: vec![Some(0), Some(0), Some(0)],
1997 }),
1998 cx,
1999 );
2000
2001 pretty_assertions::assert_eq!(
2002 buffer.text(),
2003 "
2004 mod numbers {
2005 fn one() {
2006 101
2007 }
2008
2009 fn two() {
2010 102
2011 }
2012
2013 fn three() {
2014 103
2015 }
2016 }
2017 "
2018 .unindent()
2019 );
2020
2021 buffer
2022 });
2023}
2024
2025#[gpui::test]
2026fn test_autoindent_language_without_indents_query(cx: &mut App) {
2027 init_settings(cx, |_| {});
2028
2029 cx.new(|cx| {
2030 let text = "
2031 * one
2032 - a
2033 - b
2034 * two
2035 "
2036 .unindent();
2037
2038 let mut buffer = Buffer::local(text, cx).with_language(
2039 Arc::new(Language::new(
2040 LanguageConfig {
2041 name: "Markdown".into(),
2042 auto_indent_using_last_non_empty_line: false,
2043 ..Default::default()
2044 },
2045 Some(tree_sitter_json::LANGUAGE.into()),
2046 )),
2047 cx,
2048 );
2049 buffer.edit(
2050 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
2051 Some(AutoindentMode::EachLine),
2052 cx,
2053 );
2054 assert_eq!(
2055 buffer.text(),
2056 "
2057 * one
2058 - a
2059 - b
2060
2061 * two
2062 "
2063 .unindent()
2064 );
2065 buffer
2066 });
2067}
2068
2069#[gpui::test]
2070fn test_autoindent_with_injected_languages(cx: &mut App) {
2071 init_settings(cx, |settings| {
2072 settings.languages.0.extend([
2073 (
2074 "HTML".into(),
2075 LanguageSettingsContent {
2076 tab_size: Some(2.try_into().unwrap()),
2077 ..Default::default()
2078 },
2079 ),
2080 (
2081 "JavaScript".into(),
2082 LanguageSettingsContent {
2083 tab_size: Some(8.try_into().unwrap()),
2084 ..Default::default()
2085 },
2086 ),
2087 ])
2088 });
2089
2090 let html_language = Arc::new(html_lang());
2091
2092 let javascript_language = Arc::new(javascript_lang());
2093
2094 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2095 language_registry.add(html_language.clone());
2096 language_registry.add(javascript_language);
2097
2098 cx.new(|cx| {
2099 let (text, ranges) = marked_text_ranges(
2100 &"
2101 <div>ˇ
2102 </div>
2103 <script>
2104 init({ˇ
2105 })
2106 </script>
2107 <span>ˇ
2108 </span>
2109 "
2110 .unindent(),
2111 false,
2112 );
2113
2114 let mut buffer = Buffer::local(text, cx);
2115 buffer.set_language_registry(language_registry);
2116 buffer.set_language(Some(html_language), cx);
2117 buffer.edit(
2118 ranges.into_iter().map(|range| (range, "\na")),
2119 Some(AutoindentMode::EachLine),
2120 cx,
2121 );
2122 assert_eq!(
2123 buffer.text(),
2124 "
2125 <div>
2126 a
2127 </div>
2128 <script>
2129 init({
2130 a
2131 })
2132 </script>
2133 <span>
2134 a
2135 </span>
2136 "
2137 .unindent()
2138 );
2139 buffer
2140 });
2141}
2142
2143#[gpui::test]
2144fn test_autoindent_query_with_outdent_captures(cx: &mut App) {
2145 init_settings(cx, |settings| {
2146 settings.defaults.tab_size = Some(2.try_into().unwrap());
2147 });
2148
2149 cx.new(|cx| {
2150 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
2151
2152 let text = r#"
2153 class C
2154 def a(b, c)
2155 puts b
2156 puts c
2157 rescue
2158 puts "errored"
2159 exit 1
2160 end
2161 end
2162 "#
2163 .unindent();
2164
2165 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
2166
2167 assert_eq!(
2168 buffer.text(),
2169 r#"
2170 class C
2171 def a(b, c)
2172 puts b
2173 puts c
2174 rescue
2175 puts "errored"
2176 exit 1
2177 end
2178 end
2179 "#
2180 .unindent()
2181 );
2182
2183 buffer
2184 });
2185}
2186
2187#[gpui::test]
2188async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
2189 cx.update(|cx| init_settings(cx, |_| {}));
2190
2191 // First we insert some newlines to request an auto-indent (asynchronously).
2192 // Then we request that a preview tab be preserved for the new version, even though it's edited.
2193 let buffer = cx.new(|cx| {
2194 let text = "fn a() {}";
2195 let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
2196
2197 // This causes autoindent to be async.
2198 buffer.set_sync_parse_timeout(Duration::ZERO);
2199
2200 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
2201 buffer.refresh_preview();
2202
2203 // Synchronously, we haven't auto-indented and we're still preserving the preview.
2204 assert_eq!(buffer.text(), "fn a() {\n\n}");
2205 assert!(buffer.preserve_preview());
2206 buffer
2207 });
2208
2209 // Now let the autoindent finish
2210 cx.executor().run_until_parked();
2211
2212 // The auto-indent applied, but didn't dismiss our preview
2213 buffer.update(cx, |buffer, cx| {
2214 assert_eq!(buffer.text(), "fn a() {\n \n}");
2215 assert!(buffer.preserve_preview());
2216
2217 // Edit inserting another line. It will autoindent async.
2218 // Then refresh the preview version.
2219 buffer.edit(
2220 [(Point::new(1, 4)..Point::new(1, 4), "\n")],
2221 Some(AutoindentMode::EachLine),
2222 cx,
2223 );
2224 buffer.refresh_preview();
2225 assert_eq!(buffer.text(), "fn a() {\n \n\n}");
2226 assert!(buffer.preserve_preview());
2227
2228 // Then perform another edit, this time without refreshing the preview version.
2229 buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
2230 // This causes the preview to not be preserved.
2231 assert!(!buffer.preserve_preview());
2232 });
2233
2234 // Let the async autoindent from the first edit finish.
2235 cx.executor().run_until_parked();
2236
2237 // The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
2238 buffer.update(cx, |buffer, _| {
2239 assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
2240 assert!(!buffer.preserve_preview());
2241 });
2242}
2243
2244#[gpui::test]
2245fn test_insert_empty_line(cx: &mut App) {
2246 init_settings(cx, |_| {});
2247
2248 // Insert empty line at the beginning, requesting an empty line above
2249 cx.new(|cx| {
2250 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2251 let point = buffer.insert_empty_line(Point::new(0, 0), true, false, cx);
2252 assert_eq!(buffer.text(), "\nabc\ndef\nghi");
2253 assert_eq!(point, Point::new(0, 0));
2254 buffer
2255 });
2256
2257 // Insert empty line at the beginning, requesting an empty line above and below
2258 cx.new(|cx| {
2259 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2260 let point = buffer.insert_empty_line(Point::new(0, 0), true, true, cx);
2261 assert_eq!(buffer.text(), "\n\nabc\ndef\nghi");
2262 assert_eq!(point, Point::new(0, 0));
2263 buffer
2264 });
2265
2266 // Insert empty line at the start of a line, requesting empty lines above and below
2267 cx.new(|cx| {
2268 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2269 let point = buffer.insert_empty_line(Point::new(2, 0), true, true, cx);
2270 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi");
2271 assert_eq!(point, Point::new(3, 0));
2272 buffer
2273 });
2274
2275 // Insert empty line in the middle of a line, requesting empty lines above and below
2276 cx.new(|cx| {
2277 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2278 let point = buffer.insert_empty_line(Point::new(1, 3), true, true, cx);
2279 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi\njkl");
2280 assert_eq!(point, Point::new(3, 0));
2281 buffer
2282 });
2283
2284 // Insert empty line in the middle of a line, requesting empty line above only
2285 cx.new(|cx| {
2286 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2287 let point = buffer.insert_empty_line(Point::new(1, 3), true, false, cx);
2288 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2289 assert_eq!(point, Point::new(3, 0));
2290 buffer
2291 });
2292
2293 // Insert empty line in the middle of a line, requesting empty line below only
2294 cx.new(|cx| {
2295 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2296 let point = buffer.insert_empty_line(Point::new(1, 3), false, true, cx);
2297 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2298 assert_eq!(point, Point::new(2, 0));
2299 buffer
2300 });
2301
2302 // Insert empty line at the end, requesting empty lines above and below
2303 cx.new(|cx| {
2304 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2305 let point = buffer.insert_empty_line(Point::new(2, 3), true, true, cx);
2306 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n\n");
2307 assert_eq!(point, Point::new(4, 0));
2308 buffer
2309 });
2310
2311 // Insert empty line at the end, requesting empty line above only
2312 cx.new(|cx| {
2313 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2314 let point = buffer.insert_empty_line(Point::new(2, 3), true, false, cx);
2315 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2316 assert_eq!(point, Point::new(4, 0));
2317 buffer
2318 });
2319
2320 // Insert empty line at the end, requesting empty line below only
2321 cx.new(|cx| {
2322 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2323 let point = buffer.insert_empty_line(Point::new(2, 3), false, true, cx);
2324 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2325 assert_eq!(point, Point::new(3, 0));
2326 buffer
2327 });
2328}
2329
2330#[gpui::test]
2331fn test_language_scope_at_with_javascript(cx: &mut App) {
2332 init_settings(cx, |_| {});
2333
2334 cx.new(|cx| {
2335 let language = Language::new(
2336 LanguageConfig {
2337 name: "JavaScript".into(),
2338 line_comments: vec!["// ".into()],
2339 block_comment: Some(BlockCommentConfig {
2340 start: "/*".into(),
2341 end: "*/".into(),
2342 prefix: "* ".into(),
2343 tab_size: 1,
2344 }),
2345 brackets: BracketPairConfig {
2346 pairs: vec![
2347 BracketPair {
2348 start: "{".into(),
2349 end: "}".into(),
2350 close: true,
2351 surround: true,
2352 newline: false,
2353 },
2354 BracketPair {
2355 start: "'".into(),
2356 end: "'".into(),
2357 close: true,
2358 surround: true,
2359 newline: false,
2360 },
2361 ],
2362 disabled_scopes_by_bracket_ix: vec![
2363 Vec::new(), //
2364 vec!["string".into(), "comment".into()], // single quotes disabled
2365 ],
2366 },
2367 overrides: [(
2368 "element".into(),
2369 LanguageConfigOverride {
2370 line_comments: Override::Remove { remove: true },
2371 block_comment: Override::Set(BlockCommentConfig {
2372 start: "{/*".into(),
2373 prefix: "".into(),
2374 end: "*/}".into(),
2375 tab_size: 0,
2376 }),
2377 ..Default::default()
2378 },
2379 )]
2380 .into_iter()
2381 .collect(),
2382 ..Default::default()
2383 },
2384 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2385 )
2386 .with_override_query(
2387 r#"
2388 (jsx_element) @element
2389 (string) @string
2390 (comment) @comment.inclusive
2391 [
2392 (jsx_opening_element)
2393 (jsx_closing_element)
2394 (jsx_expression)
2395 ] @default
2396 "#,
2397 )
2398 .unwrap();
2399
2400 let text = r#"
2401 a["b"] = <C d="e">
2402 <F></F>
2403 { g() }
2404 </C>; // a comment
2405 "#
2406 .unindent();
2407
2408 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2409 let snapshot = buffer.snapshot();
2410
2411 let config = snapshot.language_scope_at(0).unwrap();
2412 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2413 assert_eq!(
2414 config.block_comment(),
2415 Some(&BlockCommentConfig {
2416 start: "/*".into(),
2417 prefix: "* ".into(),
2418 end: "*/".into(),
2419 tab_size: 1,
2420 })
2421 );
2422
2423 // Both bracket pairs are enabled
2424 assert_eq!(
2425 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2426 &[true, true]
2427 );
2428
2429 let comment_config = snapshot
2430 .language_scope_at(text.find("comment").unwrap() + "comment".len())
2431 .unwrap();
2432 assert_eq!(
2433 comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2434 &[true, false]
2435 );
2436
2437 let string_config = snapshot
2438 .language_scope_at(text.find("b\"").unwrap())
2439 .unwrap();
2440 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2441 assert_eq!(
2442 string_config.block_comment(),
2443 Some(&BlockCommentConfig {
2444 start: "/*".into(),
2445 prefix: "* ".into(),
2446 end: "*/".into(),
2447 tab_size: 1,
2448 })
2449 );
2450 // Second bracket pair is disabled
2451 assert_eq!(
2452 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2453 &[true, false]
2454 );
2455
2456 // In between JSX tags: use the `element` override.
2457 let element_config = snapshot
2458 .language_scope_at(text.find("<F>").unwrap())
2459 .unwrap();
2460 // TODO nested blocks after newlines are captured with all whitespaces
2461 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2462 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2463 // assert_eq!(
2464 // element_config.block_comment_delimiters(),
2465 // Some((&"{/*".into(), &"*/}".into()))
2466 // );
2467 assert_eq!(
2468 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2469 &[true, true]
2470 );
2471
2472 // Within a JSX tag: use the default config.
2473 let tag_config = snapshot
2474 .language_scope_at(text.find(" d=").unwrap() + 1)
2475 .unwrap();
2476 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2477 assert_eq!(
2478 tag_config.block_comment(),
2479 Some(&BlockCommentConfig {
2480 start: "/*".into(),
2481 prefix: "* ".into(),
2482 end: "*/".into(),
2483 tab_size: 1,
2484 })
2485 );
2486 assert_eq!(
2487 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2488 &[true, true]
2489 );
2490
2491 // In a JSX expression: use the default config.
2492 let expression_in_element_config = snapshot
2493 .language_scope_at(text.find('{').unwrap() + 1)
2494 .unwrap();
2495 assert_eq!(
2496 expression_in_element_config.line_comment_prefixes(),
2497 &[Arc::from("// ")]
2498 );
2499 assert_eq!(
2500 expression_in_element_config.block_comment(),
2501 Some(&BlockCommentConfig {
2502 start: "/*".into(),
2503 prefix: "* ".into(),
2504 end: "*/".into(),
2505 tab_size: 1,
2506 })
2507 );
2508 assert_eq!(
2509 expression_in_element_config
2510 .brackets()
2511 .map(|e| e.1)
2512 .collect::<Vec<_>>(),
2513 &[true, true]
2514 );
2515
2516 buffer
2517 });
2518}
2519
2520#[gpui::test]
2521fn test_language_scope_at_with_rust(cx: &mut App) {
2522 init_settings(cx, |_| {});
2523
2524 cx.new(|cx| {
2525 let language = Language::new(
2526 LanguageConfig {
2527 name: "Rust".into(),
2528 brackets: BracketPairConfig {
2529 pairs: vec![
2530 BracketPair {
2531 start: "{".into(),
2532 end: "}".into(),
2533 close: true,
2534 surround: true,
2535 newline: false,
2536 },
2537 BracketPair {
2538 start: "'".into(),
2539 end: "'".into(),
2540 close: true,
2541 surround: true,
2542 newline: false,
2543 },
2544 ],
2545 disabled_scopes_by_bracket_ix: vec![
2546 Vec::new(), //
2547 vec!["string".into()],
2548 ],
2549 },
2550 ..Default::default()
2551 },
2552 Some(tree_sitter_rust::LANGUAGE.into()),
2553 )
2554 .with_override_query(
2555 r#"
2556 (string_literal) @string
2557 "#,
2558 )
2559 .unwrap();
2560
2561 let text = r#"
2562 const S: &'static str = "hello";
2563 "#
2564 .unindent();
2565
2566 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2567 let snapshot = buffer.snapshot();
2568
2569 // By default, all brackets are enabled
2570 let config = snapshot.language_scope_at(0).unwrap();
2571 assert_eq!(
2572 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2573 &[true, true]
2574 );
2575
2576 // Within a string, the quotation brackets are disabled.
2577 let string_config = snapshot
2578 .language_scope_at(text.find("ello").unwrap())
2579 .unwrap();
2580 assert_eq!(
2581 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2582 &[true, false]
2583 );
2584
2585 buffer
2586 });
2587}
2588
2589#[gpui::test]
2590fn test_language_scope_at_with_combined_injections(cx: &mut App) {
2591 init_settings(cx, |_| {});
2592
2593 cx.new(|cx| {
2594 let text = r#"
2595 <ol>
2596 <% people.each do |person| %>
2597 <li>
2598 <%= person.name %>
2599 </li>
2600 <% end %>
2601 </ol>
2602 "#
2603 .unindent();
2604
2605 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2606 language_registry.add(Arc::new(ruby_lang()));
2607 language_registry.add(Arc::new(html_lang()));
2608 language_registry.add(Arc::new(erb_lang()));
2609
2610 let mut buffer = Buffer::local(text, cx);
2611 buffer.set_language_registry(language_registry.clone());
2612 buffer.set_language(
2613 language_registry
2614 .language_for_name("ERB")
2615 .now_or_never()
2616 .unwrap()
2617 .ok(),
2618 cx,
2619 );
2620
2621 let snapshot = buffer.snapshot();
2622 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2623 assert_eq!(html_config.line_comment_prefixes(), &[]);
2624 assert_eq!(
2625 html_config.block_comment(),
2626 Some(&BlockCommentConfig {
2627 start: "<!--".into(),
2628 end: "-->".into(),
2629 prefix: "".into(),
2630 tab_size: 0,
2631 })
2632 );
2633
2634 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2635 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2636 assert_eq!(ruby_config.block_comment(), None);
2637
2638 buffer
2639 });
2640}
2641
2642#[gpui::test]
2643fn test_language_at_with_hidden_languages(cx: &mut App) {
2644 init_settings(cx, |_| {});
2645
2646 cx.new(|cx| {
2647 let text = r#"
2648 this is an *emphasized* word.
2649 "#
2650 .unindent();
2651
2652 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2653 language_registry.add(Arc::new(markdown_lang()));
2654 language_registry.add(Arc::new(markdown_inline_lang()));
2655
2656 let mut buffer = Buffer::local(text, cx);
2657 buffer.set_language_registry(language_registry.clone());
2658 buffer.set_language(
2659 language_registry
2660 .language_for_name("Markdown")
2661 .now_or_never()
2662 .unwrap()
2663 .ok(),
2664 cx,
2665 );
2666
2667 let snapshot = buffer.snapshot();
2668
2669 for point in [Point::new(0, 4), Point::new(0, 16)] {
2670 let config = snapshot.language_scope_at(point).unwrap();
2671 assert_eq!(config.language_name(), "Markdown".into());
2672
2673 let language = snapshot.language_at(point).unwrap();
2674 assert_eq!(language.name().as_ref(), "Markdown");
2675 }
2676
2677 buffer
2678 });
2679}
2680
2681#[gpui::test]
2682fn test_language_at_for_markdown_code_block(cx: &mut App) {
2683 init_settings(cx, |_| {});
2684
2685 cx.new(|cx| {
2686 let text = r#"
2687 ```rs
2688 let a = 2;
2689 // let b = 3;
2690 ```
2691 "#
2692 .unindent();
2693
2694 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2695 language_registry.add(Arc::new(markdown_lang()));
2696 language_registry.add(Arc::new(markdown_inline_lang()));
2697 language_registry.add(Arc::new(rust_lang()));
2698
2699 let mut buffer = Buffer::local(text, cx);
2700 buffer.set_language_registry(language_registry.clone());
2701 buffer.set_language(
2702 language_registry
2703 .language_for_name("Markdown")
2704 .now_or_never()
2705 .unwrap()
2706 .ok(),
2707 cx,
2708 );
2709
2710 let snapshot = buffer.snapshot();
2711
2712 // Test points in the code line
2713 for point in [Point::new(1, 4), Point::new(1, 6)] {
2714 let config = snapshot.language_scope_at(point).unwrap();
2715 assert_eq!(config.language_name(), "Rust".into());
2716
2717 let language = snapshot.language_at(point).unwrap();
2718 assert_eq!(language.name().as_ref(), "Rust");
2719 }
2720
2721 // Test points in the comment line to verify it's still detected as Rust
2722 for point in [Point::new(2, 4), Point::new(2, 6)] {
2723 let config = snapshot.language_scope_at(point).unwrap();
2724 assert_eq!(config.language_name(), "Rust".into());
2725
2726 let language = snapshot.language_at(point).unwrap();
2727 assert_eq!(language.name().as_ref(), "Rust");
2728 }
2729
2730 buffer
2731 });
2732}
2733
2734#[gpui::test]
2735fn test_serialization(cx: &mut gpui::App) {
2736 let mut now = Instant::now();
2737
2738 let buffer1 = cx.new(|cx| {
2739 let mut buffer = Buffer::local("abc", cx);
2740 buffer.edit([(3..3, "D")], None, cx);
2741
2742 now += Duration::from_secs(1);
2743 buffer.start_transaction_at(now);
2744 buffer.edit([(4..4, "E")], None, cx);
2745 buffer.end_transaction_at(now, cx);
2746 assert_eq!(buffer.text(), "abcDE");
2747
2748 buffer.undo(cx);
2749 assert_eq!(buffer.text(), "abcD");
2750
2751 buffer.edit([(4..4, "F")], None, cx);
2752 assert_eq!(buffer.text(), "abcDF");
2753 buffer
2754 });
2755 assert_eq!(buffer1.read(cx).text(), "abcDF");
2756
2757 let state = buffer1.read(cx).to_proto(cx);
2758 let ops = cx
2759 .background_executor()
2760 .block(buffer1.read(cx).serialize_ops(None, cx));
2761 let buffer2 = cx.new(|cx| {
2762 let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
2763 buffer.apply_ops(
2764 ops.into_iter()
2765 .map(|op| proto::deserialize_operation(op).unwrap()),
2766 cx,
2767 );
2768 buffer
2769 });
2770 assert_eq!(buffer2.read(cx).text(), "abcDF");
2771}
2772
2773#[gpui::test]
2774fn test_branch_and_merge(cx: &mut TestAppContext) {
2775 cx.update(|cx| init_settings(cx, |_| {}));
2776
2777 let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
2778
2779 // Create a remote replica of the base buffer.
2780 let base_replica = cx.new(|cx| {
2781 Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
2782 });
2783 base.update(cx, |_buffer, cx| {
2784 cx.subscribe(&base_replica, |this, _, event, cx| {
2785 if let BufferEvent::Operation {
2786 operation,
2787 is_local: true,
2788 } = event
2789 {
2790 this.apply_ops([operation.clone()], cx);
2791 }
2792 })
2793 .detach();
2794 });
2795
2796 // Create a branch, which initially has the same state as the base buffer.
2797 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2798 branch.read_with(cx, |buffer, _| {
2799 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
2800 });
2801
2802 // Edits to the branch are not applied to the base.
2803 branch.update(cx, |buffer, cx| {
2804 buffer.edit(
2805 [
2806 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
2807 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
2808 ],
2809 None,
2810 cx,
2811 )
2812 });
2813 branch.read_with(cx, |buffer, cx| {
2814 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
2815 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
2816 });
2817
2818 // Convert from branch buffer ranges to the corresponding ranges in the
2819 // base buffer.
2820 branch.read_with(cx, |buffer, cx| {
2821 assert_eq!(
2822 buffer.range_to_version(4..7, &base.read(cx).version()),
2823 4..4
2824 );
2825 assert_eq!(
2826 buffer.range_to_version(2..9, &base.read(cx).version()),
2827 2..5
2828 );
2829 });
2830
2831 // Edits to the base are applied to the branch.
2832 base.update(cx, |buffer, cx| {
2833 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
2834 });
2835 branch.read_with(cx, |buffer, cx| {
2836 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
2837 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
2838 });
2839
2840 // Edits to any replica of the base are applied to the branch.
2841 base_replica.update(cx, |buffer, cx| {
2842 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
2843 });
2844 branch.read_with(cx, |buffer, cx| {
2845 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
2846 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2847 });
2848
2849 // Merging the branch applies all of its changes to the base.
2850 branch.update(cx, |buffer, cx| {
2851 buffer.merge_into_base(Vec::new(), cx);
2852 });
2853
2854 branch.update(cx, |buffer, cx| {
2855 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2856 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
2857 });
2858}
2859
2860#[gpui::test]
2861fn test_merge_into_base(cx: &mut TestAppContext) {
2862 cx.update(|cx| init_settings(cx, |_| {}));
2863
2864 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2865 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2866
2867 // Make 3 edits, merge one into the base.
2868 branch.update(cx, |branch, cx| {
2869 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
2870 branch.merge_into_base(vec![5..8], cx);
2871 });
2872
2873 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
2874 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2875
2876 // Undo the one already-merged edit. Merge that into the base.
2877 branch.update(cx, |branch, cx| {
2878 branch.edit([(7..9, "hi")], None, cx);
2879 branch.merge_into_base(vec![5..8], cx);
2880 });
2881 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2882
2883 // Merge an insertion into the base.
2884 branch.update(cx, |branch, cx| {
2885 branch.merge_into_base(vec![11..11], cx);
2886 });
2887
2888 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
2889 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
2890
2891 // Deleted the inserted text and merge that into the base.
2892 branch.update(cx, |branch, cx| {
2893 branch.edit([(11..14, "")], None, cx);
2894 branch.merge_into_base(vec![10..11], cx);
2895 });
2896
2897 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2898}
2899
2900#[gpui::test]
2901fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
2902 cx.update(|cx| init_settings(cx, |_| {}));
2903
2904 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
2905 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
2906
2907 // Make 2 edits, merge one into the base.
2908 branch.update(cx, |branch, cx| {
2909 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
2910 branch.merge_into_base(vec![7..7], cx);
2911 });
2912 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2913 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2914
2915 // Undo the merge in the base buffer.
2916 base.update(cx, |base, cx| {
2917 base.undo(cx);
2918 });
2919 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
2920 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2921
2922 // Merge that operation into the base again.
2923 branch.update(cx, |branch, cx| {
2924 branch.merge_into_base(vec![7..7], cx);
2925 });
2926 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
2927 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
2928}
2929
2930#[gpui::test]
2931async fn test_preview_edits(cx: &mut TestAppContext) {
2932 cx.update(|cx| {
2933 init_settings(cx, |_| {});
2934 theme::init(theme::LoadThemes::JustBase, cx);
2935 });
2936
2937 let insertion_style = HighlightStyle {
2938 background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
2939 ..Default::default()
2940 };
2941 let deletion_style = HighlightStyle {
2942 background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
2943 ..Default::default()
2944 };
2945
2946 // no edits
2947 assert_preview_edits(
2948 indoc! {"
2949 fn test_empty() -> bool {
2950 false
2951 }"
2952 },
2953 vec![],
2954 true,
2955 cx,
2956 |hl| {
2957 assert!(hl.text.is_empty());
2958 assert!(hl.highlights.is_empty());
2959 },
2960 )
2961 .await;
2962
2963 // only insertions
2964 assert_preview_edits(
2965 indoc! {"
2966 fn calculate_area(: f64) -> f64 {
2967 std::f64::consts::PI * .powi(2)
2968 }"
2969 },
2970 vec![
2971 (Point::new(0, 18)..Point::new(0, 18), "radius"),
2972 (Point::new(1, 27)..Point::new(1, 27), "radius"),
2973 ],
2974 true,
2975 cx,
2976 |hl| {
2977 assert_eq!(
2978 hl.text,
2979 indoc! {"
2980 fn calculate_area(radius: f64) -> f64 {
2981 std::f64::consts::PI * radius.powi(2)"
2982 }
2983 );
2984
2985 assert_eq!(hl.highlights.len(), 2);
2986 assert_eq!(hl.highlights[0], ((18..24), insertion_style));
2987 assert_eq!(hl.highlights[1], ((67..73), insertion_style));
2988 },
2989 )
2990 .await;
2991
2992 // insertions & deletions
2993 assert_preview_edits(
2994 indoc! {"
2995 struct Person {
2996 first_name: String,
2997 }
2998
2999 impl Person {
3000 fn first_name(&self) -> &String {
3001 &self.first_name
3002 }
3003 }"
3004 },
3005 vec![
3006 (Point::new(1, 4)..Point::new(1, 9), "last"),
3007 (Point::new(5, 7)..Point::new(5, 12), "last"),
3008 (Point::new(6, 14)..Point::new(6, 19), "last"),
3009 ],
3010 true,
3011 cx,
3012 |hl| {
3013 assert_eq!(
3014 hl.text,
3015 indoc! {"
3016 firstlast_name: String,
3017 }
3018
3019 impl Person {
3020 fn firstlast_name(&self) -> &String {
3021 &self.firstlast_name"
3022 }
3023 );
3024
3025 assert_eq!(hl.highlights.len(), 6);
3026 assert_eq!(hl.highlights[0], ((4..9), deletion_style));
3027 assert_eq!(hl.highlights[1], ((9..13), insertion_style));
3028 assert_eq!(hl.highlights[2], ((52..57), deletion_style));
3029 assert_eq!(hl.highlights[3], ((57..61), insertion_style));
3030 assert_eq!(hl.highlights[4], ((101..106), deletion_style));
3031 assert_eq!(hl.highlights[5], ((106..110), insertion_style));
3032 },
3033 )
3034 .await;
3035
3036 async fn assert_preview_edits(
3037 text: &str,
3038 edits: Vec<(Range<Point>, &str)>,
3039 include_deletions: bool,
3040 cx: &mut TestAppContext,
3041 assert_fn: impl Fn(HighlightedText),
3042 ) {
3043 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
3044 let edits = buffer.read_with(cx, |buffer, _| {
3045 edits
3046 .into_iter()
3047 .map(|(range, text)| {
3048 (
3049 buffer.anchor_before(range.start)..buffer.anchor_after(range.end),
3050 text.to_string(),
3051 )
3052 })
3053 .collect::<Vec<_>>()
3054 });
3055 let edit_preview = buffer
3056 .read_with(cx, |buffer, cx| {
3057 buffer.preview_edits(edits.clone().into(), cx)
3058 })
3059 .await;
3060 let highlighted_edits = cx.read(|cx| {
3061 edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
3062 });
3063 assert_fn(highlighted_edits);
3064 }
3065}
3066
3067#[gpui::test(iterations = 100)]
3068fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
3069 let min_peers = env::var("MIN_PEERS")
3070 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
3071 .unwrap_or(1);
3072 let max_peers = env::var("MAX_PEERS")
3073 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
3074 .unwrap_or(5);
3075 let operations = env::var("OPERATIONS")
3076 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3077 .unwrap_or(10);
3078
3079 let base_text_len = rng.random_range(0..10);
3080 let base_text = RandomCharIter::new(&mut rng)
3081 .take(base_text_len)
3082 .collect::<String>();
3083 let mut replica_ids = Vec::new();
3084 let mut buffers = Vec::new();
3085 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
3086 let base_buffer = cx.new(|cx| Buffer::local(base_text.as_str(), cx));
3087
3088 for i in 0..rng.random_range(min_peers..=max_peers) {
3089 let buffer = cx.new(|cx| {
3090 let state = base_buffer.read(cx).to_proto(cx);
3091 let ops = cx
3092 .background_executor()
3093 .block(base_buffer.read(cx).serialize_ops(None, cx));
3094 let mut buffer =
3095 Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
3096 buffer.apply_ops(
3097 ops.into_iter()
3098 .map(|op| proto::deserialize_operation(op).unwrap()),
3099 cx,
3100 );
3101 buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
3102 let network = network.clone();
3103 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3104 if let BufferEvent::Operation {
3105 operation,
3106 is_local: true,
3107 } = event
3108 {
3109 network.lock().broadcast(
3110 buffer.replica_id(),
3111 vec![proto::serialize_operation(operation)],
3112 );
3113 }
3114 })
3115 .detach();
3116 buffer
3117 });
3118
3119 buffers.push(buffer);
3120 replica_ids.push(i as ReplicaId);
3121 network.lock().add_peer(i as ReplicaId);
3122 log::info!("Adding initial peer with replica id {}", i);
3123 }
3124
3125 log::info!("initial text: {:?}", base_text);
3126
3127 let mut now = Instant::now();
3128 let mut mutation_count = operations;
3129 let mut next_diagnostic_id = 0;
3130 let mut active_selections = BTreeMap::default();
3131 loop {
3132 let replica_index = rng.random_range(0..replica_ids.len());
3133 let replica_id = replica_ids[replica_index];
3134 let buffer = &mut buffers[replica_index];
3135 let mut new_buffer = None;
3136 match rng.random_range(0..100) {
3137 0..=29 if mutation_count != 0 => {
3138 buffer.update(cx, |buffer, cx| {
3139 buffer.start_transaction_at(now);
3140 buffer.randomly_edit(&mut rng, 5, cx);
3141 buffer.end_transaction_at(now, cx);
3142 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
3143 });
3144 mutation_count -= 1;
3145 }
3146 30..=39 if mutation_count != 0 => {
3147 buffer.update(cx, |buffer, cx| {
3148 if rng.random_bool(0.2) {
3149 log::info!("peer {} clearing active selections", replica_id);
3150 active_selections.remove(&replica_id);
3151 buffer.remove_active_selections(cx);
3152 } else {
3153 let mut selections = Vec::new();
3154 for id in 0..rng.random_range(1..=5) {
3155 let range = buffer.random_byte_range(0, &mut rng);
3156 selections.push(Selection {
3157 id,
3158 start: buffer.anchor_before(range.start),
3159 end: buffer.anchor_before(range.end),
3160 reversed: false,
3161 goal: SelectionGoal::None,
3162 });
3163 }
3164 let selections: Arc<[Selection<Anchor>]> = selections.into();
3165 log::info!(
3166 "peer {} setting active selections: {:?}",
3167 replica_id,
3168 selections
3169 );
3170 active_selections.insert(replica_id, selections.clone());
3171 buffer.set_active_selections(selections, false, Default::default(), cx);
3172 }
3173 });
3174 mutation_count -= 1;
3175 }
3176 40..=49 if mutation_count != 0 && replica_id == 0 => {
3177 let entry_count = rng.random_range(1..=5);
3178 buffer.update(cx, |buffer, cx| {
3179 let diagnostics = DiagnosticSet::new(
3180 (0..entry_count).map(|_| {
3181 let range = buffer.random_byte_range(0, &mut rng);
3182 let range = range.to_point_utf16(buffer);
3183 let range = range.start..range.end;
3184 DiagnosticEntry {
3185 range,
3186 diagnostic: Diagnostic {
3187 message: post_inc(&mut next_diagnostic_id).to_string(),
3188 ..Default::default()
3189 },
3190 }
3191 }),
3192 buffer,
3193 );
3194 log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
3195 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
3196 });
3197 mutation_count -= 1;
3198 }
3199 50..=59 if replica_ids.len() < max_peers => {
3200 let old_buffer_state = buffer.read(cx).to_proto(cx);
3201 let old_buffer_ops = cx
3202 .background_executor()
3203 .block(buffer.read(cx).serialize_ops(None, cx));
3204 let new_replica_id = (0..=replica_ids.len() as ReplicaId)
3205 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
3206 .choose(&mut rng)
3207 .unwrap();
3208 log::info!(
3209 "Adding new replica {} (replicating from {})",
3210 new_replica_id,
3211 replica_id
3212 );
3213 new_buffer = Some(cx.new(|cx| {
3214 let mut new_buffer = Buffer::from_proto(
3215 new_replica_id,
3216 Capability::ReadWrite,
3217 old_buffer_state,
3218 None,
3219 )
3220 .unwrap();
3221 new_buffer.apply_ops(
3222 old_buffer_ops
3223 .into_iter()
3224 .map(|op| deserialize_operation(op).unwrap()),
3225 cx,
3226 );
3227 log::info!(
3228 "New replica {} text: {:?}",
3229 new_buffer.replica_id(),
3230 new_buffer.text()
3231 );
3232 new_buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
3233 let network = network.clone();
3234 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3235 if let BufferEvent::Operation {
3236 operation,
3237 is_local: true,
3238 } = event
3239 {
3240 network.lock().broadcast(
3241 buffer.replica_id(),
3242 vec![proto::serialize_operation(operation)],
3243 );
3244 }
3245 })
3246 .detach();
3247 new_buffer
3248 }));
3249 network.lock().replicate(replica_id, new_replica_id);
3250
3251 if new_replica_id as usize == replica_ids.len() {
3252 replica_ids.push(new_replica_id);
3253 } else {
3254 let new_buffer = new_buffer.take().unwrap();
3255 while network.lock().has_unreceived(new_replica_id) {
3256 let ops = network
3257 .lock()
3258 .receive(new_replica_id)
3259 .into_iter()
3260 .map(|op| proto::deserialize_operation(op).unwrap());
3261 if ops.len() > 0 {
3262 log::info!(
3263 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3264 new_replica_id,
3265 buffer.read(cx).version(),
3266 ops.len(),
3267 ops
3268 );
3269 new_buffer.update(cx, |new_buffer, cx| {
3270 new_buffer.apply_ops(ops, cx);
3271 });
3272 }
3273 }
3274 buffers[new_replica_id as usize] = new_buffer;
3275 }
3276 }
3277 60..=69 if mutation_count != 0 => {
3278 buffer.update(cx, |buffer, cx| {
3279 buffer.randomly_undo_redo(&mut rng, cx);
3280 log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
3281 });
3282 mutation_count -= 1;
3283 }
3284 _ if network.lock().has_unreceived(replica_id) => {
3285 let ops = network
3286 .lock()
3287 .receive(replica_id)
3288 .into_iter()
3289 .map(|op| proto::deserialize_operation(op).unwrap());
3290 if ops.len() > 0 {
3291 log::info!(
3292 "peer {} (version: {:?}) applying {} ops from the network. {:?}",
3293 replica_id,
3294 buffer.read(cx).version(),
3295 ops.len(),
3296 ops
3297 );
3298 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
3299 }
3300 }
3301 _ => {}
3302 }
3303
3304 now += Duration::from_millis(rng.random_range(0..=200));
3305 buffers.extend(new_buffer);
3306
3307 for buffer in &buffers {
3308 buffer.read(cx).check_invariants();
3309 }
3310
3311 if mutation_count == 0 && network.lock().is_idle() {
3312 break;
3313 }
3314 }
3315
3316 let first_buffer = buffers[0].read(cx).snapshot();
3317 for buffer in &buffers[1..] {
3318 let buffer = buffer.read(cx).snapshot();
3319 assert_eq!(
3320 buffer.version(),
3321 first_buffer.version(),
3322 "Replica {} version != Replica 0 version",
3323 buffer.replica_id()
3324 );
3325 assert_eq!(
3326 buffer.text(),
3327 first_buffer.text(),
3328 "Replica {} text != Replica 0 text",
3329 buffer.replica_id()
3330 );
3331 assert_eq!(
3332 buffer
3333 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
3334 .collect::<Vec<_>>(),
3335 first_buffer
3336 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
3337 .collect::<Vec<_>>(),
3338 "Replica {} diagnostics != Replica 0 diagnostics",
3339 buffer.replica_id()
3340 );
3341 }
3342
3343 for buffer in &buffers {
3344 let buffer = buffer.read(cx).snapshot();
3345 let actual_remote_selections = buffer
3346 .selections_in_range(Anchor::MIN..Anchor::MAX, false)
3347 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3348 .collect::<Vec<_>>();
3349 let expected_remote_selections = active_selections
3350 .iter()
3351 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3352 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3353 .collect::<Vec<_>>();
3354 assert_eq!(
3355 actual_remote_selections,
3356 expected_remote_selections,
3357 "Replica {} remote selections != expected selections",
3358 buffer.replica_id()
3359 );
3360 }
3361}
3362
3363#[test]
3364fn test_contiguous_ranges() {
3365 assert_eq!(
3366 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3367 &[1..4, 5..7, 9..13]
3368 );
3369
3370 // Respects the `max_len` parameter
3371 assert_eq!(
3372 contiguous_ranges(
3373 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3374 3
3375 )
3376 .collect::<Vec<_>>(),
3377 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3378 );
3379}
3380
3381#[gpui::test(iterations = 500)]
3382fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3383 // Generate a random multi-line string containing
3384 // some lines with trailing whitespace.
3385 let mut text = String::new();
3386 for _ in 0..rng.random_range(0..16) {
3387 for _ in 0..rng.random_range(0..36) {
3388 text.push(match rng.random_range(0..10) {
3389 0..=1 => ' ',
3390 3 => '\t',
3391 _ => rng.random_range('a'..='z'),
3392 });
3393 }
3394 text.push('\n');
3395 }
3396
3397 match rng.random_range(0..10) {
3398 // sometimes remove the last newline
3399 0..=1 => drop(text.pop()), //
3400
3401 // sometimes add extra newlines
3402 2..=3 => text.push_str(&"\n".repeat(rng.random_range(1..5))),
3403 _ => {}
3404 }
3405
3406 let rope = Rope::from(text.as_str());
3407 let actual_ranges = trailing_whitespace_ranges(&rope);
3408 let expected_ranges = TRAILING_WHITESPACE_REGEX
3409 .find_iter(&text)
3410 .map(|m| m.range())
3411 .collect::<Vec<_>>();
3412 assert_eq!(
3413 actual_ranges,
3414 expected_ranges,
3415 "wrong ranges for text lines:\n{:?}",
3416 text.split('\n').collect::<Vec<_>>()
3417 );
3418}
3419
3420#[gpui::test]
3421fn test_words_in_range(cx: &mut gpui::App) {
3422 init_settings(cx, |_| {});
3423
3424 // The first line are words excluded from the results with heuristics, we do not expect them in the test assertions.
3425 let contents = r#"
34260_isize 123 3.4 4
3427let word=öäpple.bar你 Öäpple word2-öÄpPlE-Pizza-word ÖÄPPLE word
3428 "#;
3429
3430 let buffer = cx.new(|cx| {
3431 let buffer = Buffer::local(contents, cx).with_language(Arc::new(rust_lang()), cx);
3432 assert_eq!(buffer.text(), contents);
3433 buffer.check_invariants();
3434 buffer
3435 });
3436
3437 buffer.update(cx, |buffer, _| {
3438 let snapshot = buffer.snapshot();
3439 assert_eq!(
3440 BTreeSet::from_iter(["Pizza".to_string()]),
3441 snapshot
3442 .words_in_range(WordsQuery {
3443 fuzzy_contents: Some("piz"),
3444 skip_digits: true,
3445 range: 0..snapshot.len(),
3446 })
3447 .into_keys()
3448 .collect::<BTreeSet<_>>()
3449 );
3450 assert_eq!(
3451 BTreeSet::from_iter([
3452 "öäpple".to_string(),
3453 "Öäpple".to_string(),
3454 "öÄpPlE".to_string(),
3455 "ÖÄPPLE".to_string(),
3456 ]),
3457 snapshot
3458 .words_in_range(WordsQuery {
3459 fuzzy_contents: Some("öp"),
3460 skip_digits: true,
3461 range: 0..snapshot.len(),
3462 })
3463 .into_keys()
3464 .collect::<BTreeSet<_>>()
3465 );
3466 assert_eq!(
3467 BTreeSet::from_iter([
3468 "öÄpPlE".to_string(),
3469 "Öäpple".to_string(),
3470 "ÖÄPPLE".to_string(),
3471 "öäpple".to_string(),
3472 ]),
3473 snapshot
3474 .words_in_range(WordsQuery {
3475 fuzzy_contents: Some("öÄ"),
3476 skip_digits: true,
3477 range: 0..snapshot.len(),
3478 })
3479 .into_keys()
3480 .collect::<BTreeSet<_>>()
3481 );
3482 assert_eq!(
3483 BTreeSet::default(),
3484 snapshot
3485 .words_in_range(WordsQuery {
3486 fuzzy_contents: Some("öÄ好"),
3487 skip_digits: true,
3488 range: 0..snapshot.len(),
3489 })
3490 .into_keys()
3491 .collect::<BTreeSet<_>>()
3492 );
3493 assert_eq!(
3494 BTreeSet::from_iter(["bar你".to_string(),]),
3495 snapshot
3496 .words_in_range(WordsQuery {
3497 fuzzy_contents: Some("你"),
3498 skip_digits: true,
3499 range: 0..snapshot.len(),
3500 })
3501 .into_keys()
3502 .collect::<BTreeSet<_>>()
3503 );
3504 assert_eq!(
3505 BTreeSet::default(),
3506 snapshot
3507 .words_in_range(WordsQuery {
3508 fuzzy_contents: Some(""),
3509 skip_digits: true,
3510 range: 0..snapshot.len(),
3511 },)
3512 .into_keys()
3513 .collect::<BTreeSet<_>>()
3514 );
3515 assert_eq!(
3516 BTreeSet::from_iter([
3517 "bar你".to_string(),
3518 "öÄpPlE".to_string(),
3519 "Öäpple".to_string(),
3520 "ÖÄPPLE".to_string(),
3521 "öäpple".to_string(),
3522 "let".to_string(),
3523 "Pizza".to_string(),
3524 "word".to_string(),
3525 "word2".to_string(),
3526 ]),
3527 snapshot
3528 .words_in_range(WordsQuery {
3529 fuzzy_contents: None,
3530 skip_digits: true,
3531 range: 0..snapshot.len(),
3532 })
3533 .into_keys()
3534 .collect::<BTreeSet<_>>()
3535 );
3536 assert_eq!(
3537 BTreeSet::from_iter([
3538 "0_isize".to_string(),
3539 "123".to_string(),
3540 "3".to_string(),
3541 "4".to_string(),
3542 "bar你".to_string(),
3543 "öÄpPlE".to_string(),
3544 "Öäpple".to_string(),
3545 "ÖÄPPLE".to_string(),
3546 "öäpple".to_string(),
3547 "let".to_string(),
3548 "Pizza".to_string(),
3549 "word".to_string(),
3550 "word2".to_string(),
3551 ]),
3552 snapshot
3553 .words_in_range(WordsQuery {
3554 fuzzy_contents: None,
3555 skip_digits: false,
3556 range: 0..snapshot.len(),
3557 })
3558 .into_keys()
3559 .collect::<BTreeSet<_>>()
3560 );
3561 });
3562}
3563
3564fn ruby_lang() -> Language {
3565 Language::new(
3566 LanguageConfig {
3567 name: "Ruby".into(),
3568 matcher: LanguageMatcher {
3569 path_suffixes: vec!["rb".to_string()],
3570 ..Default::default()
3571 },
3572 line_comments: vec!["# ".into()],
3573 ..Default::default()
3574 },
3575 Some(tree_sitter_ruby::LANGUAGE.into()),
3576 )
3577 .with_indents_query(
3578 r#"
3579 (class "end" @end) @indent
3580 (method "end" @end) @indent
3581 (rescue) @outdent
3582 (then) @indent
3583 "#,
3584 )
3585 .unwrap()
3586}
3587
3588fn html_lang() -> Language {
3589 Language::new(
3590 LanguageConfig {
3591 name: LanguageName::new("HTML"),
3592 block_comment: Some(BlockCommentConfig {
3593 start: "<!--".into(),
3594 prefix: "".into(),
3595 end: "-->".into(),
3596 tab_size: 0,
3597 }),
3598 ..Default::default()
3599 },
3600 Some(tree_sitter_html::LANGUAGE.into()),
3601 )
3602 .with_indents_query(
3603 "
3604 (element
3605 (start_tag) @start
3606 (end_tag)? @end) @indent
3607 ",
3608 )
3609 .unwrap()
3610 .with_injection_query(
3611 r#"
3612 (script_element
3613 (raw_text) @injection.content
3614 (#set! injection.language "javascript"))
3615 "#,
3616 )
3617 .unwrap()
3618}
3619
3620fn erb_lang() -> Language {
3621 Language::new(
3622 LanguageConfig {
3623 name: "ERB".into(),
3624 matcher: LanguageMatcher {
3625 path_suffixes: vec!["erb".to_string()],
3626 ..Default::default()
3627 },
3628 block_comment: Some(BlockCommentConfig {
3629 start: "<%#".into(),
3630 prefix: "".into(),
3631 end: "%>".into(),
3632 tab_size: 0,
3633 }),
3634 ..Default::default()
3635 },
3636 Some(tree_sitter_embedded_template::LANGUAGE.into()),
3637 )
3638 .with_injection_query(
3639 r#"
3640 (
3641 (code) @injection.content
3642 (#set! injection.language "ruby")
3643 (#set! injection.combined)
3644 )
3645
3646 (
3647 (content) @injection.content
3648 (#set! injection.language "html")
3649 (#set! injection.combined)
3650 )
3651 "#,
3652 )
3653 .unwrap()
3654}
3655
3656fn rust_lang() -> Language {
3657 Language::new(
3658 LanguageConfig {
3659 name: "Rust".into(),
3660 matcher: LanguageMatcher {
3661 path_suffixes: vec!["rs".to_string()],
3662 ..Default::default()
3663 },
3664 ..Default::default()
3665 },
3666 Some(tree_sitter_rust::LANGUAGE.into()),
3667 )
3668 .with_indents_query(
3669 r#"
3670 (call_expression) @indent
3671 (field_expression) @indent
3672 (_ "(" ")" @end) @indent
3673 (_ "{" "}" @end) @indent
3674 "#,
3675 )
3676 .unwrap()
3677 .with_brackets_query(
3678 r#"
3679 ("{" @open "}" @close)
3680 "#,
3681 )
3682 .unwrap()
3683 .with_text_object_query(
3684 r#"
3685 (function_item
3686 body: (_
3687 "{"
3688 (_)* @function.inside
3689 "}" )) @function.around
3690
3691 (line_comment)+ @comment.around
3692
3693 (block_comment) @comment.around
3694 "#,
3695 )
3696 .unwrap()
3697 .with_outline_query(
3698 r#"
3699 (line_comment) @annotation
3700
3701 (struct_item
3702 "struct" @context
3703 name: (_) @name) @item
3704 (enum_item
3705 "enum" @context
3706 name: (_) @name) @item
3707 (enum_variant
3708 name: (_) @name) @item
3709 (field_declaration
3710 name: (_) @name) @item
3711 (impl_item
3712 "impl" @context
3713 trait: (_)? @name
3714 "for"? @context
3715 type: (_) @name
3716 body: (_ "{" (_)* "}")) @item
3717 (function_item
3718 "fn" @context
3719 name: (_) @name) @item
3720 (mod_item
3721 "mod" @context
3722 name: (_) @name) @item
3723 "#,
3724 )
3725 .unwrap()
3726}
3727
3728fn json_lang() -> Language {
3729 Language::new(
3730 LanguageConfig {
3731 name: "Json".into(),
3732 matcher: LanguageMatcher {
3733 path_suffixes: vec!["js".to_string()],
3734 ..Default::default()
3735 },
3736 ..Default::default()
3737 },
3738 Some(tree_sitter_json::LANGUAGE.into()),
3739 )
3740}
3741
3742fn javascript_lang() -> Language {
3743 Language::new(
3744 LanguageConfig {
3745 name: "JavaScript".into(),
3746 ..Default::default()
3747 },
3748 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
3749 )
3750 .with_brackets_query(
3751 r#"
3752 ("{" @open "}" @close)
3753 ("(" @open ")" @close)
3754 "#,
3755 )
3756 .unwrap()
3757 .with_indents_query(
3758 r#"
3759 (object "}" @end) @indent
3760 "#,
3761 )
3762 .unwrap()
3763}
3764
3765pub fn markdown_lang() -> Language {
3766 Language::new(
3767 LanguageConfig {
3768 name: "Markdown".into(),
3769 matcher: LanguageMatcher {
3770 path_suffixes: vec!["md".into()],
3771 ..Default::default()
3772 },
3773 ..Default::default()
3774 },
3775 Some(tree_sitter_md::LANGUAGE.into()),
3776 )
3777 .with_injection_query(
3778 r#"
3779 (fenced_code_block
3780 (info_string
3781 (language) @injection.language)
3782 (code_fence_content) @injection.content)
3783
3784 ((inline) @injection.content
3785 (#set! injection.language "markdown-inline"))
3786 "#,
3787 )
3788 .unwrap()
3789}
3790
3791pub fn markdown_inline_lang() -> Language {
3792 Language::new(
3793 LanguageConfig {
3794 name: "Markdown-Inline".into(),
3795 hidden: true,
3796 ..LanguageConfig::default()
3797 },
3798 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
3799 )
3800 .with_highlights_query("(emphasis) @emphasis")
3801 .unwrap()
3802}
3803
3804fn get_tree_sexp(buffer: &Entity<Buffer>, cx: &mut gpui::TestAppContext) -> String {
3805 buffer.update(cx, |buffer, _| {
3806 let snapshot = buffer.snapshot();
3807 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
3808 layers[0].node().to_sexp()
3809 })
3810}
3811
3812// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
3813#[track_caller]
3814fn assert_bracket_pairs(
3815 selection_text: &'static str,
3816 bracket_pair_texts: Vec<&'static str>,
3817 language: Language,
3818 cx: &mut App,
3819) {
3820 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
3821 let buffer =
3822 cx.new(|cx| Buffer::local(expected_text.clone(), cx).with_language(Arc::new(language), cx));
3823 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
3824
3825 let selection_range = selection_ranges[0].clone();
3826
3827 let bracket_pairs = bracket_pair_texts
3828 .into_iter()
3829 .map(|pair_text| {
3830 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
3831 assert_eq!(bracket_text, expected_text);
3832 (ranges[0].clone(), ranges[1].clone())
3833 })
3834 .collect::<Vec<_>>();
3835
3836 assert_set_eq!(
3837 buffer
3838 .bracket_ranges(selection_range)
3839 .map(|pair| (pair.open_range, pair.close_range))
3840 .collect::<Vec<_>>(),
3841 bracket_pairs
3842 );
3843}
3844
3845fn init_settings(cx: &mut App, f: fn(&mut AllLanguageSettingsContent)) {
3846 let settings_store = SettingsStore::test(cx);
3847 cx.set_global(settings_store);
3848 crate::init(cx);
3849 cx.update_global::<SettingsStore, _>(|settings, cx| {
3850 settings.update_user_settings(cx, |content| f(&mut content.project.all_languages));
3851 });
3852}
3853
3854#[gpui::test(iterations = 100)]
3855fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
3856 use util::RandomCharIter;
3857
3858 // Generate random text
3859 let len = rng.random_range(0..10000);
3860 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
3861
3862 let buffer = cx.new(|cx| Buffer::local(text, cx));
3863 let snapshot = buffer.read(cx).snapshot();
3864
3865 // Get all chunks and verify their bitmaps
3866 let chunks = snapshot.chunks(0..snapshot.len(), false);
3867
3868 for chunk in chunks {
3869 let chunk_text = chunk.text;
3870 let chars_bitmap = chunk.chars;
3871 let tabs_bitmap = chunk.tabs;
3872
3873 // Check empty chunks have empty bitmaps
3874 if chunk_text.is_empty() {
3875 assert_eq!(
3876 chars_bitmap, 0,
3877 "Empty chunk should have empty chars bitmap"
3878 );
3879 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
3880 continue;
3881 }
3882
3883 // Verify that chunk text doesn't exceed 128 bytes
3884 assert!(
3885 chunk_text.len() <= 128,
3886 "Chunk text length {} exceeds 128 bytes",
3887 chunk_text.len()
3888 );
3889
3890 // Verify chars bitmap
3891 let char_indices = chunk_text
3892 .char_indices()
3893 .map(|(i, _)| i)
3894 .collect::<Vec<_>>();
3895
3896 for byte_idx in 0..chunk_text.len() {
3897 let should_have_bit = char_indices.contains(&byte_idx);
3898 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
3899
3900 if has_bit != should_have_bit {
3901 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
3902 eprintln!("Char indices: {:?}", char_indices);
3903 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
3904 }
3905
3906 assert_eq!(
3907 has_bit, should_have_bit,
3908 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
3909 byte_idx, chunk_text, should_have_bit, has_bit
3910 );
3911 }
3912
3913 // Verify tabs bitmap
3914 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
3915 let is_tab = byte == b'\t';
3916 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
3917
3918 if has_bit != is_tab {
3919 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
3920 eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
3921 assert_eq!(
3922 has_bit, is_tab,
3923 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
3924 byte_idx, chunk_text, byte as char, is_tab, has_bit
3925 );
3926 }
3927 }
3928 }
3929}