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