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