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