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