1use super::*;
2use crate::Buffer;
3use clock::ReplicaId;
4use collections::BTreeMap;
5use futures::FutureExt as _;
6use gpui::{App, AppContext as _, BorrowAppContext, Entity};
7use gpui::{HighlightStyle, TestAppContext};
8use indoc::indoc;
9use pretty_assertions::assert_eq;
10use proto::deserialize_operation;
11use rand::prelude::*;
12use regex::RegexBuilder;
13use settings::SettingsStore;
14use settings::{AllLanguageSettingsContent, LanguageSettingsContent};
15use std::collections::BTreeSet;
16use std::fmt::Write as _;
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 smol::future::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_large_block(cx: &mut App) {
1376 // Build a buffer with an impl block large enough that the distance between
1377 // `{` and `}` exceeds MAX_BYTES_TO_QUERY (16 KB). Each comment line is
1378 // ~24 bytes, so ~700 lines push us past the limit.
1379 let comment_line_count = 1000;
1380 let mut source = String::from("impl Foo {\n");
1381 for i in 0..comment_line_count {
1382 writeln!(source, " // line {i:04} padding").unwrap();
1383 }
1384 source.push_str("}\n");
1385
1386 let buffer = cx.new(|cx| Buffer::local(source.clone(), cx).with_language(rust_lang(), cx));
1387 let snapshot = buffer.update(cx, |buffer, _cx| buffer.snapshot());
1388
1389 let open_brace = source.find('{').unwrap();
1390 let close_brace = source.rfind('}').unwrap();
1391
1392 // Cursor right after the opening brace — should find the enclosing pair.
1393 let cursor = open_brace + 1;
1394 let pairs = snapshot
1395 .enclosing_bracket_ranges(cursor..cursor)
1396 .map(|pair| (pair.open_range, pair.close_range))
1397 .collect::<Vec<_>>();
1398 assert_eq!(
1399 pairs,
1400 vec![(open_brace..open_brace + 1, close_brace..close_brace + 1)],
1401 "enclosing_bracket_ranges should find the bracket pair even when \
1402 open and close are in different row-chunks"
1403 );
1404
1405 // Cursor at the opening brace itself.
1406 let pairs = snapshot
1407 .enclosing_bracket_ranges(open_brace..open_brace)
1408 .map(|pair| (pair.open_range, pair.close_range))
1409 .collect::<Vec<_>>();
1410 assert_eq!(
1411 pairs,
1412 vec![(open_brace..open_brace + 1, close_brace..close_brace + 1)],
1413 "cursor at the opening brace should also find the pair"
1414 );
1415
1416 // Cursor somewhere in the middle of the block.
1417 let middle = source.len() / 2;
1418 let pairs = snapshot
1419 .enclosing_bracket_ranges(middle..middle)
1420 .map(|pair| (pair.open_range, pair.close_range))
1421 .collect::<Vec<_>>();
1422 assert_eq!(
1423 pairs,
1424 vec![(open_brace..open_brace + 1, close_brace..close_brace + 1)],
1425 "cursor in the middle of a large block should find the enclosing pair"
1426 );
1427}
1428
1429#[gpui::test]
1430fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &mut App) {
1431 let mut assert = |selection_text, bracket_pair_texts| {
1432 assert_bracket_pairs(
1433 selection_text,
1434 bracket_pair_texts,
1435 Arc::new(javascript_lang()),
1436 cx,
1437 )
1438 };
1439
1440 assert(
1441 indoc! {"
1442 for (const a in b)ˇ {
1443 // a comment that's longer than the for-loop header
1444 }"},
1445 vec![indoc! {"
1446 for «(»const a in b«)» {
1447 // a comment that's longer than the for-loop header
1448 }"}],
1449 );
1450
1451 // Regression test: even though the parent node of the parentheses (the for loop) does
1452 // intersect the given range, the parentheses themselves do not contain the range, so
1453 // they should not be returned. Only the curly braces contain the range.
1454 assert(
1455 indoc! {"
1456 for (const a in b) {ˇ
1457 // a comment that's longer than the for-loop header
1458 }"},
1459 vec![indoc! {"
1460 for (const a in b) «{»
1461 // a comment that's longer than the for-loop header
1462 «}»"}],
1463 );
1464}
1465
1466#[gpui::test]
1467fn test_range_for_syntax_ancestor(cx: &mut App) {
1468 cx.new(|cx| {
1469 let text = "fn a() { b(|c| {}) }";
1470 let buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
1471 let snapshot = buffer.snapshot();
1472
1473 assert_eq!(
1474 snapshot
1475 .syntax_ancestor(empty_range_at(text, "|"))
1476 .unwrap()
1477 .byte_range(),
1478 range_of(text, "|")
1479 );
1480 assert_eq!(
1481 snapshot
1482 .syntax_ancestor(range_of(text, "|"))
1483 .unwrap()
1484 .byte_range(),
1485 range_of(text, "|c|")
1486 );
1487 assert_eq!(
1488 snapshot
1489 .syntax_ancestor(range_of(text, "|c|"))
1490 .unwrap()
1491 .byte_range(),
1492 range_of(text, "|c| {}")
1493 );
1494 assert_eq!(
1495 snapshot
1496 .syntax_ancestor(range_of(text, "|c| {}"))
1497 .unwrap()
1498 .byte_range(),
1499 range_of(text, "(|c| {})")
1500 );
1501
1502 buffer
1503 });
1504
1505 fn empty_range_at(text: &str, part: &str) -> Range<usize> {
1506 let start = text.find(part).unwrap();
1507 start..start
1508 }
1509
1510 fn range_of(text: &str, part: &str) -> Range<usize> {
1511 let start = text.find(part).unwrap();
1512 start..start + part.len()
1513 }
1514}
1515
1516#[gpui::test]
1517fn test_autoindent_with_soft_tabs(cx: &mut App) {
1518 init_settings(cx, |_| {});
1519
1520 cx.new(|cx| {
1521 let text = "fn a() {}";
1522 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
1523
1524 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1525 assert_eq!(buffer.text(), "fn a() {\n \n}");
1526
1527 buffer.edit(
1528 [(Point::new(1, 4)..Point::new(1, 4), "b()\n")],
1529 Some(AutoindentMode::EachLine),
1530 cx,
1531 );
1532 assert_eq!(buffer.text(), "fn a() {\n b()\n \n}");
1533
1534 // Create a field expression on a new line, causing that line
1535 // to be indented.
1536 buffer.edit(
1537 [(Point::new(2, 4)..Point::new(2, 4), ".c")],
1538 Some(AutoindentMode::EachLine),
1539 cx,
1540 );
1541 assert_eq!(buffer.text(), "fn a() {\n b()\n .c\n}");
1542
1543 // Remove the dot so that the line is no longer a field expression,
1544 // causing the line to be outdented.
1545 buffer.edit(
1546 [(Point::new(2, 8)..Point::new(2, 9), "")],
1547 Some(AutoindentMode::EachLine),
1548 cx,
1549 );
1550 assert_eq!(buffer.text(), "fn a() {\n b()\n c\n}");
1551
1552 buffer
1553 });
1554}
1555
1556#[gpui::test]
1557fn test_autoindent_with_hard_tabs(cx: &mut App) {
1558 init_settings(cx, |settings| {
1559 settings.defaults.hard_tabs = Some(true);
1560 });
1561
1562 cx.new(|cx| {
1563 let text = "fn a() {}";
1564 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
1565
1566 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
1567 assert_eq!(buffer.text(), "fn a() {\n\t\n}");
1568
1569 buffer.edit(
1570 [(Point::new(1, 1)..Point::new(1, 1), "b()\n")],
1571 Some(AutoindentMode::EachLine),
1572 cx,
1573 );
1574 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\n}");
1575
1576 // Create a field expression on a new line, causing that line
1577 // to be indented.
1578 buffer.edit(
1579 [(Point::new(2, 1)..Point::new(2, 1), ".c")],
1580 Some(AutoindentMode::EachLine),
1581 cx,
1582 );
1583 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\t\t.c\n}");
1584
1585 // Remove the dot so that the line is no longer a field expression,
1586 // causing the line to be outdented.
1587 buffer.edit(
1588 [(Point::new(2, 2)..Point::new(2, 3), "")],
1589 Some(AutoindentMode::EachLine),
1590 cx,
1591 );
1592 assert_eq!(buffer.text(), "fn a() {\n\tb()\n\tc\n}");
1593
1594 buffer
1595 });
1596}
1597
1598#[gpui::test]
1599fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut App) {
1600 init_settings(cx, |_| {});
1601
1602 cx.new(|cx| {
1603 let mut buffer = Buffer::local(
1604 "
1605 fn a() {
1606 c;
1607 d;
1608 }
1609 "
1610 .unindent(),
1611 cx,
1612 )
1613 .with_language(rust_lang(), cx);
1614
1615 // Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
1616 // their indentation is not adjusted.
1617 buffer.edit_via_marked_text(
1618 &"
1619 fn a() {
1620 c«()»;
1621 d«()»;
1622 }
1623 "
1624 .unindent(),
1625 Some(AutoindentMode::EachLine),
1626 cx,
1627 );
1628 assert_eq!(
1629 buffer.text(),
1630 "
1631 fn a() {
1632 c();
1633 d();
1634 }
1635 "
1636 .unindent()
1637 );
1638
1639 // When appending new content after these lines, the indentation is based on the
1640 // preceding lines' actual indentation.
1641 buffer.edit_via_marked_text(
1642 &"
1643 fn a() {
1644 c«
1645 .f
1646 .g()»;
1647 d«
1648 .f
1649 .g()»;
1650 }
1651 "
1652 .unindent(),
1653 Some(AutoindentMode::EachLine),
1654 cx,
1655 );
1656 assert_eq!(
1657 buffer.text(),
1658 "
1659 fn a() {
1660 c
1661 .f
1662 .g();
1663 d
1664 .f
1665 .g();
1666 }
1667 "
1668 .unindent()
1669 );
1670
1671 // Insert a newline after the open brace. It is auto-indented
1672 buffer.edit_via_marked_text(
1673 &"
1674 fn a() {«
1675 »
1676 c
1677 .f
1678 .g();
1679 d
1680 .f
1681 .g();
1682 }
1683 "
1684 .unindent(),
1685 Some(AutoindentMode::EachLine),
1686 cx,
1687 );
1688 assert_eq!(
1689 buffer.text(),
1690 "
1691 fn a() {
1692 ˇ
1693 c
1694 .f
1695 .g();
1696 d
1697 .f
1698 .g();
1699 }
1700 "
1701 .unindent()
1702 .replace("ˇ", "")
1703 );
1704
1705 // Manually outdent the line. It stays outdented.
1706 buffer.edit_via_marked_text(
1707 &"
1708 fn a() {
1709 «»
1710 c
1711 .f
1712 .g();
1713 d
1714 .f
1715 .g();
1716 }
1717 "
1718 .unindent(),
1719 Some(AutoindentMode::EachLine),
1720 cx,
1721 );
1722 assert_eq!(
1723 buffer.text(),
1724 "
1725 fn a() {
1726
1727 c
1728 .f
1729 .g();
1730 d
1731 .f
1732 .g();
1733 }
1734 "
1735 .unindent()
1736 );
1737
1738 buffer
1739 });
1740
1741 cx.new(|cx| {
1742 eprintln!("second buffer: {:?}", cx.entity_id());
1743
1744 let mut buffer = Buffer::local(
1745 "
1746 fn a() {
1747 b();
1748 |
1749 "
1750 .replace('|', "") // marker to preserve trailing whitespace
1751 .unindent(),
1752 cx,
1753 )
1754 .with_language(rust_lang(), cx);
1755
1756 // Insert a closing brace. It is outdented.
1757 buffer.edit_via_marked_text(
1758 &"
1759 fn a() {
1760 b();
1761 «}»
1762 "
1763 .unindent(),
1764 Some(AutoindentMode::EachLine),
1765 cx,
1766 );
1767 assert_eq!(
1768 buffer.text(),
1769 "
1770 fn a() {
1771 b();
1772 }
1773 "
1774 .unindent()
1775 );
1776
1777 // Manually edit the leading whitespace. The edit is preserved.
1778 buffer.edit_via_marked_text(
1779 &"
1780 fn a() {
1781 b();
1782 « »}
1783 "
1784 .unindent(),
1785 Some(AutoindentMode::EachLine),
1786 cx,
1787 );
1788 assert_eq!(
1789 buffer.text(),
1790 "
1791 fn a() {
1792 b();
1793 }
1794 "
1795 .unindent()
1796 );
1797 buffer
1798 });
1799
1800 eprintln!("DONE");
1801}
1802
1803#[gpui::test]
1804fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut App) {
1805 init_settings(cx, |_| {});
1806
1807 cx.new(|cx| {
1808 let mut buffer = Buffer::local(
1809 "
1810 fn a() {
1811 i
1812 }
1813 "
1814 .unindent(),
1815 cx,
1816 )
1817 .with_language(rust_lang(), cx);
1818
1819 // Regression test: line does not get outdented due to syntax error
1820 buffer.edit_via_marked_text(
1821 &"
1822 fn a() {
1823 i«f let Some(x) = y»
1824 }
1825 "
1826 .unindent(),
1827 Some(AutoindentMode::EachLine),
1828 cx,
1829 );
1830 assert_eq!(
1831 buffer.text(),
1832 "
1833 fn a() {
1834 if let Some(x) = y
1835 }
1836 "
1837 .unindent()
1838 );
1839
1840 buffer.edit_via_marked_text(
1841 &"
1842 fn a() {
1843 if let Some(x) = y« {»
1844 }
1845 "
1846 .unindent(),
1847 Some(AutoindentMode::EachLine),
1848 cx,
1849 );
1850 assert_eq!(
1851 buffer.text(),
1852 "
1853 fn a() {
1854 if let Some(x) = y {
1855 }
1856 "
1857 .unindent()
1858 );
1859
1860 buffer
1861 });
1862}
1863
1864#[gpui::test]
1865fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut App) {
1866 init_settings(cx, |_| {});
1867
1868 cx.new(|cx| {
1869 let mut buffer = Buffer::local(
1870 "
1871 fn a() {}
1872 "
1873 .unindent(),
1874 cx,
1875 )
1876 .with_language(rust_lang(), cx);
1877
1878 buffer.edit_via_marked_text(
1879 &"
1880 fn a(«
1881 b») {}
1882 "
1883 .unindent(),
1884 Some(AutoindentMode::EachLine),
1885 cx,
1886 );
1887 assert_eq!(
1888 buffer.text(),
1889 "
1890 fn a(
1891 b) {}
1892 "
1893 .unindent()
1894 );
1895
1896 // The indentation suggestion changed because `@end` node (a close paren)
1897 // is now at the beginning of the line.
1898 buffer.edit_via_marked_text(
1899 &"
1900 fn a(
1901 ˇ) {}
1902 "
1903 .unindent(),
1904 Some(AutoindentMode::EachLine),
1905 cx,
1906 );
1907 assert_eq!(
1908 buffer.text(),
1909 "
1910 fn a(
1911 ) {}
1912 "
1913 .unindent()
1914 );
1915
1916 buffer
1917 });
1918}
1919
1920#[gpui::test]
1921fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut App) {
1922 init_settings(cx, |_| {});
1923
1924 cx.new(|cx| {
1925 let text = "a\nb";
1926 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
1927 buffer.edit(
1928 [(0..1, "\n"), (2..3, "\n")],
1929 Some(AutoindentMode::EachLine),
1930 cx,
1931 );
1932 assert_eq!(buffer.text(), "\n\n\n");
1933 buffer
1934 });
1935}
1936
1937#[gpui::test]
1938fn test_autoindent_multi_line_insertion(cx: &mut App) {
1939 init_settings(cx, |_| {});
1940
1941 cx.new(|cx| {
1942 let text = "
1943 const a: usize = 1;
1944 fn b() {
1945 if c {
1946 let d = 2;
1947 }
1948 }
1949 "
1950 .unindent();
1951
1952 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
1953 buffer.edit(
1954 [(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
1955 Some(AutoindentMode::EachLine),
1956 cx,
1957 );
1958 assert_eq!(
1959 buffer.text(),
1960 "
1961 const a: usize = 1;
1962 fn b() {
1963 if c {
1964 e(
1965 f()
1966 );
1967 let d = 2;
1968 }
1969 }
1970 "
1971 .unindent()
1972 );
1973
1974 buffer
1975 });
1976}
1977
1978#[gpui::test]
1979fn test_autoindent_block_mode(cx: &mut App) {
1980 init_settings(cx, |_| {});
1981
1982 cx.new(|cx| {
1983 let text = r#"
1984 fn a() {
1985 b();
1986 }
1987 "#
1988 .unindent();
1989 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
1990
1991 // When this text was copied, both of the quotation marks were at the same
1992 // indent level, but the indentation of the first line was not included in
1993 // the copied text. This information is retained in the
1994 // 'original_indent_columns' vector.
1995 let original_indent_columns = vec![Some(4)];
1996 let inserted_text = r#"
1997 "
1998 c
1999 d
2000 e
2001 "
2002 "#
2003 .unindent();
2004
2005 // Insert the block at column zero. The entire block is indented
2006 // so that the first line matches the previous line's indentation.
2007 buffer.edit(
2008 [(Point::new(2, 0)..Point::new(2, 0), inserted_text.clone())],
2009 Some(AutoindentMode::Block {
2010 original_indent_columns: original_indent_columns.clone(),
2011 }),
2012 cx,
2013 );
2014 assert_eq!(
2015 buffer.text(),
2016 r#"
2017 fn a() {
2018 b();
2019 "
2020 c
2021 d
2022 e
2023 "
2024 }
2025 "#
2026 .unindent()
2027 );
2028
2029 // Grouping is disabled in tests, so we need 2 undos
2030 buffer.undo(cx); // Undo the auto-indent
2031 buffer.undo(cx); // Undo the original edit
2032
2033 // Insert the block at a deeper indent level. The entire block is outdented.
2034 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), " ")], None, cx);
2035 buffer.edit(
2036 [(Point::new(2, 8)..Point::new(2, 8), inserted_text)],
2037 Some(AutoindentMode::Block {
2038 original_indent_columns,
2039 }),
2040 cx,
2041 );
2042 assert_eq!(
2043 buffer.text(),
2044 r#"
2045 fn a() {
2046 b();
2047 "
2048 c
2049 d
2050 e
2051 "
2052 }
2053 "#
2054 .unindent()
2055 );
2056
2057 buffer
2058 });
2059}
2060
2061#[gpui::test]
2062fn test_autoindent_block_mode_with_newline(cx: &mut App) {
2063 init_settings(cx, |_| {});
2064
2065 cx.new(|cx| {
2066 let text = r#"
2067 fn a() {
2068 b();
2069 }
2070 "#
2071 .unindent();
2072 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
2073
2074 // First line contains just '\n', it's indentation is stored in "original_indent_columns"
2075 let original_indent_columns = vec![Some(4)];
2076 let inserted_text = r#"
2077
2078 c();
2079 d();
2080 e();
2081 "#
2082 .unindent();
2083 buffer.edit(
2084 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
2085 Some(AutoindentMode::Block {
2086 original_indent_columns,
2087 }),
2088 cx,
2089 );
2090
2091 // While making edit, we ignore first line as it only contains '\n'
2092 // hence second line indent is used to calculate delta
2093 assert_eq!(
2094 buffer.text(),
2095 r#"
2096 fn a() {
2097 b();
2098
2099 c();
2100 d();
2101 e();
2102 }
2103 "#
2104 .unindent()
2105 );
2106
2107 buffer
2108 });
2109}
2110
2111#[gpui::test]
2112fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut App) {
2113 init_settings(cx, |_| {});
2114
2115 cx.new(|cx| {
2116 let text = r#"
2117 fn a() {
2118 if b() {
2119
2120 }
2121 }
2122 "#
2123 .unindent();
2124 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
2125
2126 // The original indent columns are not known, so this text is
2127 // auto-indented in a block as if the first line was copied in
2128 // its entirety.
2129 let original_indent_columns = Vec::new();
2130 let inserted_text = " c\n .d()\n .e();";
2131
2132 // Insert the block at column zero. The entire block is indented
2133 // so that the first line matches the previous line's indentation.
2134 buffer.edit(
2135 [(Point::new(2, 0)..Point::new(2, 0), inserted_text)],
2136 Some(AutoindentMode::Block {
2137 original_indent_columns,
2138 }),
2139 cx,
2140 );
2141 assert_eq!(
2142 buffer.text(),
2143 r#"
2144 fn a() {
2145 if b() {
2146 c
2147 .d()
2148 .e();
2149 }
2150 }
2151 "#
2152 .unindent()
2153 );
2154
2155 // Grouping is disabled in tests, so we need 2 undos
2156 buffer.undo(cx); // Undo the auto-indent
2157 buffer.undo(cx); // Undo the original edit
2158
2159 // Insert the block at a deeper indent level. The entire block is outdented.
2160 buffer.edit(
2161 [(Point::new(2, 0)..Point::new(2, 0), " ".repeat(12))],
2162 None,
2163 cx,
2164 );
2165 buffer.edit(
2166 [(Point::new(2, 12)..Point::new(2, 12), inserted_text)],
2167 Some(AutoindentMode::Block {
2168 original_indent_columns: Vec::new(),
2169 }),
2170 cx,
2171 );
2172 assert_eq!(
2173 buffer.text(),
2174 r#"
2175 fn a() {
2176 if b() {
2177 c
2178 .d()
2179 .e();
2180 }
2181 }
2182 "#
2183 .unindent()
2184 );
2185
2186 buffer
2187 });
2188}
2189
2190#[gpui::test]
2191fn test_autoindent_block_mode_multiple_adjacent_ranges(cx: &mut App) {
2192 init_settings(cx, |_| {});
2193
2194 cx.new(|cx| {
2195 let (text, ranges_to_replace) = marked_text_ranges(
2196 &"
2197 mod numbers {
2198 «fn one() {
2199 1
2200 }
2201 »
2202 «fn two() {
2203 2
2204 }
2205 »
2206 «fn three() {
2207 3
2208 }
2209 »}
2210 "
2211 .unindent(),
2212 false,
2213 );
2214
2215 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
2216
2217 buffer.edit(
2218 [
2219 (ranges_to_replace[0].clone(), "fn one() {\n 101\n}\n"),
2220 (ranges_to_replace[1].clone(), "fn two() {\n 102\n}\n"),
2221 (ranges_to_replace[2].clone(), "fn three() {\n 103\n}\n"),
2222 ],
2223 Some(AutoindentMode::Block {
2224 original_indent_columns: vec![Some(0), Some(0), Some(0)],
2225 }),
2226 cx,
2227 );
2228
2229 assert_eq!(
2230 buffer.text(),
2231 "
2232 mod numbers {
2233 fn one() {
2234 101
2235 }
2236
2237 fn two() {
2238 102
2239 }
2240
2241 fn three() {
2242 103
2243 }
2244 }
2245 "
2246 .unindent()
2247 );
2248
2249 buffer
2250 });
2251}
2252
2253#[gpui::test]
2254fn test_autoindent_language_without_indents_query(cx: &mut App) {
2255 init_settings(cx, |_| {});
2256
2257 cx.new(|cx| {
2258 let text = "
2259 * one
2260 - a
2261 - b
2262 * two
2263 "
2264 .unindent();
2265
2266 let mut buffer = Buffer::local(text, cx).with_language(
2267 Arc::new(Language::new(
2268 LanguageConfig {
2269 name: "Markdown".into(),
2270 auto_indent_using_last_non_empty_line: false,
2271 ..Default::default()
2272 },
2273 Some(tree_sitter_json::LANGUAGE.into()),
2274 )),
2275 cx,
2276 );
2277 buffer.edit(
2278 [(Point::new(3, 0)..Point::new(3, 0), "\n")],
2279 Some(AutoindentMode::EachLine),
2280 cx,
2281 );
2282 assert_eq!(
2283 buffer.text(),
2284 "
2285 * one
2286 - a
2287 - b
2288
2289 * two
2290 "
2291 .unindent()
2292 );
2293 buffer
2294 });
2295}
2296
2297#[gpui::test]
2298fn test_autoindent_with_injected_languages(cx: &mut App) {
2299 init_settings(cx, |settings| {
2300 settings.languages.0.extend([
2301 (
2302 "HTML".into(),
2303 LanguageSettingsContent {
2304 tab_size: Some(2.try_into().unwrap()),
2305 ..Default::default()
2306 },
2307 ),
2308 (
2309 "JavaScript".into(),
2310 LanguageSettingsContent {
2311 tab_size: Some(8.try_into().unwrap()),
2312 ..Default::default()
2313 },
2314 ),
2315 ])
2316 });
2317
2318 let html_language = Arc::new(html_lang());
2319
2320 let javascript_language = Arc::new(javascript_lang());
2321
2322 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2323 language_registry.add(html_language.clone());
2324 language_registry.add(javascript_language);
2325
2326 cx.new(|cx| {
2327 let (text, ranges) = marked_text_ranges(
2328 &"
2329 <div>ˇ
2330 </div>
2331 <script>
2332 init({ˇ
2333 })
2334 </script>
2335 <span>ˇ
2336 </span>
2337 "
2338 .unindent(),
2339 false,
2340 );
2341
2342 let mut buffer = Buffer::local(text, cx);
2343 buffer.set_language_registry(language_registry);
2344 buffer.set_language(Some(html_language), cx);
2345 buffer.edit(
2346 ranges.into_iter().map(|range| (range, "\na")),
2347 Some(AutoindentMode::EachLine),
2348 cx,
2349 );
2350 assert_eq!(
2351 buffer.text(),
2352 "
2353 <div>
2354 a
2355 </div>
2356 <script>
2357 init({
2358 a
2359 })
2360 </script>
2361 <span>
2362 a
2363 </span>
2364 "
2365 .unindent()
2366 );
2367 buffer
2368 });
2369}
2370
2371#[gpui::test]
2372fn test_autoindent_query_with_outdent_captures(cx: &mut App) {
2373 init_settings(cx, |settings| {
2374 settings.defaults.tab_size = Some(2.try_into().unwrap());
2375 });
2376
2377 cx.new(|cx| {
2378 let mut buffer = Buffer::local("", cx).with_language(Arc::new(ruby_lang()), cx);
2379
2380 let text = r#"
2381 class C
2382 def a(b, c)
2383 puts b
2384 puts c
2385 rescue
2386 puts "errored"
2387 exit 1
2388 end
2389 end
2390 "#
2391 .unindent();
2392
2393 buffer.edit([(0..0, text)], Some(AutoindentMode::EachLine), cx);
2394
2395 assert_eq!(
2396 buffer.text(),
2397 r#"
2398 class C
2399 def a(b, c)
2400 puts b
2401 puts c
2402 rescue
2403 puts "errored"
2404 exit 1
2405 end
2406 end
2407 "#
2408 .unindent()
2409 );
2410
2411 buffer
2412 });
2413}
2414
2415#[gpui::test]
2416async fn test_async_autoindents_preserve_preview(cx: &mut TestAppContext) {
2417 cx.update(|cx| init_settings(cx, |_| {}));
2418
2419 // First we insert some newlines to request an auto-indent (asynchronously).
2420 // Then we request that a preview tab be preserved for the new version, even though it's edited.
2421 let buffer = cx.new(|cx| {
2422 let text = "fn a() {}";
2423 let mut buffer = Buffer::local(text, cx).with_language(rust_lang(), cx);
2424
2425 // This causes autoindent to be async.
2426 buffer.set_sync_parse_timeout(None);
2427
2428 buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
2429 buffer.refresh_preview();
2430
2431 // Synchronously, we haven't auto-indented and we're still preserving the preview.
2432 assert_eq!(buffer.text(), "fn a() {\n\n}");
2433 assert!(buffer.preserve_preview());
2434 buffer
2435 });
2436
2437 // Now let the autoindent finish
2438 cx.executor().run_until_parked();
2439
2440 // The auto-indent applied, but didn't dismiss our preview
2441 buffer.update(cx, |buffer, cx| {
2442 assert_eq!(buffer.text(), "fn a() {\n \n}");
2443 assert!(buffer.preserve_preview());
2444
2445 // Edit inserting another line. It will autoindent async.
2446 // Then refresh the preview version.
2447 buffer.edit(
2448 [(Point::new(1, 4)..Point::new(1, 4), "\n")],
2449 Some(AutoindentMode::EachLine),
2450 cx,
2451 );
2452 buffer.refresh_preview();
2453 assert_eq!(buffer.text(), "fn a() {\n \n\n}");
2454 assert!(buffer.preserve_preview());
2455
2456 // Then perform another edit, this time without refreshing the preview version.
2457 buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "x")], None, cx);
2458 // This causes the preview to not be preserved.
2459 assert!(!buffer.preserve_preview());
2460 });
2461
2462 // Let the async autoindent from the first edit finish.
2463 cx.executor().run_until_parked();
2464
2465 // The autoindent applies, but it shouldn't restore the preview status because we had an edit in the meantime.
2466 buffer.update(cx, |buffer, _| {
2467 assert_eq!(buffer.text(), "fn a() {\n x\n \n}");
2468 assert!(!buffer.preserve_preview());
2469 });
2470}
2471
2472#[gpui::test]
2473fn test_insert_empty_line(cx: &mut App) {
2474 init_settings(cx, |_| {});
2475
2476 // Insert empty line at the beginning, requesting an empty line above
2477 cx.new(|cx| {
2478 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2479 let point = buffer.insert_empty_line(Point::new(0, 0), true, false, cx);
2480 assert_eq!(buffer.text(), "\nabc\ndef\nghi");
2481 assert_eq!(point, Point::new(0, 0));
2482 buffer
2483 });
2484
2485 // Insert empty line at the beginning, requesting an empty line above and below
2486 cx.new(|cx| {
2487 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2488 let point = buffer.insert_empty_line(Point::new(0, 0), true, true, cx);
2489 assert_eq!(buffer.text(), "\n\nabc\ndef\nghi");
2490 assert_eq!(point, Point::new(0, 0));
2491 buffer
2492 });
2493
2494 // Insert empty line at the start of a line, requesting empty lines above and below
2495 cx.new(|cx| {
2496 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2497 let point = buffer.insert_empty_line(Point::new(2, 0), true, true, cx);
2498 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi");
2499 assert_eq!(point, Point::new(3, 0));
2500 buffer
2501 });
2502
2503 // Insert empty line in the middle of a line, requesting empty lines above and below
2504 cx.new(|cx| {
2505 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2506 let point = buffer.insert_empty_line(Point::new(1, 3), true, true, cx);
2507 assert_eq!(buffer.text(), "abc\ndef\n\n\n\nghi\njkl");
2508 assert_eq!(point, Point::new(3, 0));
2509 buffer
2510 });
2511
2512 // Insert empty line in the middle of a line, requesting empty line above only
2513 cx.new(|cx| {
2514 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2515 let point = buffer.insert_empty_line(Point::new(1, 3), true, false, cx);
2516 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2517 assert_eq!(point, Point::new(3, 0));
2518 buffer
2519 });
2520
2521 // Insert empty line in the middle of a line, requesting empty line below only
2522 cx.new(|cx| {
2523 let mut buffer = Buffer::local("abc\ndefghi\njkl", cx);
2524 let point = buffer.insert_empty_line(Point::new(1, 3), false, true, cx);
2525 assert_eq!(buffer.text(), "abc\ndef\n\n\nghi\njkl");
2526 assert_eq!(point, Point::new(2, 0));
2527 buffer
2528 });
2529
2530 // Insert empty line at the end, requesting empty lines above and below
2531 cx.new(|cx| {
2532 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2533 let point = buffer.insert_empty_line(Point::new(2, 3), true, true, cx);
2534 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n\n");
2535 assert_eq!(point, Point::new(4, 0));
2536 buffer
2537 });
2538
2539 // Insert empty line at the end, requesting empty line above only
2540 cx.new(|cx| {
2541 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2542 let point = buffer.insert_empty_line(Point::new(2, 3), true, false, cx);
2543 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2544 assert_eq!(point, Point::new(4, 0));
2545 buffer
2546 });
2547
2548 // Insert empty line at the end, requesting empty line below only
2549 cx.new(|cx| {
2550 let mut buffer = Buffer::local("abc\ndef\nghi", cx);
2551 let point = buffer.insert_empty_line(Point::new(2, 3), false, true, cx);
2552 assert_eq!(buffer.text(), "abc\ndef\nghi\n\n");
2553 assert_eq!(point, Point::new(3, 0));
2554 buffer
2555 });
2556}
2557
2558#[gpui::test]
2559fn test_language_scope_at_with_javascript(cx: &mut App) {
2560 init_settings(cx, |_| {});
2561
2562 cx.new(|cx| {
2563 let language = Language::new(
2564 LanguageConfig {
2565 name: "JavaScript".into(),
2566 line_comments: vec!["// ".into()],
2567 block_comment: Some(BlockCommentConfig {
2568 start: "/*".into(),
2569 end: "*/".into(),
2570 prefix: "* ".into(),
2571 tab_size: 1,
2572 }),
2573 brackets: BracketPairConfig {
2574 pairs: vec![
2575 BracketPair {
2576 start: "{".into(),
2577 end: "}".into(),
2578 close: true,
2579 surround: true,
2580 newline: false,
2581 },
2582 BracketPair {
2583 start: "'".into(),
2584 end: "'".into(),
2585 close: true,
2586 surround: true,
2587 newline: false,
2588 },
2589 ],
2590 disabled_scopes_by_bracket_ix: vec![
2591 Vec::new(), //
2592 vec!["string".into(), "comment".into()], // single quotes disabled
2593 ],
2594 },
2595 overrides: [(
2596 "element".into(),
2597 LanguageConfigOverride {
2598 line_comments: Override::Remove { remove: true },
2599 block_comment: Override::Set(BlockCommentConfig {
2600 start: "{/*".into(),
2601 prefix: "".into(),
2602 end: "*/}".into(),
2603 tab_size: 0,
2604 }),
2605 ..Default::default()
2606 },
2607 )]
2608 .into_iter()
2609 .collect(),
2610 ..Default::default()
2611 },
2612 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
2613 )
2614 .with_override_query(
2615 r#"
2616 (jsx_element) @element
2617 (string) @string
2618 (comment) @comment.inclusive
2619 [
2620 (jsx_opening_element)
2621 (jsx_closing_element)
2622 (jsx_expression)
2623 ] @default
2624 "#,
2625 )
2626 .unwrap();
2627
2628 let text = r#"
2629 a["b"] = <C d="e">
2630 <F></F>
2631 { g() }
2632 </C>; // a comment
2633 "#
2634 .unindent();
2635
2636 let buffer = Buffer::local(&text, cx).with_language(Arc::new(language), cx);
2637 let snapshot = buffer.snapshot();
2638
2639 let config = snapshot.language_scope_at(0).unwrap();
2640 assert_eq!(config.line_comment_prefixes(), &[Arc::from("// ")]);
2641 assert_eq!(
2642 config.block_comment(),
2643 Some(&BlockCommentConfig {
2644 start: "/*".into(),
2645 prefix: "* ".into(),
2646 end: "*/".into(),
2647 tab_size: 1,
2648 })
2649 );
2650
2651 // Both bracket pairs are enabled
2652 assert_eq!(
2653 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2654 &[true, true]
2655 );
2656
2657 let comment_config = snapshot
2658 .language_scope_at(text.find("comment").unwrap() + "comment".len())
2659 .unwrap();
2660 assert_eq!(
2661 comment_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2662 &[true, false]
2663 );
2664
2665 let string_config = snapshot
2666 .language_scope_at(text.find("b\"").unwrap())
2667 .unwrap();
2668 assert_eq!(string_config.line_comment_prefixes(), &[Arc::from("// ")]);
2669 assert_eq!(
2670 string_config.block_comment(),
2671 Some(&BlockCommentConfig {
2672 start: "/*".into(),
2673 prefix: "* ".into(),
2674 end: "*/".into(),
2675 tab_size: 1,
2676 })
2677 );
2678 // Second bracket pair is disabled
2679 assert_eq!(
2680 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2681 &[true, false]
2682 );
2683
2684 // In between JSX tags: use the `element` override.
2685 let element_config = snapshot
2686 .language_scope_at(text.find("<F>").unwrap())
2687 .unwrap();
2688 // TODO nested blocks after newlines are captured with all whitespaces
2689 // https://github.com/tree-sitter/tree-sitter-typescript/issues/306
2690 // assert_eq!(element_config.line_comment_prefixes(), &[]);
2691 // assert_eq!(
2692 // element_config.block_comment_delimiters(),
2693 // Some((&"{/*".into(), &"*/}".into()))
2694 // );
2695 assert_eq!(
2696 element_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2697 &[true, true]
2698 );
2699
2700 // Within a JSX tag: use the default config.
2701 let tag_config = snapshot
2702 .language_scope_at(text.find(" d=").unwrap() + 1)
2703 .unwrap();
2704 assert_eq!(tag_config.line_comment_prefixes(), &[Arc::from("// ")]);
2705 assert_eq!(
2706 tag_config.block_comment(),
2707 Some(&BlockCommentConfig {
2708 start: "/*".into(),
2709 prefix: "* ".into(),
2710 end: "*/".into(),
2711 tab_size: 1,
2712 })
2713 );
2714 assert_eq!(
2715 tag_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2716 &[true, true]
2717 );
2718
2719 // In a JSX expression: use the default config.
2720 let expression_in_element_config = snapshot
2721 .language_scope_at(text.find('{').unwrap() + 1)
2722 .unwrap();
2723 assert_eq!(
2724 expression_in_element_config.line_comment_prefixes(),
2725 &[Arc::from("// ")]
2726 );
2727 assert_eq!(
2728 expression_in_element_config.block_comment(),
2729 Some(&BlockCommentConfig {
2730 start: "/*".into(),
2731 prefix: "* ".into(),
2732 end: "*/".into(),
2733 tab_size: 1,
2734 })
2735 );
2736 assert_eq!(
2737 expression_in_element_config
2738 .brackets()
2739 .map(|e| e.1)
2740 .collect::<Vec<_>>(),
2741 &[true, true]
2742 );
2743
2744 buffer
2745 });
2746}
2747
2748#[gpui::test]
2749fn test_language_scope_at_with_rust(cx: &mut App) {
2750 init_settings(cx, |_| {});
2751
2752 cx.new(|cx| {
2753 let language = Language::new(
2754 LanguageConfig {
2755 name: "Rust".into(),
2756 brackets: BracketPairConfig {
2757 pairs: vec![
2758 BracketPair {
2759 start: "{".into(),
2760 end: "}".into(),
2761 close: true,
2762 surround: true,
2763 newline: false,
2764 },
2765 BracketPair {
2766 start: "'".into(),
2767 end: "'".into(),
2768 close: true,
2769 surround: true,
2770 newline: false,
2771 },
2772 ],
2773 disabled_scopes_by_bracket_ix: vec![
2774 Vec::new(), //
2775 vec!["string".into()],
2776 ],
2777 },
2778 ..Default::default()
2779 },
2780 Some(tree_sitter_rust::LANGUAGE.into()),
2781 )
2782 .with_override_query(
2783 r#"
2784 (string_literal) @string
2785 "#,
2786 )
2787 .unwrap();
2788
2789 let text = r#"
2790 const S: &'static str = "hello";
2791 "#
2792 .unindent();
2793
2794 let buffer = Buffer::local(text.clone(), cx).with_language(Arc::new(language), cx);
2795 let snapshot = buffer.snapshot();
2796
2797 // By default, all brackets are enabled
2798 let config = snapshot.language_scope_at(0).unwrap();
2799 assert_eq!(
2800 config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2801 &[true, true]
2802 );
2803
2804 // Within a string, the quotation brackets are disabled.
2805 let string_config = snapshot
2806 .language_scope_at(text.find("ello").unwrap())
2807 .unwrap();
2808 assert_eq!(
2809 string_config.brackets().map(|e| e.1).collect::<Vec<_>>(),
2810 &[true, false]
2811 );
2812
2813 buffer
2814 });
2815}
2816
2817#[gpui::test]
2818fn test_language_scope_at_with_combined_injections(cx: &mut App) {
2819 init_settings(cx, |_| {});
2820
2821 cx.new(|cx| {
2822 let text = r#"
2823 <ol>
2824 <% people.each do |person| %>
2825 <li>
2826 <%= person.name %>
2827 </li>
2828 <% end %>
2829 </ol>
2830 "#
2831 .unindent();
2832
2833 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2834 language_registry.add(Arc::new(ruby_lang()));
2835 language_registry.add(Arc::new(html_lang()));
2836 language_registry.add(Arc::new(erb_lang()));
2837
2838 let mut buffer = Buffer::local(text, cx);
2839 buffer.set_language_registry(language_registry.clone());
2840 let language = language_registry
2841 .language_for_name("HTML+ERB")
2842 .now_or_never()
2843 .and_then(Result::ok);
2844 buffer.set_language(language, cx);
2845
2846 let snapshot = buffer.snapshot();
2847 let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
2848 assert_eq!(html_config.line_comment_prefixes(), &[]);
2849 assert_eq!(
2850 html_config.block_comment(),
2851 Some(&BlockCommentConfig {
2852 start: "<!--".into(),
2853 end: "-->".into(),
2854 prefix: "".into(),
2855 tab_size: 0,
2856 })
2857 );
2858
2859 let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
2860 assert_eq!(ruby_config.line_comment_prefixes(), &[Arc::from("# ")]);
2861 assert_eq!(ruby_config.block_comment(), None);
2862
2863 buffer
2864 });
2865}
2866
2867#[gpui::test]
2868fn test_language_at_with_hidden_languages(cx: &mut App) {
2869 init_settings(cx, |_| {});
2870
2871 cx.new(|cx| {
2872 let text = r#"
2873 this is an *emphasized* word.
2874 "#
2875 .unindent();
2876
2877 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2878 language_registry.add(markdown_lang());
2879 language_registry.add(Arc::new(markdown_inline_lang()));
2880
2881 let mut buffer = Buffer::local(text, cx);
2882 buffer.set_language_registry(language_registry.clone());
2883 buffer.set_language(
2884 language_registry
2885 .language_for_name("Markdown")
2886 .now_or_never()
2887 .unwrap()
2888 .ok(),
2889 cx,
2890 );
2891
2892 let snapshot = buffer.snapshot();
2893
2894 for point in [Point::new(0, 4), Point::new(0, 16)] {
2895 let config = snapshot.language_scope_at(point).unwrap();
2896 assert_eq!(config.language_name(), "Markdown");
2897
2898 let language = snapshot.language_at(point).unwrap();
2899 assert_eq!(language.name().as_ref(), "Markdown");
2900 }
2901
2902 buffer
2903 });
2904}
2905
2906#[gpui::test]
2907fn test_language_at_for_markdown_code_block(cx: &mut App) {
2908 init_settings(cx, |_| {});
2909
2910 cx.new(|cx| {
2911 let text = r#"
2912 ```rs
2913 let a = 2;
2914 // let b = 3;
2915 ```
2916 "#
2917 .unindent();
2918
2919 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2920 language_registry.add(markdown_lang());
2921 language_registry.add(Arc::new(markdown_inline_lang()));
2922 language_registry.add(rust_lang());
2923
2924 let mut buffer = Buffer::local(text, cx);
2925 buffer.set_language_registry(language_registry.clone());
2926 buffer.set_language(
2927 language_registry
2928 .language_for_name("Markdown")
2929 .now_or_never()
2930 .unwrap()
2931 .ok(),
2932 cx,
2933 );
2934
2935 let snapshot = buffer.snapshot();
2936
2937 // Test points in the code line
2938 for point in [Point::new(1, 4), Point::new(1, 6)] {
2939 let config = snapshot.language_scope_at(point).unwrap();
2940 assert_eq!(config.language_name(), "Rust");
2941
2942 let language = snapshot.language_at(point).unwrap();
2943 assert_eq!(language.name().as_ref(), "Rust");
2944 }
2945
2946 // Test points in the comment line to verify it's still detected as Rust
2947 for point in [Point::new(2, 4), Point::new(2, 6)] {
2948 let config = snapshot.language_scope_at(point).unwrap();
2949 assert_eq!(config.language_name(), "Rust");
2950
2951 let language = snapshot.language_at(point).unwrap();
2952 assert_eq!(language.name().as_ref(), "Rust");
2953 }
2954
2955 buffer
2956 });
2957}
2958
2959#[gpui::test]
2960fn test_syntax_layer_at_for_combined_injections(cx: &mut App) {
2961 init_settings(cx, |_| {});
2962
2963 cx.new(|cx| {
2964 // ERB template with HTML and Ruby content
2965 let text = r#"
2966<div>Hello</div>
2967<%= link_to "Click", url %>
2968<p>World</p>
2969 "#
2970 .unindent();
2971
2972 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
2973 language_registry.add(Arc::new(erb_lang()));
2974 language_registry.add(Arc::new(html_lang()));
2975 language_registry.add(Arc::new(ruby_lang()));
2976
2977 let mut buffer = Buffer::local(text, cx);
2978 buffer.set_language_registry(language_registry.clone());
2979 let language = language_registry
2980 .language_for_name("HTML+ERB")
2981 .now_or_never()
2982 .and_then(Result::ok);
2983 buffer.set_language(language, cx);
2984
2985 let snapshot = buffer.snapshot();
2986
2987 // Test language_at for HTML content (line 0: "<div>Hello</div>")
2988 let html_point = Point::new(0, 4);
2989 let language = snapshot.language_at(html_point).unwrap();
2990 assert_eq!(
2991 language.name().as_ref(),
2992 "HTML",
2993 "Expected HTML at {:?}, got {}",
2994 html_point,
2995 language.name()
2996 );
2997
2998 // Test language_at for Ruby code (line 1: "<%= link_to ... %>")
2999 let ruby_point = Point::new(1, 6);
3000 let language = snapshot.language_at(ruby_point).unwrap();
3001 assert_eq!(
3002 language.name().as_ref(),
3003 "Ruby",
3004 "Expected Ruby at {:?}, got {}",
3005 ruby_point,
3006 language.name()
3007 );
3008
3009 // Test language_at for HTML after Ruby (line 2: "<p>World</p>")
3010 let html_after_ruby = Point::new(2, 2);
3011 let language = snapshot.language_at(html_after_ruby).unwrap();
3012 assert_eq!(
3013 language.name().as_ref(),
3014 "HTML",
3015 "Expected HTML at {:?}, got {}",
3016 html_after_ruby,
3017 language.name()
3018 );
3019
3020 buffer
3021 });
3022}
3023
3024#[gpui::test]
3025fn test_languages_at_for_combined_injections(cx: &mut App) {
3026 init_settings(cx, |_| {});
3027
3028 cx.new(|cx| {
3029 // ERB template with HTML and Ruby content
3030 let text = r#"
3031<div>Hello</div>
3032<%= yield %>
3033<p>World</p>
3034 "#
3035 .unindent();
3036
3037 let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3038 language_registry.add(Arc::new(erb_lang()));
3039 language_registry.add(Arc::new(html_lang()));
3040 language_registry.add(Arc::new(ruby_lang()));
3041
3042 let mut buffer = Buffer::local(text, cx);
3043 buffer.set_language_registry(language_registry.clone());
3044 buffer.set_language(
3045 language_registry
3046 .language_for_name("HTML+ERB")
3047 .now_or_never()
3048 .unwrap()
3049 .ok(),
3050 cx,
3051 );
3052
3053 // Test languages_at for HTML content - should NOT include Ruby
3054 let html_point = Point::new(0, 4);
3055 let languages = buffer.languages_at(html_point);
3056 let language_names: Vec<_> = languages.iter().map(|language| language.name()).collect();
3057 assert!(
3058 language_names
3059 .iter()
3060 .any(|language_name| language_name.as_ref() == "HTML"),
3061 "Expected HTML in languages at {:?}, got {:?}",
3062 html_point,
3063 language_names
3064 );
3065 assert!(
3066 !language_names
3067 .iter()
3068 .any(|language_name| language_name.as_ref() == "Ruby"),
3069 "Did not expect Ruby in languages at {:?}, got {:?}",
3070 html_point,
3071 language_names
3072 );
3073
3074 // Test languages_at for Ruby code - should NOT include HTML
3075 let ruby_point = Point::new(1, 6);
3076 let languages = buffer.languages_at(ruby_point);
3077 let language_names: Vec<_> = languages.iter().map(|language| language.name()).collect();
3078 assert!(
3079 language_names
3080 .iter()
3081 .any(|language_name| language_name.as_ref() == "Ruby"),
3082 "Expected Ruby in languages at {:?}, got {:?}",
3083 ruby_point,
3084 language_names
3085 );
3086 assert!(
3087 !language_names
3088 .iter()
3089 .any(|language_name| language_name.as_ref() == "HTML"),
3090 "Did not expect HTML in languages at {:?}, got {:?}",
3091 ruby_point,
3092 language_names
3093 );
3094
3095 buffer
3096 });
3097}
3098
3099#[gpui::test]
3100fn test_serialization(cx: &mut gpui::App) {
3101 let mut now = Instant::now();
3102
3103 let buffer1 = cx.new(|cx| {
3104 let mut buffer = Buffer::local("abc", cx);
3105 buffer.edit([(3..3, "D")], None, cx);
3106
3107 now += Duration::from_secs(1);
3108 buffer.start_transaction_at(now);
3109 buffer.edit([(4..4, "E")], None, cx);
3110 buffer.end_transaction_at(now, cx);
3111 assert_eq!(buffer.text(), "abcDE");
3112
3113 buffer.undo(cx);
3114 assert_eq!(buffer.text(), "abcD");
3115
3116 buffer.edit([(4..4, "F")], None, cx);
3117 assert_eq!(buffer.text(), "abcDF");
3118 buffer
3119 });
3120 assert_eq!(buffer1.read(cx).text(), "abcDF");
3121
3122 let state = buffer1.read(cx).to_proto(cx);
3123 let ops = cx
3124 .foreground_executor()
3125 .block_on(buffer1.read(cx).serialize_ops(None, cx));
3126 let buffer2 = cx.new(|cx| {
3127 let mut buffer =
3128 Buffer::from_proto(ReplicaId::new(1), Capability::ReadWrite, state, None).unwrap();
3129 buffer.apply_ops(
3130 ops.into_iter()
3131 .map(|op| proto::deserialize_operation(op).unwrap()),
3132 cx,
3133 );
3134 buffer
3135 });
3136 assert_eq!(buffer2.read(cx).text(), "abcDF");
3137}
3138
3139#[gpui::test]
3140fn test_branch_and_merge(cx: &mut TestAppContext) {
3141 cx.update(|cx| init_settings(cx, |_| {}));
3142
3143 let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
3144
3145 // Create a remote replica of the base buffer.
3146 let base_replica = cx.new(|cx| {
3147 Buffer::from_proto(
3148 ReplicaId::new(1),
3149 Capability::ReadWrite,
3150 base.read(cx).to_proto(cx),
3151 None,
3152 )
3153 .unwrap()
3154 });
3155 base.update(cx, |_buffer, cx| {
3156 cx.subscribe(&base_replica, |this, _, event, cx| {
3157 if let BufferEvent::Operation {
3158 operation,
3159 is_local: true,
3160 } = event
3161 {
3162 this.apply_ops([operation.clone()], cx);
3163 }
3164 })
3165 .detach();
3166 });
3167
3168 // Create a branch, which initially has the same state as the base buffer.
3169 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
3170 branch.read_with(cx, |buffer, _| {
3171 assert_eq!(buffer.text(), "one\ntwo\nthree\n");
3172 });
3173
3174 // Edits to the branch are not applied to the base.
3175 branch.update(cx, |buffer, cx| {
3176 buffer.edit(
3177 [
3178 (Point::new(1, 0)..Point::new(1, 0), "1.5\n"),
3179 (Point::new(2, 0)..Point::new(2, 5), "THREE"),
3180 ],
3181 None,
3182 cx,
3183 )
3184 });
3185 branch.read_with(cx, |buffer, cx| {
3186 assert_eq!(base.read(cx).text(), "one\ntwo\nthree\n");
3187 assert_eq!(buffer.text(), "one\n1.5\ntwo\nTHREE\n");
3188 });
3189
3190 // Convert from branch buffer ranges to the corresponding ranges in the
3191 // base buffer.
3192 branch.read_with(cx, |buffer, cx| {
3193 assert_eq!(
3194 buffer.range_to_version(4..7, &base.read(cx).version()),
3195 4..4
3196 );
3197 assert_eq!(
3198 buffer.range_to_version(2..9, &base.read(cx).version()),
3199 2..5
3200 );
3201 });
3202
3203 // Edits to the base are applied to the branch.
3204 base.update(cx, |buffer, cx| {
3205 buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "ZERO\n")], None, cx)
3206 });
3207 branch.read_with(cx, |buffer, cx| {
3208 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\nthree\n");
3209 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\nTHREE\n");
3210 });
3211
3212 // Edits to any replica of the base are applied to the branch.
3213 base_replica.update(cx, |buffer, cx| {
3214 buffer.edit([(Point::new(2, 0)..Point::new(2, 0), "2.5\n")], None, cx)
3215 });
3216 branch.read_with(cx, |buffer, cx| {
3217 assert_eq!(base.read(cx).text(), "ZERO\none\ntwo\n2.5\nthree\n");
3218 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
3219 });
3220
3221 // Merging the branch applies all of its changes to the base.
3222 branch.update(cx, |buffer, cx| {
3223 buffer.merge_into_base(Vec::new(), cx);
3224 });
3225
3226 branch.update(cx, |buffer, cx| {
3227 assert_eq!(base.read(cx).text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
3228 assert_eq!(buffer.text(), "ZERO\none\n1.5\ntwo\n2.5\nTHREE\n");
3229 });
3230}
3231
3232#[gpui::test]
3233fn test_merge_into_base(cx: &mut TestAppContext) {
3234 cx.update(|cx| init_settings(cx, |_| {}));
3235
3236 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
3237 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
3238
3239 // Make 3 edits, merge one into the base.
3240 branch.update(cx, |branch, cx| {
3241 branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
3242 branch.merge_into_base(vec![5..8], cx);
3243 });
3244
3245 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
3246 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
3247
3248 // Undo the one already-merged edit. Merge that into the base.
3249 branch.update(cx, |branch, cx| {
3250 branch.edit([(7..9, "hi")], None, cx);
3251 branch.merge_into_base(vec![5..8], cx);
3252 });
3253 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
3254
3255 // Merge an insertion into the base.
3256 branch.update(cx, |branch, cx| {
3257 branch.merge_into_base(vec![11..11], cx);
3258 });
3259
3260 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
3261 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
3262
3263 // Deleted the inserted text and merge that into the base.
3264 branch.update(cx, |branch, cx| {
3265 branch.edit([(11..14, "")], None, cx);
3266 branch.merge_into_base(vec![10..11], cx);
3267 });
3268
3269 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
3270}
3271
3272#[gpui::test]
3273fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
3274 cx.update(|cx| init_settings(cx, |_| {}));
3275
3276 let base = cx.new(|cx| Buffer::local("abcdefghijk", cx));
3277 let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
3278
3279 // Make 2 edits, merge one into the base.
3280 branch.update(cx, |branch, cx| {
3281 branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
3282 branch.merge_into_base(vec![7..7], cx);
3283 });
3284 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
3285 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
3286
3287 // Undo the merge in the base buffer.
3288 base.update(cx, |base, cx| {
3289 base.undo(cx);
3290 });
3291 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
3292 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
3293
3294 // Merge that operation into the base again.
3295 branch.update(cx, |branch, cx| {
3296 branch.merge_into_base(vec![7..7], cx);
3297 });
3298 base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
3299 branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
3300}
3301
3302#[gpui::test]
3303async fn test_preview_edits(cx: &mut TestAppContext) {
3304 cx.update(|cx| {
3305 init_settings(cx, |_| {});
3306 theme_settings::init(theme::LoadThemes::JustBase, cx);
3307 });
3308
3309 let insertion_style = HighlightStyle {
3310 background_color: Some(cx.read(|cx| cx.theme().status().created_background)),
3311 ..Default::default()
3312 };
3313 let deletion_style = HighlightStyle {
3314 background_color: Some(cx.read(|cx| cx.theme().status().deleted_background)),
3315 ..Default::default()
3316 };
3317
3318 // no edits
3319 assert_preview_edits(
3320 indoc! {"
3321 fn test_empty() -> bool {
3322 false
3323 }"
3324 },
3325 vec![],
3326 true,
3327 cx,
3328 |hl| {
3329 assert!(hl.text.is_empty());
3330 assert!(hl.highlights.is_empty());
3331 },
3332 )
3333 .await;
3334
3335 // only insertions
3336 assert_preview_edits(
3337 indoc! {"
3338 fn calculate_area(: f64) -> f64 {
3339 std::f64::consts::PI * .powi(2)
3340 }"
3341 },
3342 vec![
3343 (Point::new(0, 18)..Point::new(0, 18), "radius"),
3344 (Point::new(1, 27)..Point::new(1, 27), "radius"),
3345 ],
3346 true,
3347 cx,
3348 |hl| {
3349 assert_eq!(
3350 hl.text,
3351 indoc! {"
3352 fn calculate_area(radius: f64) -> f64 {
3353 std::f64::consts::PI * radius.powi(2)"
3354 }
3355 );
3356
3357 assert_eq!(hl.highlights.len(), 2);
3358 assert_eq!(hl.highlights[0], ((18..24), insertion_style));
3359 assert_eq!(hl.highlights[1], ((67..73), insertion_style));
3360 },
3361 )
3362 .await;
3363
3364 // insertions & deletions
3365 assert_preview_edits(
3366 indoc! {"
3367 struct Person {
3368 first_name: String,
3369 }
3370
3371 impl Person {
3372 fn first_name(&self) -> &String {
3373 &self.first_name
3374 }
3375 }"
3376 },
3377 vec![
3378 (Point::new(1, 4)..Point::new(1, 9), "last"),
3379 (Point::new(5, 7)..Point::new(5, 12), "last"),
3380 (Point::new(6, 14)..Point::new(6, 19), "last"),
3381 ],
3382 true,
3383 cx,
3384 |hl| {
3385 assert_eq!(
3386 hl.text,
3387 indoc! {"
3388 firstlast_name: String,
3389 }
3390
3391 impl Person {
3392 fn firstlast_name(&self) -> &String {
3393 &self.firstlast_name"
3394 }
3395 );
3396
3397 assert_eq!(hl.highlights.len(), 6);
3398 assert_eq!(hl.highlights[0], ((4..9), deletion_style));
3399 assert_eq!(hl.highlights[1], ((9..13), insertion_style));
3400 assert_eq!(hl.highlights[2], ((52..57), deletion_style));
3401 assert_eq!(hl.highlights[3], ((57..61), insertion_style));
3402 assert_eq!(hl.highlights[4], ((101..106), deletion_style));
3403 assert_eq!(hl.highlights[5], ((106..110), insertion_style));
3404 },
3405 )
3406 .await;
3407
3408 async fn assert_preview_edits(
3409 text: &str,
3410 edits: Vec<(Range<Point>, &str)>,
3411 include_deletions: bool,
3412 cx: &mut TestAppContext,
3413 assert_fn: impl Fn(HighlightedText),
3414 ) {
3415 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(rust_lang(), cx));
3416 let edits = buffer.read_with(cx, |buffer, _| {
3417 edits
3418 .into_iter()
3419 .map(|(range, text)| {
3420 (
3421 buffer.anchor_before(range.start)..buffer.anchor_after(range.end),
3422 text.into(),
3423 )
3424 })
3425 .collect::<Arc<[_]>>()
3426 });
3427 let edit_preview = buffer
3428 .read_with(cx, |buffer, cx| buffer.preview_edits(edits.clone(), cx))
3429 .await;
3430 let highlighted_edits = cx.read(|cx| {
3431 edit_preview.highlight_edits(&buffer.read(cx).snapshot(), &edits, include_deletions, cx)
3432 });
3433 assert_fn(highlighted_edits);
3434 }
3435}
3436
3437#[gpui::test(iterations = 100)]
3438fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
3439 let min_peers = env::var("MIN_PEERS")
3440 .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
3441 .unwrap_or(1);
3442 let max_peers = env::var("MAX_PEERS")
3443 .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
3444 .unwrap_or(5);
3445 let operations = env::var("OPERATIONS")
3446 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3447 .unwrap_or(10);
3448
3449 let base_text_len = rng.random_range(0..10);
3450 let base_text = RandomCharIter::new(&mut rng)
3451 .take(base_text_len)
3452 .collect::<String>();
3453 let mut replica_ids = Vec::new();
3454 let mut buffers = Vec::new();
3455 let network = Arc::new(Mutex::new(Network::new(rng.clone())));
3456 let base_buffer = cx.new(|cx| Buffer::local(base_text.as_str(), cx));
3457
3458 for i in 0..rng.random_range(min_peers..=max_peers) {
3459 let buffer = cx.new(|cx| {
3460 let state = base_buffer.read(cx).to_proto(cx);
3461 let ops = cx
3462 .foreground_executor()
3463 .block_on(base_buffer.read(cx).serialize_ops(None, cx));
3464 let mut buffer =
3465 Buffer::from_proto(ReplicaId::new(i as u16), Capability::ReadWrite, state, None)
3466 .unwrap();
3467 buffer.apply_ops(
3468 ops.into_iter()
3469 .map(|op| proto::deserialize_operation(op).unwrap()),
3470 cx,
3471 );
3472 buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
3473 let network = network.clone();
3474 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3475 if let BufferEvent::Operation {
3476 operation,
3477 is_local: true,
3478 } = event
3479 {
3480 network.lock().broadcast(
3481 buffer.replica_id(),
3482 vec![proto::serialize_operation(operation)],
3483 );
3484 }
3485 })
3486 .detach();
3487 buffer
3488 });
3489
3490 buffers.push(buffer);
3491 replica_ids.push(ReplicaId::new(i as u16));
3492 network.lock().add_peer(ReplicaId::new(i as u16));
3493 log::info!("Adding initial peer with replica id {:?}", replica_ids[i]);
3494 }
3495
3496 log::info!("initial text: {:?}", base_text);
3497
3498 let mut now = Instant::now();
3499 let mut mutation_count = operations;
3500 let mut next_diagnostic_id = 0;
3501 let mut active_selections = BTreeMap::default();
3502 loop {
3503 let replica_index = rng.random_range(0..replica_ids.len());
3504 let replica_id = replica_ids[replica_index];
3505 let buffer = &mut buffers[replica_index];
3506 let mut new_buffer = None;
3507 match rng.random_range(0..100) {
3508 0..=29 if mutation_count != 0 => {
3509 buffer.update(cx, |buffer, cx| {
3510 buffer.start_transaction_at(now);
3511 buffer.randomly_edit(&mut rng, 5, cx);
3512 buffer.end_transaction_at(now, cx);
3513 log::info!("buffer {:?} text: {:?}", buffer.replica_id(), buffer.text());
3514 });
3515 mutation_count -= 1;
3516 }
3517 30..=39 if mutation_count != 0 => {
3518 buffer.update(cx, |buffer, cx| {
3519 if rng.random_bool(0.2) {
3520 log::info!("peer {:?} clearing active selections", replica_id);
3521 active_selections.remove(&replica_id);
3522 buffer.remove_active_selections(cx);
3523 } else {
3524 let mut selections = Vec::new();
3525 for id in 0..rng.random_range(1..=5) {
3526 let range = buffer.random_byte_range(0, &mut rng);
3527 selections.push(Selection {
3528 id,
3529 start: buffer.anchor_before(range.start),
3530 end: buffer.anchor_before(range.end),
3531 reversed: false,
3532 goal: SelectionGoal::None,
3533 });
3534 }
3535 let selections: Arc<[Selection<Anchor>]> = selections.into();
3536 log::info!(
3537 "peer {:?} setting active selections: {:?}",
3538 replica_id,
3539 selections
3540 );
3541 active_selections.insert(replica_id, selections.clone());
3542 buffer.set_active_selections(selections, false, Default::default(), cx);
3543 }
3544 });
3545 mutation_count -= 1;
3546 }
3547 40..=49 if mutation_count != 0 && replica_id == ReplicaId::REMOTE_SERVER => {
3548 let entry_count = rng.random_range(1..=5);
3549 buffer.update(cx, |buffer, cx| {
3550 let diagnostics = DiagnosticSet::new(
3551 (0..entry_count).map(|_| {
3552 let range = buffer.random_byte_range(0, &mut rng);
3553 let range = range.to_point_utf16(buffer);
3554 let range = range.start..range.end;
3555 DiagnosticEntry {
3556 range,
3557 diagnostic: Diagnostic {
3558 message: post_inc(&mut next_diagnostic_id).to_string(),
3559 ..Default::default()
3560 },
3561 }
3562 }),
3563 buffer,
3564 );
3565 log::info!(
3566 "peer {:?} setting diagnostics: {:?}",
3567 replica_id,
3568 diagnostics
3569 );
3570 buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
3571 });
3572 mutation_count -= 1;
3573 }
3574 50..=59 if replica_ids.len() < max_peers => {
3575 let old_buffer_state = buffer.read(cx).to_proto(cx);
3576 let old_buffer_ops = cx
3577 .foreground_executor()
3578 .block_on(buffer.read(cx).serialize_ops(None, cx));
3579 let new_replica_id = (0..=replica_ids.len() as u16)
3580 .map(ReplicaId::new)
3581 .filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
3582 .choose(&mut rng)
3583 .unwrap();
3584 log::info!(
3585 "Adding new replica {:?} (replicating from {:?})",
3586 new_replica_id,
3587 replica_id
3588 );
3589 new_buffer = Some(cx.new(|cx| {
3590 let mut new_buffer = Buffer::from_proto(
3591 new_replica_id,
3592 Capability::ReadWrite,
3593 old_buffer_state,
3594 None,
3595 )
3596 .unwrap();
3597 new_buffer.apply_ops(
3598 old_buffer_ops
3599 .into_iter()
3600 .map(|op| deserialize_operation(op).unwrap()),
3601 cx,
3602 );
3603 log::info!(
3604 "New replica {:?} text: {:?}",
3605 new_buffer.replica_id(),
3606 new_buffer.text()
3607 );
3608 new_buffer.set_group_interval(Duration::from_millis(rng.random_range(0..=200)));
3609 let network = network.clone();
3610 cx.subscribe(&cx.entity(), move |buffer, _, event, _| {
3611 if let BufferEvent::Operation {
3612 operation,
3613 is_local: true,
3614 } = event
3615 {
3616 network.lock().broadcast(
3617 buffer.replica_id(),
3618 vec![proto::serialize_operation(operation)],
3619 );
3620 }
3621 })
3622 .detach();
3623 new_buffer
3624 }));
3625 network.lock().replicate(replica_id, new_replica_id);
3626
3627 if new_replica_id.as_u16() as usize == replica_ids.len() {
3628 replica_ids.push(new_replica_id);
3629 } else {
3630 let new_buffer = new_buffer.take().unwrap();
3631 while network.lock().has_unreceived(new_replica_id) {
3632 let ops = network
3633 .lock()
3634 .receive(new_replica_id)
3635 .into_iter()
3636 .map(|op| proto::deserialize_operation(op).unwrap());
3637 if ops.len() > 0 {
3638 log::info!(
3639 "peer {:?} (version: {:?}) applying {} ops from the network. {:?}",
3640 new_replica_id,
3641 buffer.read(cx).version(),
3642 ops.len(),
3643 ops
3644 );
3645 new_buffer.update(cx, |new_buffer, cx| {
3646 new_buffer.apply_ops(ops, cx);
3647 });
3648 }
3649 }
3650 buffers[new_replica_id.as_u16() as usize] = new_buffer;
3651 }
3652 }
3653 60..=69 if mutation_count != 0 => {
3654 buffer.update(cx, |buffer, cx| {
3655 buffer.randomly_undo_redo(&mut rng, cx);
3656 log::info!("buffer {:?} text: {:?}", buffer.replica_id(), buffer.text());
3657 });
3658 mutation_count -= 1;
3659 }
3660 _ if network.lock().has_unreceived(replica_id) => {
3661 let ops = network
3662 .lock()
3663 .receive(replica_id)
3664 .into_iter()
3665 .map(|op| proto::deserialize_operation(op).unwrap());
3666 if ops.len() > 0 {
3667 log::info!(
3668 "peer {:?} (version: {:?}) applying {} ops from the network. {:?}",
3669 replica_id,
3670 buffer.read(cx).version(),
3671 ops.len(),
3672 ops
3673 );
3674 buffer.update(cx, |buffer, cx| buffer.apply_ops(ops, cx));
3675 }
3676 }
3677 _ => {}
3678 }
3679
3680 now += Duration::from_millis(rng.random_range(0..=200));
3681 buffers.extend(new_buffer);
3682
3683 for buffer in &buffers {
3684 buffer.read(cx).check_invariants();
3685 }
3686
3687 if mutation_count == 0 && network.lock().is_idle() {
3688 break;
3689 }
3690 }
3691
3692 let first_buffer = buffers[0].read(cx).snapshot();
3693 for buffer in &buffers[1..] {
3694 let buffer = buffer.read(cx).snapshot();
3695 assert_eq!(
3696 buffer.version(),
3697 first_buffer.version(),
3698 "Replica {:?} version != Replica 0 version",
3699 buffer.replica_id()
3700 );
3701 assert_eq!(
3702 buffer.text(),
3703 first_buffer.text(),
3704 "Replica {:?} text != Replica 0 text",
3705 buffer.replica_id()
3706 );
3707 assert_eq!(
3708 buffer
3709 .diagnostics_in_range::<_, usize>(0..buffer.len(), false)
3710 .collect::<Vec<_>>(),
3711 first_buffer
3712 .diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
3713 .collect::<Vec<_>>(),
3714 "Replica {:?} diagnostics != Replica 0 diagnostics",
3715 buffer.replica_id()
3716 );
3717 }
3718
3719 for buffer in &buffers {
3720 let buffer = buffer.read(cx).snapshot();
3721 let actual_remote_selections = buffer
3722 .selections_in_range(Anchor::min_max_range_for_buffer(buffer.remote_id()), false)
3723 .map(|(replica_id, _, _, selections)| (replica_id, selections.collect::<Vec<_>>()))
3724 .collect::<Vec<_>>();
3725 let expected_remote_selections = active_selections
3726 .iter()
3727 .filter(|(replica_id, _)| **replica_id != buffer.replica_id())
3728 .map(|(replica_id, selections)| (*replica_id, selections.iter().collect::<Vec<_>>()))
3729 .collect::<Vec<_>>();
3730 assert_eq!(
3731 actual_remote_selections,
3732 expected_remote_selections,
3733 "Replica {:?} remote selections != expected selections",
3734 buffer.replica_id()
3735 );
3736 }
3737}
3738
3739#[test]
3740fn test_contiguous_ranges() {
3741 assert_eq!(
3742 contiguous_ranges([1, 2, 3, 5, 6, 9, 10, 11, 12].into_iter(), 100).collect::<Vec<_>>(),
3743 &[1..4, 5..7, 9..13]
3744 );
3745
3746 // Respects the `max_len` parameter
3747 assert_eq!(
3748 contiguous_ranges(
3749 [2, 3, 4, 5, 6, 7, 8, 9, 23, 24, 25, 26, 30, 31].into_iter(),
3750 3
3751 )
3752 .collect::<Vec<_>>(),
3753 &[2..5, 5..8, 8..10, 23..26, 26..27, 30..32],
3754 );
3755}
3756
3757#[gpui::test]
3758fn test_insertion_after_deletion(cx: &mut gpui::App) {
3759 let buffer = cx.new(|cx| Buffer::local("struct Foo {\n \n}", cx));
3760 buffer.update(cx, |buffer, cx| {
3761 let mut anchor = buffer.anchor_after(17);
3762 buffer.edit([(12..18, "")], None, cx);
3763 let snapshot = buffer.snapshot();
3764 assert_eq!(snapshot.text(), "struct Foo {}");
3765 if !anchor.is_valid(&snapshot) {
3766 anchor = snapshot.anchor_after(snapshot.offset_for_anchor(&anchor));
3767 }
3768 buffer.edit([(anchor..anchor, "\n")], None, cx);
3769 buffer.edit([(anchor..anchor, "field1:")], None, cx);
3770 buffer.edit([(anchor..anchor, " i32,")], None, cx);
3771 let snapshot = buffer.snapshot();
3772 assert_eq!(snapshot.text(), "struct Foo {\nfield1: i32,}");
3773 })
3774}
3775
3776#[gpui::test(iterations = 500)]
3777fn test_trailing_whitespace_ranges(mut rng: StdRng) {
3778 // Generate a random multi-line string containing
3779 // some lines with trailing whitespace.
3780 let mut text = String::new();
3781 for _ in 0..rng.random_range(0..16) {
3782 for _ in 0..rng.random_range(0..36) {
3783 text.push(match rng.random_range(0..10) {
3784 0..=1 => ' ',
3785 3 => '\t',
3786 _ => rng.random_range('a'..='z'),
3787 });
3788 }
3789 text.push('\n');
3790 }
3791
3792 match rng.random_range(0..10) {
3793 // sometimes remove the last newline
3794 0..=1 => drop(text.pop()), //
3795
3796 // sometimes add extra newlines
3797 2..=3 => text.push_str(&"\n".repeat(rng.random_range(1..5))),
3798 _ => {}
3799 }
3800
3801 let rope = Rope::from(text.as_str());
3802 let actual_ranges = trailing_whitespace_ranges(&rope);
3803 let expected_ranges = TRAILING_WHITESPACE_REGEX
3804 .find_iter(&text)
3805 .map(|m| m.range())
3806 .collect::<Vec<_>>();
3807 assert_eq!(
3808 actual_ranges,
3809 expected_ranges,
3810 "wrong ranges for text lines:\n{:?}",
3811 text.split('\n').collect::<Vec<_>>()
3812 );
3813}
3814
3815#[gpui::test]
3816fn test_words_in_range(cx: &mut gpui::App) {
3817 init_settings(cx, |_| {});
3818
3819 // The first line are words excluded from the results with heuristics, we do not expect them in the test assertions.
3820 let contents = r#"
38210_isize 123 3.4 4
3822let word=öäpple.bar你 Öäpple word2-öÄpPlE-Pizza-word ÖÄPPLE word
3823 "#;
3824
3825 let buffer = cx.new(|cx| {
3826 let buffer = Buffer::local(contents, cx).with_language(rust_lang(), cx);
3827 assert_eq!(buffer.text(), contents);
3828 buffer.check_invariants();
3829 buffer
3830 });
3831
3832 buffer.update(cx, |buffer, _| {
3833 let snapshot = buffer.snapshot();
3834 assert_eq!(
3835 BTreeSet::from_iter(["Pizza".to_string()]),
3836 snapshot
3837 .words_in_range(WordsQuery {
3838 fuzzy_contents: Some("piz"),
3839 skip_digits: true,
3840 range: 0..snapshot.len(),
3841 })
3842 .into_keys()
3843 .collect::<BTreeSet<_>>()
3844 );
3845 assert_eq!(
3846 BTreeSet::from_iter([
3847 "öäpple".to_string(),
3848 "Öäpple".to_string(),
3849 "öÄpPlE".to_string(),
3850 "ÖÄPPLE".to_string(),
3851 ]),
3852 snapshot
3853 .words_in_range(WordsQuery {
3854 fuzzy_contents: Some("öp"),
3855 skip_digits: true,
3856 range: 0..snapshot.len(),
3857 })
3858 .into_keys()
3859 .collect::<BTreeSet<_>>()
3860 );
3861 assert_eq!(
3862 BTreeSet::from_iter([
3863 "öÄpPlE".to_string(),
3864 "Öäpple".to_string(),
3865 "ÖÄPPLE".to_string(),
3866 "öäpple".to_string(),
3867 ]),
3868 snapshot
3869 .words_in_range(WordsQuery {
3870 fuzzy_contents: Some("öÄ"),
3871 skip_digits: true,
3872 range: 0..snapshot.len(),
3873 })
3874 .into_keys()
3875 .collect::<BTreeSet<_>>()
3876 );
3877 assert_eq!(
3878 BTreeSet::default(),
3879 snapshot
3880 .words_in_range(WordsQuery {
3881 fuzzy_contents: Some("öÄ好"),
3882 skip_digits: true,
3883 range: 0..snapshot.len(),
3884 })
3885 .into_keys()
3886 .collect::<BTreeSet<_>>()
3887 );
3888 assert_eq!(
3889 BTreeSet::from_iter(["bar你".to_string(),]),
3890 snapshot
3891 .words_in_range(WordsQuery {
3892 fuzzy_contents: Some("你"),
3893 skip_digits: true,
3894 range: 0..snapshot.len(),
3895 })
3896 .into_keys()
3897 .collect::<BTreeSet<_>>()
3898 );
3899 assert_eq!(
3900 BTreeSet::default(),
3901 snapshot
3902 .words_in_range(WordsQuery {
3903 fuzzy_contents: Some(""),
3904 skip_digits: true,
3905 range: 0..snapshot.len(),
3906 },)
3907 .into_keys()
3908 .collect::<BTreeSet<_>>()
3909 );
3910 assert_eq!(
3911 BTreeSet::from_iter([
3912 "bar你".to_string(),
3913 "öÄpPlE".to_string(),
3914 "Öäpple".to_string(),
3915 "ÖÄPPLE".to_string(),
3916 "öäpple".to_string(),
3917 "let".to_string(),
3918 "Pizza".to_string(),
3919 "word".to_string(),
3920 "word2".to_string(),
3921 ]),
3922 snapshot
3923 .words_in_range(WordsQuery {
3924 fuzzy_contents: None,
3925 skip_digits: true,
3926 range: 0..snapshot.len(),
3927 })
3928 .into_keys()
3929 .collect::<BTreeSet<_>>()
3930 );
3931 assert_eq!(
3932 BTreeSet::from_iter([
3933 "0_isize".to_string(),
3934 "123".to_string(),
3935 "3".to_string(),
3936 "4".to_string(),
3937 "bar你".to_string(),
3938 "öÄpPlE".to_string(),
3939 "Öäpple".to_string(),
3940 "ÖÄPPLE".to_string(),
3941 "öäpple".to_string(),
3942 "let".to_string(),
3943 "Pizza".to_string(),
3944 "word".to_string(),
3945 "word2".to_string(),
3946 ]),
3947 snapshot
3948 .words_in_range(WordsQuery {
3949 fuzzy_contents: None,
3950 skip_digits: false,
3951 range: 0..snapshot.len(),
3952 })
3953 .into_keys()
3954 .collect::<BTreeSet<_>>()
3955 );
3956 });
3957}
3958
3959fn ruby_lang() -> Language {
3960 Language::new(
3961 LanguageConfig {
3962 name: "Ruby".into(),
3963 matcher: LanguageMatcher {
3964 path_suffixes: vec!["rb".to_string()],
3965 ..Default::default()
3966 },
3967 line_comments: vec!["# ".into()],
3968 ..Default::default()
3969 },
3970 Some(tree_sitter_ruby::LANGUAGE.into()),
3971 )
3972 .with_indents_query(
3973 r#"
3974 (class "end" @end) @indent
3975 (method "end" @end) @indent
3976 (rescue) @outdent
3977 (then) @indent
3978 "#,
3979 )
3980 .unwrap()
3981}
3982
3983fn html_lang() -> Language {
3984 Language::new(
3985 LanguageConfig {
3986 name: LanguageName::new_static("HTML"),
3987 block_comment: Some(BlockCommentConfig {
3988 start: "<!--".into(),
3989 prefix: "".into(),
3990 end: "-->".into(),
3991 tab_size: 0,
3992 }),
3993 ..Default::default()
3994 },
3995 Some(tree_sitter_html::LANGUAGE.into()),
3996 )
3997 .with_indents_query(
3998 "
3999 (element
4000 (start_tag) @start
4001 (end_tag)? @end) @indent
4002 ",
4003 )
4004 .unwrap()
4005 .with_injection_query(
4006 r#"
4007 (script_element
4008 (raw_text) @injection.content
4009 (#set! injection.language "javascript"))
4010 "#,
4011 )
4012 .unwrap()
4013}
4014
4015fn erb_lang() -> Language {
4016 Language::new(
4017 LanguageConfig {
4018 name: "HTML+ERB".into(),
4019 matcher: LanguageMatcher {
4020 path_suffixes: vec!["erb".to_string()],
4021 ..Default::default()
4022 },
4023 block_comment: Some(BlockCommentConfig {
4024 start: "<%#".into(),
4025 prefix: "".into(),
4026 end: "%>".into(),
4027 tab_size: 0,
4028 }),
4029 ..Default::default()
4030 },
4031 Some(tree_sitter_embedded_template::LANGUAGE.into()),
4032 )
4033 .with_injection_query(
4034 r#"
4035 (
4036 (code) @content
4037 (#set! "language" "ruby")
4038 (#set! "combined")
4039 )
4040
4041 (
4042 (content) @content
4043 (#set! "language" "html")
4044 (#set! "combined")
4045 )
4046 "#,
4047 )
4048 .unwrap()
4049}
4050
4051fn json_lang() -> Language {
4052 Language::new(
4053 LanguageConfig {
4054 name: "Json".into(),
4055 matcher: LanguageMatcher {
4056 path_suffixes: vec!["js".to_string()],
4057 ..Default::default()
4058 },
4059 ..Default::default()
4060 },
4061 Some(tree_sitter_json::LANGUAGE.into()),
4062 )
4063}
4064
4065fn javascript_lang() -> Language {
4066 Language::new(
4067 LanguageConfig {
4068 name: "JavaScript".into(),
4069 ..Default::default()
4070 },
4071 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
4072 )
4073 .with_brackets_query(
4074 r#"
4075 ("{" @open "}" @close)
4076 ("(" @open ")" @close)
4077 "#,
4078 )
4079 .unwrap()
4080 .with_indents_query(
4081 r#"
4082 (object "}" @end) @indent
4083 "#,
4084 )
4085 .unwrap()
4086}
4087
4088pub fn markdown_inline_lang() -> Language {
4089 Language::new(
4090 LanguageConfig {
4091 name: "Markdown-Inline".into(),
4092 hidden: true,
4093 ..LanguageConfig::default()
4094 },
4095 Some(tree_sitter_md::INLINE_LANGUAGE.into()),
4096 )
4097 .with_highlights_query("(emphasis) @emphasis")
4098 .unwrap()
4099}
4100
4101fn get_tree_sexp(buffer: &Entity<Buffer>, cx: &mut gpui::TestAppContext) -> String {
4102 buffer.update(cx, |buffer, _| {
4103 let snapshot = buffer.snapshot();
4104 let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
4105 layers[0].node().to_sexp()
4106 })
4107}
4108
4109// Assert that the enclosing bracket ranges around the selection match the pairs indicated by the marked text in `range_markers`
4110#[track_caller]
4111fn assert_bracket_pairs(
4112 selection_text: &'static str,
4113 bracket_pair_texts: Vec<&'static str>,
4114 language: Arc<Language>,
4115 cx: &mut App,
4116) {
4117 let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
4118 let buffer = cx.new(|cx| Buffer::local(expected_text.clone(), cx).with_language(language, cx));
4119 let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
4120
4121 let selection_range = selection_ranges[0].clone();
4122
4123 let bracket_pairs = bracket_pair_texts
4124 .into_iter()
4125 .map(|pair_text| {
4126 let (bracket_text, ranges) = marked_text_ranges(pair_text, false);
4127 assert_eq!(bracket_text, expected_text);
4128 (ranges[0].clone(), ranges[1].clone())
4129 })
4130 .collect::<Vec<_>>();
4131
4132 assert_set_eq!(
4133 buffer
4134 .bracket_ranges(selection_range)
4135 .map(|pair| (pair.open_range, pair.close_range))
4136 .collect::<Vec<_>>(),
4137 bracket_pairs
4138 );
4139}
4140
4141fn init_settings(cx: &mut App, f: fn(&mut AllLanguageSettingsContent)) {
4142 let settings_store = SettingsStore::test(cx);
4143 cx.set_global(settings_store);
4144 cx.update_global::<SettingsStore, _>(|settings, cx| {
4145 settings.update_user_settings(cx, |content| f(&mut content.project.all_languages));
4146 });
4147}
4148
4149#[gpui::test(iterations = 100)]
4150fn test_random_chunk_bitmaps(cx: &mut App, mut rng: StdRng) {
4151 use util::RandomCharIter;
4152
4153 // Generate random text
4154 let len = rng.random_range(0..10000);
4155 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
4156
4157 let buffer = cx.new(|cx| Buffer::local(text, cx));
4158 let snapshot = buffer.read(cx).snapshot();
4159
4160 // Get all chunks and verify their bitmaps
4161 let chunks = snapshot.chunks(
4162 0..snapshot.len(),
4163 LanguageAwareStyling {
4164 tree_sitter: false,
4165 diagnostics: false,
4166 },
4167 );
4168
4169 for chunk in chunks {
4170 let chunk_text = chunk.text;
4171 let chars_bitmap = chunk.chars;
4172 let tabs_bitmap = chunk.tabs;
4173
4174 // Check empty chunks have empty bitmaps
4175 if chunk_text.is_empty() {
4176 assert_eq!(
4177 chars_bitmap, 0,
4178 "Empty chunk should have empty chars bitmap"
4179 );
4180 assert_eq!(tabs_bitmap, 0, "Empty chunk should have empty tabs bitmap");
4181 continue;
4182 }
4183
4184 // Verify that chunk text doesn't exceed 128 bytes
4185 assert!(
4186 chunk_text.len() <= 128,
4187 "Chunk text length {} exceeds 128 bytes",
4188 chunk_text.len()
4189 );
4190
4191 // Verify chars bitmap
4192 let char_indices = chunk_text
4193 .char_indices()
4194 .map(|(i, _)| i)
4195 .collect::<Vec<_>>();
4196
4197 for byte_idx in 0..chunk_text.len() {
4198 let should_have_bit = char_indices.contains(&byte_idx);
4199 let has_bit = chars_bitmap & (1 << byte_idx) != 0;
4200
4201 if has_bit != should_have_bit {
4202 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4203 eprintln!("Char indices: {:?}", char_indices);
4204 eprintln!("Chars bitmap: {:#b}", chars_bitmap);
4205 }
4206
4207 assert_eq!(
4208 has_bit, should_have_bit,
4209 "Chars bitmap mismatch at byte index {} in chunk {:?}. Expected bit: {}, Got bit: {}",
4210 byte_idx, chunk_text, should_have_bit, has_bit
4211 );
4212 }
4213
4214 // Verify tabs bitmap
4215 for (byte_idx, byte) in chunk_text.bytes().enumerate() {
4216 let is_tab = byte == b'\t';
4217 let has_bit = tabs_bitmap & (1 << byte_idx) != 0;
4218
4219 if has_bit != is_tab {
4220 eprintln!("Chunk text bytes: {:?}", chunk_text.as_bytes());
4221 eprintln!("Tabs bitmap: {:#b}", tabs_bitmap);
4222 assert_eq!(
4223 has_bit, is_tab,
4224 "Tabs bitmap mismatch at byte index {} in chunk {:?}. Byte: {:?}, Expected bit: {}, Got bit: {}",
4225 byte_idx, chunk_text, byte as char, is_tab, has_bit
4226 );
4227 }
4228 }
4229 }
4230}