1use super::*;
2use crate::assemble_excerpts::assemble_excerpt_ranges;
3use futures::channel::mpsc::UnboundedReceiver;
4use gpui::TestAppContext;
5use indoc::indoc;
6use language::{Point, ToPoint as _, rust_lang};
7use lsp::FakeLanguageServer;
8use project::{FakeFs, LocationLink, Project};
9use serde_json::json;
10use settings::SettingsStore;
11use std::fmt::Write as _;
12use util::{path, test::marked_text_ranges};
13
14#[gpui::test]
15async fn test_edit_prediction_context(cx: &mut TestAppContext) {
16 init_test(cx);
17 let fs = FakeFs::new(cx.executor());
18 fs.insert_tree(path!("/root"), test_project_1()).await;
19
20 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
21 let mut servers = setup_fake_lsp(&project, cx);
22
23 let (buffer, _handle) = project
24 .update(cx, |project, cx| {
25 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
26 })
27 .await
28 .unwrap();
29
30 let _server = servers.next().await.unwrap();
31 cx.run_until_parked();
32
33 let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
34 related_excerpt_store.update(cx, |store, cx| {
35 let position = {
36 let buffer = buffer.read(cx);
37 let offset = buffer.text().find("todo").unwrap();
38 buffer.anchor_before(offset)
39 };
40
41 store.set_identifier_line_count(0);
42 store.refresh(buffer.clone(), position, cx);
43 });
44
45 cx.executor().advance_clock(DEBOUNCE_DURATION);
46 related_excerpt_store.update(cx, |store, cx| {
47 let excerpts = store.related_files(cx);
48 assert_related_files(
49 &excerpts,
50 &[
51 (
52 "root/src/person.rs",
53 &[
54 indoc! {"
55 pub struct Person {
56 first_name: String,
57 last_name: String,
58 email: String,
59 age: u32,
60 }
61
62 impl Person {
63 pub fn get_first_name(&self) -> &str {
64 &self.first_name
65 }"},
66 "}",
67 ],
68 ),
69 (
70 "root/src/company.rs",
71 &[indoc! {"
72 pub struct Company {
73 owner: Arc<Person>,
74 address: Address,
75 }"}],
76 ),
77 (
78 "root/src/main.rs",
79 &[
80 indoc! {"
81 pub struct Session {
82 company: Arc<Company>,
83 }
84
85 impl Session {
86 pub fn set_company(&mut self, company: Arc<Company>) {"},
87 indoc! {"
88 }
89 }"},
90 ],
91 ),
92 ],
93 );
94 });
95
96 let company_buffer = related_excerpt_store.update(cx, |store, cx| {
97 store
98 .related_files_with_buffers(cx)
99 .find(|(file, _)| file.path.to_str() == Some("root/src/company.rs"))
100 .map(|(_, buffer)| buffer)
101 .expect("company.rs buffer not found")
102 });
103
104 company_buffer.update(cx, |buffer, cx| {
105 let text = buffer.text();
106 let insert_pos = text.find("address: Address,").unwrap() + "address: Address,".len();
107 buffer.edit([(insert_pos..insert_pos, "\n name: String,")], None, cx);
108 });
109
110 related_excerpt_store.update(cx, |store, cx| {
111 let excerpts = store.related_files(cx);
112 assert_related_files(
113 &excerpts,
114 &[
115 (
116 "root/src/person.rs",
117 &[
118 indoc! {"
119 pub struct Person {
120 first_name: String,
121 last_name: String,
122 email: String,
123 age: u32,
124 }
125
126 impl Person {
127 pub fn get_first_name(&self) -> &str {
128 &self.first_name
129 }"},
130 "}",
131 ],
132 ),
133 (
134 "root/src/company.rs",
135 &[indoc! {"
136 pub struct Company {
137 owner: Arc<Person>,
138 address: Address,
139 name: String,
140 }"}],
141 ),
142 (
143 "root/src/main.rs",
144 &[
145 indoc! {"
146 pub struct Session {
147 company: Arc<Company>,
148 }
149
150 impl Session {
151 pub fn set_company(&mut self, company: Arc<Company>) {"},
152 indoc! {"
153 }
154 }"},
155 ],
156 ),
157 ],
158 );
159 });
160}
161
162#[gpui::test]
163fn test_assemble_excerpts(cx: &mut TestAppContext) {
164 let table = [
165 (
166 indoc! {r#"
167 struct User {
168 first_name: String,
169 «last_name»: String,
170 age: u32,
171 email: String,
172 create_at: Instant,
173 }
174
175 impl User {
176 pub fn first_name(&self) -> String {
177 self.first_name.clone()
178 }
179
180 pub fn full_name(&self) -> String {
181 « format!("{} {}", self.first_name, self.last_name)
182 » }
183 }
184 "#},
185 indoc! {r#"
186 struct User {
187 first_name: String,
188 last_name: String,
189 …
190 }
191
192 impl User {
193 …
194 pub fn full_name(&self) -> String {
195 format!("{} {}", self.first_name, self.last_name)
196 }
197 }
198 "#},
199 ),
200 (
201 indoc! {r#"
202 struct «User» {
203 first_name: String,
204 last_name: String,
205 age: u32,
206 }
207
208 impl User {
209 // methods
210 }
211 "#},
212 indoc! {r#"
213 struct User {
214 first_name: String,
215 last_name: String,
216 age: u32,
217 }
218 …
219 "#},
220 ),
221 (
222 indoc! {r#"
223 trait «FooProvider» {
224 const NAME: &'static str;
225
226 fn provide_foo(&self, id: usize) -> Foo;
227
228 fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
229 ids.iter()
230 .map(|id| self.provide_foo(*id))
231 .collect()
232 }
233
234 fn sync(&self);
235 }
236 "#
237 },
238 indoc! {r#"
239 trait FooProvider {
240 const NAME: &'static str;
241
242 fn provide_foo(&self, id: usize) -> Foo;
243
244 fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
245 …
246 }
247
248 fn sync(&self);
249 }
250 "#},
251 ),
252 (
253 indoc! {r#"
254 trait «Something» {
255 fn method1(&self, id: usize) -> Foo;
256
257 fn method2(&self, ids: &[usize]) -> Vec<Foo> {
258 struct Helper1 {
259 field1: usize,
260 }
261
262 struct Helper2 {
263 field2: usize,
264 }
265
266 struct Helper3 {
267 filed2: usize,
268 }
269 }
270
271 fn sync(&self);
272 }
273 "#
274 },
275 indoc! {r#"
276 trait Something {
277 fn method1(&self, id: usize) -> Foo;
278
279 fn method2(&self, ids: &[usize]) -> Vec<Foo> {
280 …
281 }
282
283 fn sync(&self);
284 }
285 "#},
286 ),
287 ];
288
289 for (input, expected_output) in table {
290 let (input, ranges) = marked_text_ranges(&input, false);
291 let buffer = cx.new(|cx| Buffer::local(input, cx).with_language(rust_lang(), cx));
292 buffer.read_with(cx, |buffer, _cx| {
293 let ranges: Vec<(Range<Point>, usize)> = ranges
294 .into_iter()
295 .map(|range| (range.to_point(&buffer), 0))
296 .collect();
297
298 let assembled = assemble_excerpt_ranges(&buffer.snapshot(), ranges);
299 let excerpts: Vec<RelatedExcerpt> = assembled
300 .into_iter()
301 .map(|(row_range, order)| {
302 let start = Point::new(row_range.start, 0);
303 let end = Point::new(row_range.end, buffer.line_len(row_range.end));
304 RelatedExcerpt {
305 row_range,
306 text: buffer.text_for_range(start..end).collect::<String>().into(),
307 order,
308 }
309 })
310 .collect();
311
312 let output = format_excerpts(buffer, &excerpts);
313 assert_eq!(output, expected_output);
314 });
315 }
316}
317
318#[gpui::test]
319async fn test_fake_definition_lsp(cx: &mut TestAppContext) {
320 init_test(cx);
321
322 let fs = FakeFs::new(cx.executor());
323 fs.insert_tree(path!("/root"), test_project_1()).await;
324
325 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
326 let mut servers = setup_fake_lsp(&project, cx);
327
328 let (buffer, _handle) = project
329 .update(cx, |project, cx| {
330 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
331 })
332 .await
333 .unwrap();
334
335 let _server = servers.next().await.unwrap();
336 cx.run_until_parked();
337
338 let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
339
340 let definitions = project
341 .update(cx, |project, cx| {
342 let offset = buffer_text.find("Address {").unwrap();
343 project.definitions(&buffer, offset, cx)
344 })
345 .await
346 .unwrap()
347 .unwrap();
348 assert_definitions(&definitions, &["pub struct Address {"], cx);
349
350 let definitions = project
351 .update(cx, |project, cx| {
352 let offset = buffer_text.find("State::CA").unwrap();
353 project.definitions(&buffer, offset, cx)
354 })
355 .await
356 .unwrap()
357 .unwrap();
358 assert_definitions(&definitions, &["pub enum State {"], cx);
359
360 let definitions = project
361 .update(cx, |project, cx| {
362 let offset = buffer_text.find("to_string()").unwrap();
363 project.definitions(&buffer, offset, cx)
364 })
365 .await
366 .unwrap()
367 .unwrap();
368 assert_definitions(&definitions, &["pub fn to_string(&self) -> String {"], cx);
369}
370
371#[gpui::test]
372async fn test_fake_type_definition_lsp(cx: &mut TestAppContext) {
373 init_test(cx);
374
375 let fs = FakeFs::new(cx.executor());
376 fs.insert_tree(path!("/root"), test_project_1()).await;
377
378 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
379 let mut servers = setup_fake_lsp(&project, cx);
380
381 let (buffer, _handle) = project
382 .update(cx, |project, cx| {
383 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
384 })
385 .await
386 .unwrap();
387
388 let _server = servers.next().await.unwrap();
389 cx.run_until_parked();
390
391 let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
392
393 // Type definition on a type name returns its own definition
394 // (same as regular definition)
395 let type_defs = project
396 .update(cx, |project, cx| {
397 let offset = buffer_text.find("Address {").expect("Address { not found");
398 project.type_definitions(&buffer, offset, cx)
399 })
400 .await
401 .unwrap()
402 .unwrap();
403 assert_definitions(&type_defs, &["pub struct Address {"], cx);
404
405 // Type definition on a field resolves through the type annotation.
406 // company.rs has `owner: Arc<Person>`, so type-def of `owner` → Person.
407 let (company_buffer, _handle) = project
408 .update(cx, |project, cx| {
409 project.open_local_buffer_with_lsp(path!("/root/src/company.rs"), cx)
410 })
411 .await
412 .unwrap();
413 cx.run_until_parked();
414
415 let company_text = company_buffer.read_with(cx, |buffer, _| buffer.text());
416 let type_defs = project
417 .update(cx, |project, cx| {
418 let offset = company_text.find("owner").expect("owner not found");
419 project.type_definitions(&company_buffer, offset, cx)
420 })
421 .await
422 .unwrap()
423 .unwrap();
424 assert_definitions(&type_defs, &["pub struct Person {"], cx);
425
426 // Type definition on another field: `address: Address` → Address.
427 let type_defs = project
428 .update(cx, |project, cx| {
429 let offset = company_text.find("address").expect("address not found");
430 project.type_definitions(&company_buffer, offset, cx)
431 })
432 .await
433 .unwrap()
434 .unwrap();
435 assert_definitions(&type_defs, &["pub struct Address {"], cx);
436
437 // Type definition on a lowercase name with no type annotation returns empty.
438 let type_defs = project
439 .update(cx, |project, cx| {
440 let offset = buffer_text.find("main").expect("main not found");
441 project.type_definitions(&buffer, offset, cx)
442 })
443 .await;
444 let is_empty = match &type_defs {
445 Ok(Some(defs)) => defs.is_empty(),
446 Ok(None) => true,
447 Err(_) => false,
448 };
449 assert!(is_empty, "expected no type definitions for `main`");
450}
451
452#[gpui::test]
453async fn test_type_definitions_in_related_files(cx: &mut TestAppContext) {
454 init_test(cx);
455 let fs = FakeFs::new(cx.executor());
456 fs.insert_tree(
457 path!("/root"),
458 json!({
459 "src": {
460 "config.rs": indoc! {r#"
461 pub struct Config {
462 debug: bool,
463 verbose: bool,
464 }
465 "#},
466 "widget.rs": indoc! {r#"
467 use super::config::Config;
468
469 pub struct Widget {
470 config: Config,
471 name: String,
472 }
473
474 impl Widget {
475 pub fn render(&self) {
476 if self.config.debug {
477 println!("debug mode");
478 }
479 }
480 }
481 "#},
482 },
483 }),
484 )
485 .await;
486
487 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
488 let mut servers = setup_fake_lsp(&project, cx);
489
490 let (buffer, _handle) = project
491 .update(cx, |project, cx| {
492 project.open_local_buffer_with_lsp(path!("/root/src/widget.rs"), cx)
493 })
494 .await
495 .unwrap();
496
497 let _server = servers.next().await.unwrap();
498 cx.run_until_parked();
499
500 let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
501 related_excerpt_store.update(cx, |store, cx| {
502 let position = {
503 let buffer = buffer.read(cx);
504 let offset = buffer
505 .text()
506 .find("self.config.debug")
507 .expect("self.config.debug not found");
508 buffer.anchor_before(offset)
509 };
510
511 store.set_identifier_line_count(0);
512 store.refresh(buffer.clone(), position, cx);
513 });
514
515 cx.executor().advance_clock(DEBOUNCE_DURATION);
516 // config.rs appears ONLY because the fake LSP resolves the type annotation
517 // `config: Config` to `pub struct Config` via GotoTypeDefinition.
518 // widget.rs appears from regular definitions of Widget / render.
519 related_excerpt_store.update(cx, |store, cx| {
520 let excerpts = store.related_files(cx);
521 assert_related_files(
522 &excerpts,
523 &[
524 (
525 "root/src/config.rs",
526 &[indoc! {"
527 pub struct Config {
528 debug: bool,
529 verbose: bool,
530 }"}],
531 ),
532 (
533 "root/src/widget.rs",
534 &[
535 indoc! {"
536 pub struct Widget {
537 config: Config,
538 name: String,
539 }
540
541 impl Widget {
542 pub fn render(&self) {"},
543 indoc! {"
544 }
545 }"},
546 ],
547 ),
548 ],
549 );
550 });
551}
552
553#[gpui::test]
554async fn test_type_definition_deduplication(cx: &mut TestAppContext) {
555 init_test(cx);
556 let fs = FakeFs::new(cx.executor());
557
558 // In this project the only identifier near the cursor whose type definition
559 // resolves is `TypeA`, and its GotoTypeDefinition returns the exact same
560 // location as GotoDefinition. After deduplication the CacheEntry for `TypeA`
561 // should have an empty `type_definitions` vec, meaning the type-definition
562 // path contributes nothing extra to the related-file output.
563 fs.insert_tree(
564 path!("/root"),
565 json!({
566 "src": {
567 "types.rs": indoc! {r#"
568 pub struct TypeA {
569 value: i32,
570 }
571
572 pub struct TypeB {
573 label: String,
574 }
575 "#},
576 "main.rs": indoc! {r#"
577 use super::types::TypeA;
578
579 fn work() {
580 let item: TypeA = unimplemented!();
581 println!("{}", item.value);
582 }
583 "#},
584 },
585 }),
586 )
587 .await;
588
589 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
590 let mut servers = setup_fake_lsp(&project, cx);
591
592 let (buffer, _handle) = project
593 .update(cx, |project, cx| {
594 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
595 })
596 .await
597 .unwrap();
598
599 let _server = servers.next().await.unwrap();
600 cx.run_until_parked();
601
602 let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
603 related_excerpt_store.update(cx, |store, cx| {
604 let position = {
605 let buffer = buffer.read(cx);
606 let offset = buffer.text().find("let item").expect("let item not found");
607 buffer.anchor_before(offset)
608 };
609
610 store.set_identifier_line_count(0);
611 store.refresh(buffer.clone(), position, cx);
612 });
613
614 cx.executor().advance_clock(DEBOUNCE_DURATION);
615 // types.rs appears because `TypeA` has a regular definition there.
616 // `item`'s type definition also resolves to TypeA in types.rs, but
617 // deduplication removes it since it points to the same location.
618 // TypeB should NOT appear because nothing references it.
619 related_excerpt_store.update(cx, |store, cx| {
620 let excerpts = store.related_files(cx);
621 assert_related_files(
622 &excerpts,
623 &[
624 (
625 "root/src/types.rs",
626 &[indoc! {"
627 pub struct TypeA {
628 value: i32,
629 }"}],
630 ),
631 ("root/src/main.rs", &["fn work() {", "}"]),
632 ],
633 );
634 });
635}
636
637#[gpui::test]
638async fn test_definitions_ranked_by_cursor_proximity(cx: &mut TestAppContext) {
639 init_test(cx);
640 let fs = FakeFs::new(cx.executor());
641
642 // helpers.rs has an impl block whose body exceeds the test
643 // MAX_OUTLINE_ITEM_BODY_SIZE (24 bytes), so assemble_excerpt_ranges
644 // splits it into header + individual children + closing brace. main.rs
645 // references two of the three methods on separate lines at varying
646 // distances from the cursor. This exercises:
647 // 1. File ordering by closest identifier rank.
648 // 2. Per-excerpt ordering within a file — child excerpts carry the rank
649 // of the identifier that discovered them.
650 // 3. Parent excerpt (impl header / closing brace) inheriting the minimum
651 // order of its children.
652 fs.insert_tree(
653 path!("/root"),
654 json!({
655 "src": {
656 "helpers.rs": indoc! {r#"
657 pub struct Helpers {
658 value: i32,
659 }
660
661 impl Helpers {
662 pub fn alpha(&self) -> i32 {
663 let intermediate = self.value;
664 intermediate + 1
665 }
666
667 pub fn beta(&self) -> i32 {
668 let intermediate = self.value;
669 intermediate + 2
670 }
671
672 pub fn gamma(&self) -> i32 {
673 let intermediate = self.value;
674 intermediate + 3
675 }
676 }
677 "#},
678 "main.rs": indoc! {r#"
679 use super::helpers::Helpers;
680
681 fn process(h: Helpers) {
682 let a = h.alpha();
683 let b = h.gamma();
684 }
685 "#},
686 },
687 }),
688 )
689 .await;
690
691 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
692 let mut servers = setup_fake_lsp(&project, cx);
693
694 let (buffer, _handle) = project
695 .update(cx, |project, cx| {
696 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
697 })
698 .await
699 .unwrap();
700
701 let _server = servers.next().await.unwrap();
702 cx.run_until_parked();
703
704 // Place cursor on "h.alpha()". `alpha` is at distance 0, `gamma` is
705 // farther below. Both resolve to methods inside `impl Helpers` in
706 // helpers.rs. The impl header and closing brace excerpts should inherit
707 // the min order of their children (alpha's order).
708 let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
709 related_excerpt_store.update(cx, |store, cx| {
710 let position = {
711 let buffer = buffer.read(cx);
712 let offset = buffer.text().find("h.alpha()").unwrap();
713 buffer.anchor_before(offset)
714 };
715
716 store.set_identifier_line_count(1);
717 store.refresh(buffer.clone(), position, cx);
718 });
719
720 cx.executor().advance_clock(DEBOUNCE_DURATION);
721 related_excerpt_store.update(cx, |store, cx| {
722 let files = store.related_files(cx);
723
724 // helpers.rs has 4 excerpts: the struct+impl header merged with
725 // the alpha method header (order 1 from alpha), alpha's closing
726 // brace (order 1), gamma's method header (order 6), and the
727 // gamma+impl closing brace (order 1, inherited from alpha which
728 // is also a child of the impl).
729 let alpha_order = 1;
730 let gamma_order = 6;
731 assert_related_files_with_orders(
732 &files,
733 &[
734 (
735 "root/src/helpers.rs",
736 &[
737 (
738 indoc! {"
739 pub struct Helpers {
740 value: i32,
741 }
742
743 impl Helpers {
744 pub fn alpha(&self) -> i32 {"},
745 alpha_order,
746 ),
747 (" }", alpha_order),
748 (" pub fn gamma(&self) -> i32 {", gamma_order),
749 (
750 indoc! {"
751 }
752 }"},
753 alpha_order,
754 ),
755 ],
756 ),
757 (
758 "root/src/main.rs",
759 &[("fn process(h: Helpers) {", 8), ("}", 8)],
760 ),
761 ],
762 );
763 });
764
765 // Now move cursor to "h.gamma()" — gamma becomes closest, reranking the
766 // excerpts so that the gamma method excerpt has the best order and the
767 // alpha method excerpt has a worse order.
768 related_excerpt_store.update(cx, |store, cx| {
769 let position = {
770 let buffer = buffer.read(cx);
771 let offset = buffer.text().find("h.gamma()").unwrap();
772 buffer.anchor_before(offset)
773 };
774
775 store.set_identifier_line_count(1);
776 store.refresh(buffer.clone(), position, cx);
777 });
778
779 cx.executor().advance_clock(DEBOUNCE_DURATION);
780 related_excerpt_store.update(cx, |store, cx| {
781 let files = store.related_files(cx);
782
783 // Now gamma is closest. The alpha method excerpts carry alpha's
784 // rank (3), and the gamma method excerpts carry gamma's rank (1).
785 // The impl closing brace merges with gamma's closing brace and
786 // inherits gamma's order (the best child).
787 let alpha_order = 3;
788 let gamma_order = 1;
789 assert_related_files_with_orders(
790 &files,
791 &[
792 (
793 "root/src/helpers.rs",
794 &[
795 (
796 indoc! {"
797 pub struct Helpers {
798 value: i32,
799 }
800
801 impl Helpers {
802 pub fn alpha(&self) -> i32 {"},
803 alpha_order,
804 ),
805 (" }", alpha_order),
806 (" pub fn gamma(&self) -> i32 {", gamma_order),
807 (
808 indoc! {"
809 }
810 }"},
811 gamma_order,
812 ),
813 ],
814 ),
815 (
816 "root/src/main.rs",
817 &[("fn process(h: Helpers) {", 8), ("}", 8)],
818 ),
819 ],
820 );
821 });
822}
823
824fn init_test(cx: &mut TestAppContext) {
825 let settings_store = cx.update(|cx| SettingsStore::test(cx));
826 cx.set_global(settings_store);
827 env_logger::try_init().ok();
828}
829
830fn setup_fake_lsp(
831 project: &Entity<Project>,
832 cx: &mut TestAppContext,
833) -> UnboundedReceiver<FakeLanguageServer> {
834 let (language_registry, fs) = project.read_with(cx, |project, _| {
835 (project.languages().clone(), project.fs().clone())
836 });
837 let language = rust_lang();
838 language_registry.add(language.clone());
839 fake_definition_lsp::register_fake_definition_server(&language_registry, language, fs)
840}
841
842fn test_project_1() -> serde_json::Value {
843 let person_rs = indoc! {r#"
844 pub struct Person {
845 first_name: String,
846 last_name: String,
847 email: String,
848 age: u32,
849 }
850
851 impl Person {
852 pub fn get_first_name(&self) -> &str {
853 &self.first_name
854 }
855
856 pub fn get_last_name(&self) -> &str {
857 &self.last_name
858 }
859
860 pub fn get_email(&self) -> &str {
861 &self.email
862 }
863
864 pub fn get_age(&self) -> u32 {
865 self.age
866 }
867 }
868 "#};
869
870 let address_rs = indoc! {r#"
871 pub struct Address {
872 street: String,
873 city: String,
874 state: State,
875 zip: u32,
876 }
877
878 pub enum State {
879 CA,
880 OR,
881 WA,
882 TX,
883 // ...
884 }
885
886 impl Address {
887 pub fn get_street(&self) -> &str {
888 &self.street
889 }
890
891 pub fn get_city(&self) -> &str {
892 &self.city
893 }
894
895 pub fn get_state(&self) -> State {
896 self.state
897 }
898
899 pub fn get_zip(&self) -> u32 {
900 self.zip
901 }
902 }
903 "#};
904
905 let company_rs = indoc! {r#"
906 use super::person::Person;
907 use super::address::Address;
908
909 pub struct Company {
910 owner: Arc<Person>,
911 address: Address,
912 }
913
914 impl Company {
915 pub fn get_owner(&self) -> &Person {
916 &self.owner
917 }
918
919 pub fn get_address(&self) -> &Address {
920 &self.address
921 }
922
923 pub fn to_string(&self) -> String {
924 format!("{} ({})", self.owner.first_name, self.address.city)
925 }
926 }
927 "#};
928
929 let main_rs = indoc! {r#"
930 use std::sync::Arc;
931 use super::person::Person;
932 use super::address::Address;
933 use super::company::Company;
934
935 pub struct Session {
936 company: Arc<Company>,
937 }
938
939 impl Session {
940 pub fn set_company(&mut self, company: Arc<Company>) {
941 self.company = company;
942 if company.owner != self.company.owner {
943 log("new owner", company.owner.get_first_name()); todo();
944 }
945 }
946 }
947
948 fn main() {
949 let company = Company {
950 owner: Arc::new(Person {
951 first_name: "John".to_string(),
952 last_name: "Doe".to_string(),
953 email: "john@example.com".to_string(),
954 age: 30,
955 }),
956 address: Address {
957 street: "123 Main St".to_string(),
958 city: "Anytown".to_string(),
959 state: State::CA,
960 zip: 12345,
961 },
962 };
963
964 println!("Company: {}", company.to_string());
965 }
966 "#};
967
968 json!({
969 "src": {
970 "person.rs": person_rs,
971 "address.rs": address_rs,
972 "company.rs": company_rs,
973 "main.rs": main_rs,
974 },
975 })
976}
977
978fn assert_related_files(actual_files: &[RelatedFile], expected_files: &[(&str, &[&str])]) {
979 let expected_with_orders: Vec<(&str, Vec<(&str, usize)>)> = expected_files
980 .iter()
981 .map(|(path, texts)| (*path, texts.iter().map(|text| (*text, 0)).collect()))
982 .collect();
983 let expected_refs: Vec<(&str, &[(&str, usize)])> = expected_with_orders
984 .iter()
985 .map(|(path, excerpts)| (*path, excerpts.as_slice()))
986 .collect();
987 assert_related_files_impl(actual_files, &expected_refs, false)
988}
989
990fn assert_related_files_with_orders(
991 actual_files: &[RelatedFile],
992 expected_files: &[(&str, &[(&str, usize)])],
993) {
994 assert_related_files_impl(actual_files, expected_files, true)
995}
996
997fn assert_related_files_impl(
998 actual_files: &[RelatedFile],
999 expected_files: &[(&str, &[(&str, usize)])],
1000 check_orders: bool,
1001) {
1002 let actual: Vec<(&str, Vec<(String, usize)>)> = actual_files
1003 .iter()
1004 .map(|file| {
1005 let excerpts = file
1006 .excerpts
1007 .iter()
1008 .map(|excerpt| {
1009 let order = if check_orders { excerpt.order } else { 0 };
1010 (excerpt.text.to_string(), order)
1011 })
1012 .collect();
1013 (file.path.to_str().unwrap(), excerpts)
1014 })
1015 .collect();
1016 let expected: Vec<(&str, Vec<(String, usize)>)> = expected_files
1017 .iter()
1018 .map(|(path, excerpts)| {
1019 (
1020 *path,
1021 excerpts
1022 .iter()
1023 .map(|(text, order)| (text.to_string(), *order))
1024 .collect(),
1025 )
1026 })
1027 .collect();
1028 pretty_assertions::assert_eq!(actual, expected)
1029}
1030
1031fn assert_definitions(definitions: &[LocationLink], first_lines: &[&str], cx: &mut TestAppContext) {
1032 let actual_first_lines = definitions
1033 .iter()
1034 .map(|definition| {
1035 definition.target.buffer.read_with(cx, |buffer, _| {
1036 let mut start = definition.target.range.start.to_point(&buffer);
1037 start.column = 0;
1038 let end = Point::new(start.row, buffer.line_len(start.row));
1039 buffer
1040 .text_for_range(start..end)
1041 .collect::<String>()
1042 .trim()
1043 .to_string()
1044 })
1045 })
1046 .collect::<Vec<String>>();
1047
1048 assert_eq!(actual_first_lines, first_lines);
1049}
1050
1051fn format_excerpts(buffer: &Buffer, excerpts: &[RelatedExcerpt]) -> String {
1052 let mut output = String::new();
1053 let file_line_count = buffer.max_point().row;
1054 let mut current_row = 0;
1055 for excerpt in excerpts {
1056 if excerpt.text.is_empty() {
1057 continue;
1058 }
1059 if current_row < excerpt.row_range.start {
1060 writeln!(&mut output, "…").unwrap();
1061 }
1062 current_row = excerpt.row_range.start;
1063
1064 for line in excerpt.text.to_string().lines() {
1065 output.push_str(line);
1066 output.push('\n');
1067 current_row += 1;
1068 }
1069 }
1070 if current_row < file_line_count {
1071 writeln!(&mut output, "…").unwrap();
1072 }
1073 output
1074}