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