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