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