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