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};
   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}