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