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]
163async fn 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
293 .read_with(cx, |buffer, _| buffer.parsing_idle())
294 .await;
295 buffer.read_with(cx, |buffer, _cx| {
296 let ranges: Vec<(Range<Point>, usize)> = ranges
297 .into_iter()
298 .map(|range| (range.to_point(&buffer), 0))
299 .collect();
300
301 let assembled = assemble_excerpt_ranges(&buffer.snapshot(), ranges);
302 let excerpts: Vec<RelatedExcerpt> = assembled
303 .into_iter()
304 .map(|(row_range, order)| {
305 let start = Point::new(row_range.start, 0);
306 let end = Point::new(row_range.end, buffer.line_len(row_range.end));
307 RelatedExcerpt {
308 row_range,
309 text: buffer.text_for_range(start..end).collect::<String>().into(),
310 order,
311 }
312 })
313 .collect();
314
315 let output = format_excerpts(buffer, &excerpts);
316 assert_eq!(output, expected_output);
317 });
318 }
319}
320
321#[gpui::test]
322async fn test_fake_definition_lsp(cx: &mut TestAppContext) {
323 init_test(cx);
324
325 let fs = FakeFs::new(cx.executor());
326 fs.insert_tree(path!("/root"), test_project_1()).await;
327
328 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
329 let mut servers = setup_fake_lsp(&project, cx);
330
331 let (buffer, _handle) = project
332 .update(cx, |project, cx| {
333 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
334 })
335 .await
336 .unwrap();
337
338 let _server = servers.next().await.unwrap();
339 cx.run_until_parked();
340
341 let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
342
343 let definitions = project
344 .update(cx, |project, cx| {
345 let offset = buffer_text.find("Address {").unwrap();
346 project.definitions(&buffer, offset, cx)
347 })
348 .await
349 .unwrap()
350 .unwrap();
351 assert_definitions(&definitions, &["pub struct Address {"], cx);
352
353 let definitions = project
354 .update(cx, |project, cx| {
355 let offset = buffer_text.find("State::CA").unwrap();
356 project.definitions(&buffer, offset, cx)
357 })
358 .await
359 .unwrap()
360 .unwrap();
361 assert_definitions(&definitions, &["pub enum State {"], cx);
362
363 let definitions = project
364 .update(cx, |project, cx| {
365 let offset = buffer_text.find("to_string()").unwrap();
366 project.definitions(&buffer, offset, cx)
367 })
368 .await
369 .unwrap()
370 .unwrap();
371 assert_definitions(&definitions, &["pub fn to_string(&self) -> String {"], cx);
372}
373
374#[gpui::test]
375async fn test_fake_type_definition_lsp(cx: &mut TestAppContext) {
376 init_test(cx);
377
378 let fs = FakeFs::new(cx.executor());
379 fs.insert_tree(path!("/root"), test_project_1()).await;
380
381 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
382 let mut servers = setup_fake_lsp(&project, cx);
383
384 let (buffer, _handle) = project
385 .update(cx, |project, cx| {
386 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
387 })
388 .await
389 .unwrap();
390
391 let _server = servers.next().await.unwrap();
392 cx.run_until_parked();
393
394 let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
395
396 // Type definition on a type name returns its own definition
397 // (same as regular definition)
398 let type_defs = project
399 .update(cx, |project, cx| {
400 let offset = buffer_text.find("Address {").expect("Address { not found");
401 project.type_definitions(&buffer, offset, cx)
402 })
403 .await
404 .unwrap()
405 .unwrap();
406 assert_definitions(&type_defs, &["pub struct Address {"], cx);
407
408 // Type definition on a field resolves through the type annotation.
409 // company.rs has `owner: Arc<Person>`, so type-def of `owner` → Person.
410 let (company_buffer, _handle) = project
411 .update(cx, |project, cx| {
412 project.open_local_buffer_with_lsp(path!("/root/src/company.rs"), cx)
413 })
414 .await
415 .unwrap();
416 cx.run_until_parked();
417
418 let company_text = company_buffer.read_with(cx, |buffer, _| buffer.text());
419 let type_defs = project
420 .update(cx, |project, cx| {
421 let offset = company_text.find("owner").expect("owner not found");
422 project.type_definitions(&company_buffer, offset, cx)
423 })
424 .await
425 .unwrap()
426 .unwrap();
427 assert_definitions(&type_defs, &["pub struct Person {"], cx);
428
429 // Type definition on another field: `address: Address` → Address.
430 let type_defs = project
431 .update(cx, |project, cx| {
432 let offset = company_text.find("address").expect("address not found");
433 project.type_definitions(&company_buffer, offset, cx)
434 })
435 .await
436 .unwrap()
437 .unwrap();
438 assert_definitions(&type_defs, &["pub struct Address {"], cx);
439
440 // Type definition on a lowercase name with no type annotation returns empty.
441 let type_defs = project
442 .update(cx, |project, cx| {
443 let offset = buffer_text.find("main").expect("main not found");
444 project.type_definitions(&buffer, offset, cx)
445 })
446 .await;
447 let is_empty = match &type_defs {
448 Ok(Some(defs)) => defs.is_empty(),
449 Ok(None) => true,
450 Err(_) => false,
451 };
452 assert!(is_empty, "expected no type definitions for `main`");
453}
454
455#[gpui::test]
456async fn test_type_definitions_in_related_files(cx: &mut TestAppContext) {
457 init_test(cx);
458 let fs = FakeFs::new(cx.executor());
459 fs.insert_tree(
460 path!("/root"),
461 json!({
462 "src": {
463 "config.rs": indoc! {r#"
464 pub struct Config {
465 debug: bool,
466 verbose: bool,
467 }
468 "#},
469 "widget.rs": indoc! {r#"
470 use super::config::Config;
471
472 pub struct Widget {
473 config: Config,
474 name: String,
475 }
476
477 impl Widget {
478 pub fn render(&self) {
479 if self.config.debug {
480 println!("debug mode");
481 }
482 }
483 }
484 "#},
485 },
486 }),
487 )
488 .await;
489
490 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
491 let mut servers = setup_fake_lsp(&project, cx);
492
493 let (buffer, _handle) = project
494 .update(cx, |project, cx| {
495 project.open_local_buffer_with_lsp(path!("/root/src/widget.rs"), cx)
496 })
497 .await
498 .unwrap();
499
500 let _server = servers.next().await.unwrap();
501 cx.run_until_parked();
502
503 let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
504 related_excerpt_store.update(cx, |store, cx| {
505 let position = {
506 let buffer = buffer.read(cx);
507 let offset = buffer
508 .text()
509 .find("self.config.debug")
510 .expect("self.config.debug not found");
511 buffer.anchor_before(offset)
512 };
513
514 store.set_identifier_line_count(0);
515 store.refresh(buffer.clone(), position, cx);
516 });
517
518 cx.executor().advance_clock(DEBOUNCE_DURATION);
519 // config.rs appears ONLY because the fake LSP resolves the type annotation
520 // `config: Config` to `pub struct Config` via GotoTypeDefinition.
521 // widget.rs appears from regular definitions of Widget / render.
522 related_excerpt_store.update(cx, |store, cx| {
523 let excerpts = store.related_files(cx);
524 assert_related_files(
525 &excerpts,
526 &[
527 (
528 "root/src/config.rs",
529 &[indoc! {"
530 pub struct Config {
531 debug: bool,
532 verbose: bool,
533 }"}],
534 ),
535 (
536 "root/src/widget.rs",
537 &[
538 indoc! {"
539 pub struct Widget {
540 config: Config,
541 name: String,
542 }
543
544 impl Widget {
545 pub fn render(&self) {"},
546 indoc! {"
547 }
548 }"},
549 ],
550 ),
551 ],
552 );
553 });
554}
555
556#[gpui::test]
557async fn test_type_definition_deduplication(cx: &mut TestAppContext) {
558 init_test(cx);
559 let fs = FakeFs::new(cx.executor());
560
561 // In this project the only identifier near the cursor whose type definition
562 // resolves is `TypeA`, and its GotoTypeDefinition returns the exact same
563 // location as GotoDefinition. After deduplication the CacheEntry for `TypeA`
564 // should have an empty `type_definitions` vec, meaning the type-definition
565 // path contributes nothing extra to the related-file output.
566 fs.insert_tree(
567 path!("/root"),
568 json!({
569 "src": {
570 "types.rs": indoc! {r#"
571 pub struct TypeA {
572 value: i32,
573 }
574
575 pub struct TypeB {
576 label: String,
577 }
578 "#},
579 "main.rs": indoc! {r#"
580 use super::types::TypeA;
581
582 fn work() {
583 let item: TypeA = unimplemented!();
584 println!("{}", item.value);
585 }
586 "#},
587 },
588 }),
589 )
590 .await;
591
592 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
593 let mut servers = setup_fake_lsp(&project, cx);
594
595 let (buffer, _handle) = project
596 .update(cx, |project, cx| {
597 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
598 })
599 .await
600 .unwrap();
601
602 let _server = servers.next().await.unwrap();
603 cx.run_until_parked();
604
605 let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
606 related_excerpt_store.update(cx, |store, cx| {
607 let position = {
608 let buffer = buffer.read(cx);
609 let offset = buffer.text().find("let item").expect("let item not found");
610 buffer.anchor_before(offset)
611 };
612
613 store.set_identifier_line_count(0);
614 store.refresh(buffer.clone(), position, cx);
615 });
616
617 cx.executor().advance_clock(DEBOUNCE_DURATION);
618 // types.rs appears because `TypeA` has a regular definition there.
619 // `item`'s type definition also resolves to TypeA in types.rs, but
620 // deduplication removes it since it points to the same location.
621 // TypeB should NOT appear because nothing references it.
622 related_excerpt_store.update(cx, |store, cx| {
623 let excerpts = store.related_files(cx);
624 assert_related_files(
625 &excerpts,
626 &[
627 (
628 "root/src/types.rs",
629 &[indoc! {"
630 pub struct TypeA {
631 value: i32,
632 }"}],
633 ),
634 ("root/src/main.rs", &["fn work() {", "}"]),
635 ],
636 );
637 });
638}
639
640#[gpui::test]
641async fn test_definitions_ranked_by_cursor_proximity(cx: &mut TestAppContext) {
642 init_test(cx);
643 let fs = FakeFs::new(cx.executor());
644
645 // helpers.rs has an impl block whose body exceeds the test
646 // MAX_OUTLINE_ITEM_BODY_SIZE (24 bytes), so assemble_excerpt_ranges
647 // splits it into header + individual children + closing brace. main.rs
648 // references two of the three methods on separate lines at varying
649 // distances from the cursor. This exercises:
650 // 1. File ordering by closest identifier rank.
651 // 2. Per-excerpt ordering within a file — child excerpts carry the rank
652 // of the identifier that discovered them.
653 // 3. Parent excerpt (impl header / closing brace) inheriting the minimum
654 // order of its children.
655 fs.insert_tree(
656 path!("/root"),
657 json!({
658 "src": {
659 "helpers.rs": indoc! {r#"
660 pub struct Helpers {
661 value: i32,
662 }
663
664 impl Helpers {
665 pub fn alpha(&self) -> i32 {
666 let intermediate = self.value;
667 intermediate + 1
668 }
669
670 pub fn beta(&self) -> i32 {
671 let intermediate = self.value;
672 intermediate + 2
673 }
674
675 pub fn gamma(&self) -> i32 {
676 let intermediate = self.value;
677 intermediate + 3
678 }
679 }
680 "#},
681 "main.rs": indoc! {r#"
682 use super::helpers::Helpers;
683
684 fn process(h: Helpers) {
685 let a = h.alpha();
686 let b = h.gamma();
687 }
688 "#},
689 },
690 }),
691 )
692 .await;
693
694 let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
695 let mut servers = setup_fake_lsp(&project, cx);
696
697 let (buffer, _handle) = project
698 .update(cx, |project, cx| {
699 project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
700 })
701 .await
702 .unwrap();
703
704 let _server = servers.next().await.unwrap();
705 cx.run_until_parked();
706
707 // Place cursor on "h.alpha()". `alpha` is at distance 0, `gamma` is
708 // farther below. Both resolve to methods inside `impl Helpers` in
709 // helpers.rs. The impl header and closing brace excerpts should inherit
710 // the min order of their children (alpha's order).
711 let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
712 related_excerpt_store.update(cx, |store, cx| {
713 let position = {
714 let buffer = buffer.read(cx);
715 let offset = buffer.text().find("h.alpha()").unwrap();
716 buffer.anchor_before(offset)
717 };
718
719 store.set_identifier_line_count(1);
720 store.refresh(buffer.clone(), position, cx);
721 });
722
723 cx.executor().advance_clock(DEBOUNCE_DURATION);
724 related_excerpt_store.update(cx, |store, cx| {
725 let files = store.related_files(cx);
726
727 // helpers.rs has 4 excerpts: the struct+impl header merged with
728 // the alpha method header (order 1 from alpha), alpha's closing
729 // brace (order 1), gamma's method header (order 6), and the
730 // gamma+impl closing brace (order 1, inherited from alpha which
731 // is also a child of the impl).
732 let alpha_order = 1;
733 let gamma_order = 6;
734 assert_related_files_with_orders(
735 &files,
736 &[
737 (
738 "root/src/helpers.rs",
739 &[
740 (
741 indoc! {"
742 pub struct Helpers {
743 value: i32,
744 }
745
746 impl Helpers {
747 pub fn alpha(&self) -> i32 {"},
748 alpha_order,
749 ),
750 (" }", alpha_order),
751 (" pub fn gamma(&self) -> i32 {", gamma_order),
752 (
753 indoc! {"
754 }
755 }"},
756 alpha_order,
757 ),
758 ],
759 ),
760 (
761 "root/src/main.rs",
762 &[("fn process(h: Helpers) {", 8), ("}", 8)],
763 ),
764 ],
765 );
766 });
767
768 // Now move cursor to "h.gamma()" — gamma becomes closest, reranking the
769 // excerpts so that the gamma method excerpt has the best order and the
770 // alpha method excerpt has a worse order.
771 related_excerpt_store.update(cx, |store, cx| {
772 let position = {
773 let buffer = buffer.read(cx);
774 let offset = buffer.text().find("h.gamma()").unwrap();
775 buffer.anchor_before(offset)
776 };
777
778 store.set_identifier_line_count(1);
779 store.refresh(buffer.clone(), position, cx);
780 });
781
782 cx.executor().advance_clock(DEBOUNCE_DURATION);
783 related_excerpt_store.update(cx, |store, cx| {
784 let files = store.related_files(cx);
785
786 // Now gamma is closest. The alpha method excerpts carry alpha's
787 // rank (3), and the gamma method excerpts carry gamma's rank (1).
788 // The impl closing brace merges with gamma's closing brace and
789 // inherits gamma's order (the best child).
790 let alpha_order = 3;
791 let gamma_order = 1;
792 assert_related_files_with_orders(
793 &files,
794 &[
795 (
796 "root/src/helpers.rs",
797 &[
798 (
799 indoc! {"
800 pub struct Helpers {
801 value: i32,
802 }
803
804 impl Helpers {
805 pub fn alpha(&self) -> i32 {"},
806 alpha_order,
807 ),
808 (" }", alpha_order),
809 (" pub fn gamma(&self) -> i32 {", gamma_order),
810 (
811 indoc! {"
812 }
813 }"},
814 gamma_order,
815 ),
816 ],
817 ),
818 (
819 "root/src/main.rs",
820 &[("fn process(h: Helpers) {", 8), ("}", 8)],
821 ),
822 ],
823 );
824 });
825}
826
827fn init_test(cx: &mut TestAppContext) {
828 let settings_store = cx.update(|cx| SettingsStore::test(cx));
829 cx.set_global(settings_store);
830 env_logger::try_init().ok();
831}
832
833fn setup_fake_lsp(
834 project: &Entity<Project>,
835 cx: &mut TestAppContext,
836) -> UnboundedReceiver<FakeLanguageServer> {
837 let (language_registry, fs) = project.read_with(cx, |project, _| {
838 (project.languages().clone(), project.fs().clone())
839 });
840 let language = rust_lang();
841 language_registry.add(language.clone());
842 fake_definition_lsp::register_fake_definition_server(&language_registry, language, fs)
843}
844
845fn test_project_1() -> serde_json::Value {
846 let person_rs = indoc! {r#"
847 pub struct Person {
848 first_name: String,
849 last_name: String,
850 email: String,
851 age: u32,
852 }
853
854 impl Person {
855 pub fn get_first_name(&self) -> &str {
856 &self.first_name
857 }
858
859 pub fn get_last_name(&self) -> &str {
860 &self.last_name
861 }
862
863 pub fn get_email(&self) -> &str {
864 &self.email
865 }
866
867 pub fn get_age(&self) -> u32 {
868 self.age
869 }
870 }
871 "#};
872
873 let address_rs = indoc! {r#"
874 pub struct Address {
875 street: String,
876 city: String,
877 state: State,
878 zip: u32,
879 }
880
881 pub enum State {
882 CA,
883 OR,
884 WA,
885 TX,
886 // ...
887 }
888
889 impl Address {
890 pub fn get_street(&self) -> &str {
891 &self.street
892 }
893
894 pub fn get_city(&self) -> &str {
895 &self.city
896 }
897
898 pub fn get_state(&self) -> State {
899 self.state
900 }
901
902 pub fn get_zip(&self) -> u32 {
903 self.zip
904 }
905 }
906 "#};
907
908 let company_rs = indoc! {r#"
909 use super::person::Person;
910 use super::address::Address;
911
912 pub struct Company {
913 owner: Arc<Person>,
914 address: Address,
915 }
916
917 impl Company {
918 pub fn get_owner(&self) -> &Person {
919 &self.owner
920 }
921
922 pub fn get_address(&self) -> &Address {
923 &self.address
924 }
925
926 pub fn to_string(&self) -> String {
927 format!("{} ({})", self.owner.first_name, self.address.city)
928 }
929 }
930 "#};
931
932 let main_rs = indoc! {r#"
933 use std::sync::Arc;
934 use super::person::Person;
935 use super::address::Address;
936 use super::company::Company;
937
938 pub struct Session {
939 company: Arc<Company>,
940 }
941
942 impl Session {
943 pub fn set_company(&mut self, company: Arc<Company>) {
944 self.company = company;
945 if company.owner != self.company.owner {
946 log("new owner", company.owner.get_first_name()); todo();
947 }
948 }
949 }
950
951 fn main() {
952 let company = Company {
953 owner: Arc::new(Person {
954 first_name: "John".to_string(),
955 last_name: "Doe".to_string(),
956 email: "john@example.com".to_string(),
957 age: 30,
958 }),
959 address: Address {
960 street: "123 Main St".to_string(),
961 city: "Anytown".to_string(),
962 state: State::CA,
963 zip: 12345,
964 },
965 };
966
967 println!("Company: {}", company.to_string());
968 }
969 "#};
970
971 json!({
972 "src": {
973 "person.rs": person_rs,
974 "address.rs": address_rs,
975 "company.rs": company_rs,
976 "main.rs": main_rs,
977 },
978 })
979}
980
981fn assert_related_files(actual_files: &[RelatedFile], expected_files: &[(&str, &[&str])]) {
982 let expected_with_orders: Vec<(&str, Vec<(&str, usize)>)> = expected_files
983 .iter()
984 .map(|(path, texts)| (*path, texts.iter().map(|text| (*text, 0)).collect()))
985 .collect();
986 let expected_refs: Vec<(&str, &[(&str, usize)])> = expected_with_orders
987 .iter()
988 .map(|(path, excerpts)| (*path, excerpts.as_slice()))
989 .collect();
990 assert_related_files_impl(actual_files, &expected_refs, false)
991}
992
993fn assert_related_files_with_orders(
994 actual_files: &[RelatedFile],
995 expected_files: &[(&str, &[(&str, usize)])],
996) {
997 assert_related_files_impl(actual_files, expected_files, true)
998}
999
1000fn assert_related_files_impl(
1001 actual_files: &[RelatedFile],
1002 expected_files: &[(&str, &[(&str, usize)])],
1003 check_orders: bool,
1004) {
1005 let actual: Vec<(&str, Vec<(String, usize)>)> = actual_files
1006 .iter()
1007 .map(|file| {
1008 let excerpts = file
1009 .excerpts
1010 .iter()
1011 .map(|excerpt| {
1012 let order = if check_orders { excerpt.order } else { 0 };
1013 (excerpt.text.to_string(), order)
1014 })
1015 .collect();
1016 (file.path.to_str().unwrap(), excerpts)
1017 })
1018 .collect();
1019 let expected: Vec<(&str, Vec<(String, usize)>)> = expected_files
1020 .iter()
1021 .map(|(path, excerpts)| {
1022 (
1023 *path,
1024 excerpts
1025 .iter()
1026 .map(|(text, order)| (text.to_string(), *order))
1027 .collect(),
1028 )
1029 })
1030 .collect();
1031 pretty_assertions::assert_eq!(actual, expected)
1032}
1033
1034#[track_caller]
1035fn assert_definitions(definitions: &[LocationLink], first_lines: &[&str], cx: &mut TestAppContext) {
1036 let actual_first_lines = definitions
1037 .iter()
1038 .map(|definition| {
1039 definition.target.buffer.read_with(cx, |buffer, _| {
1040 let mut start = definition.target.range.start.to_point(&buffer);
1041 start.column = 0;
1042 let end = Point::new(start.row, buffer.line_len(start.row));
1043 buffer
1044 .text_for_range(start..end)
1045 .collect::<String>()
1046 .trim()
1047 .to_string()
1048 })
1049 })
1050 .collect::<Vec<String>>();
1051
1052 assert_eq!(actual_first_lines, first_lines);
1053}
1054
1055fn format_excerpts(buffer: &Buffer, excerpts: &[RelatedExcerpt]) -> String {
1056 let mut output = String::new();
1057 let file_line_count = buffer.max_point().row;
1058 let mut current_row = 0;
1059 for excerpt in excerpts {
1060 if excerpt.text.is_empty() {
1061 continue;
1062 }
1063 if current_row < excerpt.row_range.start {
1064 writeln!(&mut output, "…").unwrap();
1065 }
1066 current_row = excerpt.row_range.start;
1067
1068 for line in excerpt.text.to_string().lines() {
1069 output.push_str(line);
1070 output.push('\n');
1071 current_row += 1;
1072 }
1073 }
1074 if current_row < file_line_count {
1075 writeln!(&mut output, "…").unwrap();
1076 }
1077 output
1078}