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