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/company.rs",
 53                    &[indoc! {"
 54                        pub struct Company {
 55                            owner: Arc<Person>,
 56                            address: Address,
 57                        }"}],
 58                ),
 59                (
 60                    "root/src/main.rs",
 61                    &[
 62                        indoc! {"
 63                        pub struct Session {
 64                            company: Arc<Company>,
 65                        }
 66
 67                        impl Session {
 68                            pub fn set_company(&mut self, company: Arc<Company>) {"},
 69                        indoc! {"
 70                            }
 71                        }"},
 72                    ],
 73                ),
 74                (
 75                    "root/src/person.rs",
 76                    &[
 77                        indoc! {"
 78                        impl Person {
 79                            pub fn get_first_name(&self) -> &str {
 80                                &self.first_name
 81                            }"},
 82                        "}",
 83                    ],
 84                ),
 85            ],
 86        );
 87    });
 88
 89    let company_buffer = related_excerpt_store.update(cx, |store, cx| {
 90        store
 91            .related_files_with_buffers(cx)
 92            .find(|(file, _)| file.path.to_str() == Some("root/src/company.rs"))
 93            .map(|(_, buffer)| buffer)
 94            .expect("company.rs buffer not found")
 95    });
 96
 97    company_buffer.update(cx, |buffer, cx| {
 98        let text = buffer.text();
 99        let insert_pos = text.find("address: Address,").unwrap() + "address: Address,".len();
100        buffer.edit([(insert_pos..insert_pos, "\n    name: String,")], None, cx);
101    });
102
103    related_excerpt_store.update(cx, |store, cx| {
104        let excerpts = store.related_files(cx);
105        assert_related_files(
106            &excerpts,
107            &[
108                (
109                    "root/src/company.rs",
110                    &[indoc! {"
111                        pub struct Company {
112                            owner: Arc<Person>,
113                            address: Address,
114                            name: String,
115                        }"}],
116                ),
117                (
118                    "root/src/main.rs",
119                    &[
120                        indoc! {"
121                        pub struct Session {
122                            company: Arc<Company>,
123                        }
124
125                        impl Session {
126                            pub fn set_company(&mut self, company: Arc<Company>) {"},
127                        indoc! {"
128                            }
129                        }"},
130                    ],
131                ),
132                (
133                    "root/src/person.rs",
134                    &[
135                        indoc! {"
136                        impl Person {
137                            pub fn get_first_name(&self) -> &str {
138                                &self.first_name
139                            }"},
140                        "}",
141                    ],
142                ),
143            ],
144        );
145    });
146}
147
148#[gpui::test]
149fn test_assemble_excerpts(cx: &mut TestAppContext) {
150    let table = [
151        (
152            indoc! {r#"
153                struct User {
154                    first_name: String,
155                    «last_name»: String,
156                    age: u32,
157                    email: String,
158                    create_at: Instant,
159                }
160
161                impl User {
162                    pub fn first_name(&self) -> String {
163                        self.first_name.clone()
164                    }
165
166                    pub fn full_name(&self) -> String {
167                «        format!("{} {}", self.first_name, self.last_name)
168                »    }
169                }
170            "#},
171            indoc! {r#"
172                struct User {
173                    first_name: String,
174                    last_name: String,
175176                }
177
178                impl User {
179180                    pub fn full_name(&self) -> String {
181                        format!("{} {}", self.first_name, self.last_name)
182                    }
183                }
184            "#},
185        ),
186        (
187            indoc! {r#"
188                struct «User» {
189                    first_name: String,
190                    last_name: String,
191                    age: u32,
192                }
193
194                impl User {
195                    // methods
196                }
197            "#},
198            indoc! {r#"
199                struct User {
200                    first_name: String,
201                    last_name: String,
202                    age: u32,
203                }
204205            "#},
206        ),
207        (
208            indoc! {r#"
209                trait «FooProvider» {
210                    const NAME: &'static str;
211
212                    fn provide_foo(&self, id: usize) -> Foo;
213
214                    fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
215                            ids.iter()
216                            .map(|id| self.provide_foo(*id))
217                            .collect()
218                    }
219
220                    fn sync(&self);
221                }
222                "#
223            },
224            indoc! {r#"
225                trait FooProvider {
226                    const NAME: &'static str;
227
228                    fn provide_foo(&self, id: usize) -> Foo;
229
230                    fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
231232                    }
233
234                    fn sync(&self);
235                }
236            "#},
237        ),
238        (
239            indoc! {r#"
240                trait «Something» {
241                    fn method1(&self, id: usize) -> Foo;
242
243                    fn method2(&self, ids: &[usize]) -> Vec<Foo> {
244                            struct Helper1 {
245                            field1: usize,
246                            }
247
248                            struct Helper2 {
249                            field2: usize,
250                            }
251
252                            struct Helper3 {
253                            filed2: usize,
254                        }
255                    }
256
257                    fn sync(&self);
258                }
259                "#
260            },
261            indoc! {r#"
262                trait Something {
263                    fn method1(&self, id: usize) -> Foo;
264
265                    fn method2(&self, ids: &[usize]) -> Vec<Foo> {
266267                    }
268
269                    fn sync(&self);
270                }
271            "#},
272        ),
273    ];
274
275    for (input, expected_output) in table {
276        let (input, ranges) = marked_text_ranges(&input, false);
277        let buffer = cx.new(|cx| Buffer::local(input, cx).with_language(rust_lang(), cx));
278        buffer.read_with(cx, |buffer, _cx| {
279            let ranges: Vec<Range<Point>> = ranges
280                .into_iter()
281                .map(|range| range.to_point(&buffer))
282                .collect();
283
284            let row_ranges = assemble_excerpt_ranges(&buffer.snapshot(), ranges);
285            let excerpts: Vec<RelatedExcerpt> = row_ranges
286                .into_iter()
287                .map(|row_range| {
288                    let start = Point::new(row_range.start, 0);
289                    let end = Point::new(row_range.end, buffer.line_len(row_range.end));
290                    RelatedExcerpt {
291                        row_range,
292                        text: buffer.text_for_range(start..end).collect::<String>().into(),
293                    }
294                })
295                .collect();
296
297            let output = format_excerpts(buffer, &excerpts);
298            assert_eq!(output, expected_output);
299        });
300    }
301}
302
303#[gpui::test]
304async fn test_fake_definition_lsp(cx: &mut TestAppContext) {
305    init_test(cx);
306
307    let fs = FakeFs::new(cx.executor());
308    fs.insert_tree(path!("/root"), test_project_1()).await;
309
310    let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
311    let mut servers = setup_fake_lsp(&project, cx);
312
313    let (buffer, _handle) = project
314        .update(cx, |project, cx| {
315            project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
316        })
317        .await
318        .unwrap();
319
320    let _server = servers.next().await.unwrap();
321    cx.run_until_parked();
322
323    let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
324
325    let definitions = project
326        .update(cx, |project, cx| {
327            let offset = buffer_text.find("Address {").unwrap();
328            project.definitions(&buffer, offset, cx)
329        })
330        .await
331        .unwrap()
332        .unwrap();
333    assert_definitions(&definitions, &["pub struct Address {"], cx);
334
335    let definitions = project
336        .update(cx, |project, cx| {
337            let offset = buffer_text.find("State::CA").unwrap();
338            project.definitions(&buffer, offset, cx)
339        })
340        .await
341        .unwrap()
342        .unwrap();
343    assert_definitions(&definitions, &["pub enum State {"], cx);
344
345    let definitions = project
346        .update(cx, |project, cx| {
347            let offset = buffer_text.find("to_string()").unwrap();
348            project.definitions(&buffer, offset, cx)
349        })
350        .await
351        .unwrap()
352        .unwrap();
353    assert_definitions(&definitions, &["pub fn to_string(&self) -> String {"], cx);
354}
355
356fn init_test(cx: &mut TestAppContext) {
357    let settings_store = cx.update(|cx| SettingsStore::test(cx));
358    cx.set_global(settings_store);
359    env_logger::try_init().ok();
360}
361
362fn setup_fake_lsp(
363    project: &Entity<Project>,
364    cx: &mut TestAppContext,
365) -> UnboundedReceiver<FakeLanguageServer> {
366    let (language_registry, fs) = project.read_with(cx, |project, _| {
367        (project.languages().clone(), project.fs().clone())
368    });
369    let language = rust_lang();
370    language_registry.add(language.clone());
371    fake_definition_lsp::register_fake_definition_server(&language_registry, language, fs)
372}
373
374fn test_project_1() -> serde_json::Value {
375    let person_rs = indoc! {r#"
376        pub struct Person {
377            first_name: String,
378            last_name: String,
379            email: String,
380            age: u32,
381        }
382
383        impl Person {
384            pub fn get_first_name(&self) -> &str {
385                &self.first_name
386            }
387
388            pub fn get_last_name(&self) -> &str {
389                &self.last_name
390            }
391
392            pub fn get_email(&self) -> &str {
393                &self.email
394            }
395
396            pub fn get_age(&self) -> u32 {
397                self.age
398            }
399        }
400    "#};
401
402    let address_rs = indoc! {r#"
403        pub struct Address {
404            street: String,
405            city: String,
406            state: State,
407            zip: u32,
408        }
409
410        pub enum State {
411            CA,
412            OR,
413            WA,
414            TX,
415            // ...
416        }
417
418        impl Address {
419            pub fn get_street(&self) -> &str {
420                &self.street
421            }
422
423            pub fn get_city(&self) -> &str {
424                &self.city
425            }
426
427            pub fn get_state(&self) -> State {
428                self.state
429            }
430
431            pub fn get_zip(&self) -> u32 {
432                self.zip
433            }
434        }
435    "#};
436
437    let company_rs = indoc! {r#"
438        use super::person::Person;
439        use super::address::Address;
440
441        pub struct Company {
442            owner: Arc<Person>,
443            address: Address,
444        }
445
446        impl Company {
447            pub fn get_owner(&self) -> &Person {
448                &self.owner
449            }
450
451            pub fn get_address(&self) -> &Address {
452                &self.address
453            }
454
455            pub fn to_string(&self) -> String {
456                format!("{} ({})", self.owner.first_name, self.address.city)
457            }
458        }
459    "#};
460
461    let main_rs = indoc! {r#"
462        use std::sync::Arc;
463        use super::person::Person;
464        use super::address::Address;
465        use super::company::Company;
466
467        pub struct Session {
468            company: Arc<Company>,
469        }
470
471        impl Session {
472            pub fn set_company(&mut self, company: Arc<Company>) {
473                self.company = company;
474                if company.owner != self.company.owner {
475                    log("new owner", company.owner.get_first_name()); todo();
476                }
477            }
478        }
479
480        fn main() {
481            let company = Company {
482                owner: Arc::new(Person {
483                    first_name: "John".to_string(),
484                    last_name: "Doe".to_string(),
485                    email: "john@example.com".to_string(),
486                    age: 30,
487                }),
488                address: Address {
489                    street: "123 Main St".to_string(),
490                    city: "Anytown".to_string(),
491                    state: State::CA,
492                    zip: 12345,
493                },
494            };
495
496            println!("Company: {}", company.to_string());
497        }
498    "#};
499
500    json!({
501        "src": {
502            "person.rs": person_rs,
503            "address.rs": address_rs,
504            "company.rs": company_rs,
505            "main.rs": main_rs,
506        },
507    })
508}
509
510fn assert_related_files(actual_files: &[RelatedFile], expected_files: &[(&str, &[&str])]) {
511    let actual_files = actual_files
512        .iter()
513        .map(|file| {
514            let excerpts = file
515                .excerpts
516                .iter()
517                .map(|excerpt| excerpt.text.to_string())
518                .collect::<Vec<_>>();
519            (file.path.to_str().unwrap(), excerpts)
520        })
521        .collect::<Vec<_>>();
522    let expected_excerpts = expected_files
523        .iter()
524        .map(|(path, texts)| {
525            (
526                *path,
527                texts
528                    .iter()
529                    .map(|line| line.to_string())
530                    .collect::<Vec<_>>(),
531            )
532        })
533        .collect::<Vec<_>>();
534    pretty_assertions::assert_eq!(actual_files, expected_excerpts)
535}
536
537fn assert_definitions(definitions: &[LocationLink], first_lines: &[&str], cx: &mut TestAppContext) {
538    let actual_first_lines = definitions
539        .iter()
540        .map(|definition| {
541            definition.target.buffer.read_with(cx, |buffer, _| {
542                let mut start = definition.target.range.start.to_point(&buffer);
543                start.column = 0;
544                let end = Point::new(start.row, buffer.line_len(start.row));
545                buffer
546                    .text_for_range(start..end)
547                    .collect::<String>()
548                    .trim()
549                    .to_string()
550            })
551        })
552        .collect::<Vec<String>>();
553
554    assert_eq!(actual_first_lines, first_lines);
555}
556
557fn format_excerpts(buffer: &Buffer, excerpts: &[RelatedExcerpt]) -> String {
558    let mut output = String::new();
559    let file_line_count = buffer.max_point().row;
560    let mut current_row = 0;
561    for excerpt in excerpts {
562        if excerpt.text.is_empty() {
563            continue;
564        }
565        if current_row < excerpt.row_range.start {
566            writeln!(&mut output, "").unwrap();
567        }
568        current_row = excerpt.row_range.start;
569
570        for line in excerpt.text.to_string().lines() {
571            output.push_str(line);
572            output.push('\n');
573            current_row += 1;
574        }
575    }
576    if current_row < file_line_count {
577        writeln!(&mut output, "").unwrap();
578    }
579    output
580}