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