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