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