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