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