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