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