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