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