edit_prediction_context_tests.rs

  1use super::*;
  2use futures::channel::mpsc::UnboundedReceiver;
  3use gpui::TestAppContext;
  4use indoc::indoc;
  5use language::{Point, ToPoint as _, rust_lang};
  6use lsp::FakeLanguageServer;
  7use project::{FakeFs, LocationLink, Project};
  8use serde_json::json;
  9use settings::SettingsStore;
 10use std::fmt::Write as _;
 11use util::{path, test::marked_text_ranges};
 12
 13#[gpui::test]
 14async fn test_edit_prediction_context(cx: &mut TestAppContext) {
 15    init_test(cx);
 16    let fs = FakeFs::new(cx.executor());
 17    fs.insert_tree(path!("/root"), test_project_1()).await;
 18
 19    let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
 20    let mut servers = setup_fake_lsp(&project, cx);
 21
 22    let (buffer, _handle) = project
 23        .update(cx, |project, cx| {
 24            project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
 25        })
 26        .await
 27        .unwrap();
 28
 29    let _server = servers.next().await.unwrap();
 30    cx.run_until_parked();
 31
 32    let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx));
 33    related_excerpt_store.update(cx, |store, cx| {
 34        let position = {
 35            let buffer = buffer.read(cx);
 36            let offset = buffer.text().find("todo").unwrap();
 37            buffer.anchor_before(offset)
 38        };
 39
 40        store.set_identifier_line_count(0);
 41        store.refresh(buffer.clone(), position, cx);
 42    });
 43
 44    cx.executor().advance_clock(DEBOUNCE_DURATION);
 45    related_excerpt_store.update(cx, |store, _| {
 46        let excerpts = store.related_files();
 47        assert_related_files(
 48            &excerpts,
 49            &[
 50                (
 51                    "root/src/company.rs",
 52                    &[indoc! {"
 53                        pub struct Company {
 54                            owner: Arc<Person>,
 55                            address: Address,
 56                        }"}],
 57                ),
 58                (
 59                    "root/src/main.rs",
 60                    &[
 61                        indoc! {"
 62                        pub struct Session {
 63                            company: Arc<Company>,
 64                        }
 65
 66                        impl Session {
 67                            pub fn set_company(&mut self, company: Arc<Company>) {"},
 68                        indoc! {"
 69                            }
 70                        }"},
 71                    ],
 72                ),
 73                (
 74                    "root/src/person.rs",
 75                    &[
 76                        indoc! {"
 77                        impl Person {
 78                            pub fn get_first_name(&self) -> &str {
 79                                &self.first_name
 80                            }"},
 81                        "}",
 82                    ],
 83                ),
 84            ],
 85        );
 86    });
 87}
 88
 89#[gpui::test]
 90fn test_assemble_excerpts(cx: &mut TestAppContext) {
 91    let table = [
 92        (
 93            indoc! {r#"
 94                struct User {
 95                    first_name: String,
 96                    «last_name»: String,
 97                    age: u32,
 98                    email: String,
 99                    create_at: Instant,
100                }
101
102                impl User {
103                    pub fn first_name(&self) -> String {
104                        self.first_name.clone()
105                    }
106
107                    pub fn full_name(&self) -> String {
108                «        format!("{} {}", self.first_name, self.last_name)
109                »    }
110                }
111            "#},
112            indoc! {r#"
113                struct User {
114                    first_name: String,
115                    last_name: String,
116117                }
118
119                impl User {
120121                    pub fn full_name(&self) -> String {
122                        format!("{} {}", self.first_name, self.last_name)
123                    }
124                }
125            "#},
126        ),
127        (
128            indoc! {r#"
129                struct «User» {
130                    first_name: String,
131                    last_name: String,
132                    age: u32,
133                }
134
135                impl User {
136                    // methods
137                }
138            "#},
139            indoc! {r#"
140                struct User {
141                    first_name: String,
142                    last_name: String,
143                    age: u32,
144                }
145146            "#},
147        ),
148        (
149            indoc! {r#"
150                trait «FooProvider» {
151                    const NAME: &'static str;
152
153                    fn provide_foo(&self, id: usize) -> Foo;
154
155                    fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
156                            ids.iter()
157                            .map(|id| self.provide_foo(*id))
158                            .collect()
159                    }
160
161                    fn sync(&self);
162                }
163                "#
164            },
165            indoc! {r#"
166                trait FooProvider {
167                    const NAME: &'static str;
168
169                    fn provide_foo(&self, id: usize) -> Foo;
170
171                    fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
172173                    }
174
175                    fn sync(&self);
176                }
177            "#},
178        ),
179        (
180            indoc! {r#"
181                trait «Something» {
182                    fn method1(&self, id: usize) -> Foo;
183
184                    fn method2(&self, ids: &[usize]) -> Vec<Foo> {
185                            struct Helper1 {
186                            field1: usize,
187                            }
188
189                            struct Helper2 {
190                            field2: usize,
191                            }
192
193                            struct Helper3 {
194                            filed2: usize,
195                        }
196                    }
197
198                    fn sync(&self);
199                }
200                "#
201            },
202            indoc! {r#"
203                trait Something {
204                    fn method1(&self, id: usize) -> Foo;
205
206                    fn method2(&self, ids: &[usize]) -> Vec<Foo> {
207208                    }
209
210                    fn sync(&self);
211                }
212            "#},
213        ),
214    ];
215
216    for (input, expected_output) in table {
217        let (input, ranges) = marked_text_ranges(&input, false);
218        let buffer = cx.new(|cx| Buffer::local(input, cx).with_language(rust_lang(), cx));
219        buffer.read_with(cx, |buffer, _cx| {
220            let ranges: Vec<Range<Point>> = ranges
221                .into_iter()
222                .map(|range| range.to_point(&buffer))
223                .collect();
224
225            let excerpts = assemble_excerpts(&buffer.snapshot(), ranges);
226
227            let output = format_excerpts(buffer, &excerpts);
228            assert_eq!(output, expected_output);
229        });
230    }
231}
232
233#[gpui::test]
234async fn test_fake_definition_lsp(cx: &mut TestAppContext) {
235    init_test(cx);
236
237    let fs = FakeFs::new(cx.executor());
238    fs.insert_tree(path!("/root"), test_project_1()).await;
239
240    let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
241    let mut servers = setup_fake_lsp(&project, cx);
242
243    let (buffer, _handle) = project
244        .update(cx, |project, cx| {
245            project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx)
246        })
247        .await
248        .unwrap();
249
250    let _server = servers.next().await.unwrap();
251    cx.run_until_parked();
252
253    let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text());
254
255    let definitions = project
256        .update(cx, |project, cx| {
257            let offset = buffer_text.find("Address {").unwrap();
258            project.definitions(&buffer, offset, cx)
259        })
260        .await
261        .unwrap()
262        .unwrap();
263    assert_definitions(&definitions, &["pub struct Address {"], cx);
264
265    let definitions = project
266        .update(cx, |project, cx| {
267            let offset = buffer_text.find("State::CA").unwrap();
268            project.definitions(&buffer, offset, cx)
269        })
270        .await
271        .unwrap()
272        .unwrap();
273    assert_definitions(&definitions, &["pub enum State {"], cx);
274
275    let definitions = project
276        .update(cx, |project, cx| {
277            let offset = buffer_text.find("to_string()").unwrap();
278            project.definitions(&buffer, offset, cx)
279        })
280        .await
281        .unwrap()
282        .unwrap();
283    assert_definitions(&definitions, &["pub fn to_string(&self) -> String {"], cx);
284}
285
286fn init_test(cx: &mut TestAppContext) {
287    let settings_store = cx.update(|cx| SettingsStore::test(cx));
288    cx.set_global(settings_store);
289    env_logger::try_init().ok();
290}
291
292fn setup_fake_lsp(
293    project: &Entity<Project>,
294    cx: &mut TestAppContext,
295) -> UnboundedReceiver<FakeLanguageServer> {
296    let (language_registry, fs) = project.read_with(cx, |project, _| {
297        (project.languages().clone(), project.fs().clone())
298    });
299    let language = rust_lang();
300    language_registry.add(language.clone());
301    fake_definition_lsp::register_fake_definition_server(&language_registry, language, fs)
302}
303
304fn test_project_1() -> serde_json::Value {
305    let person_rs = indoc! {r#"
306        pub struct Person {
307            first_name: String,
308            last_name: String,
309            email: String,
310            age: u32,
311        }
312
313        impl Person {
314            pub fn get_first_name(&self) -> &str {
315                &self.first_name
316            }
317
318            pub fn get_last_name(&self) -> &str {
319                &self.last_name
320            }
321
322            pub fn get_email(&self) -> &str {
323                &self.email
324            }
325
326            pub fn get_age(&self) -> u32 {
327                self.age
328            }
329        }
330    "#};
331
332    let address_rs = indoc! {r#"
333        pub struct Address {
334            street: String,
335            city: String,
336            state: State,
337            zip: u32,
338        }
339
340        pub enum State {
341            CA,
342            OR,
343            WA,
344            TX,
345            // ...
346        }
347
348        impl Address {
349            pub fn get_street(&self) -> &str {
350                &self.street
351            }
352
353            pub fn get_city(&self) -> &str {
354                &self.city
355            }
356
357            pub fn get_state(&self) -> State {
358                self.state
359            }
360
361            pub fn get_zip(&self) -> u32 {
362                self.zip
363            }
364        }
365    "#};
366
367    let company_rs = indoc! {r#"
368        use super::person::Person;
369        use super::address::Address;
370
371        pub struct Company {
372            owner: Arc<Person>,
373            address: Address,
374        }
375
376        impl Company {
377            pub fn get_owner(&self) -> &Person {
378                &self.owner
379            }
380
381            pub fn get_address(&self) -> &Address {
382                &self.address
383            }
384
385            pub fn to_string(&self) -> String {
386                format!("{} ({})", self.owner.first_name, self.address.city)
387            }
388        }
389    "#};
390
391    let main_rs = indoc! {r#"
392        use std::sync::Arc;
393        use super::person::Person;
394        use super::address::Address;
395        use super::company::Company;
396
397        pub struct Session {
398            company: Arc<Company>,
399        }
400
401        impl Session {
402            pub fn set_company(&mut self, company: Arc<Company>) {
403                self.company = company;
404                if company.owner != self.company.owner {
405                    log("new owner", company.owner.get_first_name()); todo();
406                }
407            }
408        }
409
410        fn main() {
411            let company = Company {
412                owner: Arc::new(Person {
413                    first_name: "John".to_string(),
414                    last_name: "Doe".to_string(),
415                    email: "john@example.com".to_string(),
416                    age: 30,
417                }),
418                address: Address {
419                    street: "123 Main St".to_string(),
420                    city: "Anytown".to_string(),
421                    state: State::CA,
422                    zip: 12345,
423                },
424            };
425
426            println!("Company: {}", company.to_string());
427        }
428    "#};
429
430    json!({
431        "src": {
432            "person.rs": person_rs,
433            "address.rs": address_rs,
434            "company.rs": company_rs,
435            "main.rs": main_rs,
436        },
437    })
438}
439
440fn assert_related_files(actual_files: &[RelatedFile], expected_files: &[(&str, &[&str])]) {
441    let actual_files = actual_files
442        .iter()
443        .map(|file| {
444            let excerpts = file
445                .excerpts
446                .iter()
447                .map(|excerpt| excerpt.text.to_string())
448                .collect::<Vec<_>>();
449            (file.path.to_str().unwrap(), excerpts)
450        })
451        .collect::<Vec<_>>();
452    let expected_excerpts = expected_files
453        .iter()
454        .map(|(path, texts)| {
455            (
456                *path,
457                texts
458                    .iter()
459                    .map(|line| line.to_string())
460                    .collect::<Vec<_>>(),
461            )
462        })
463        .collect::<Vec<_>>();
464    pretty_assertions::assert_eq!(actual_files, expected_excerpts)
465}
466
467fn assert_definitions(definitions: &[LocationLink], first_lines: &[&str], cx: &mut TestAppContext) {
468    let actual_first_lines = definitions
469        .iter()
470        .map(|definition| {
471            definition.target.buffer.read_with(cx, |buffer, _| {
472                let mut start = definition.target.range.start.to_point(&buffer);
473                start.column = 0;
474                let end = Point::new(start.row, buffer.line_len(start.row));
475                buffer
476                    .text_for_range(start..end)
477                    .collect::<String>()
478                    .trim()
479                    .to_string()
480            })
481        })
482        .collect::<Vec<String>>();
483
484    assert_eq!(actual_first_lines, first_lines);
485}
486
487fn format_excerpts(buffer: &Buffer, excerpts: &[RelatedExcerpt]) -> String {
488    let mut output = String::new();
489    let file_line_count = buffer.max_point().row;
490    let mut current_row = 0;
491    for excerpt in excerpts {
492        if excerpt.text.is_empty() {
493            continue;
494        }
495        if current_row < excerpt.row_range.start {
496            writeln!(&mut output, "").unwrap();
497        }
498        current_row = excerpt.row_range.start;
499
500        for line in excerpt.text.to_string().lines() {
501            output.push_str(line);
502            output.push('\n');
503            current_row += 1;
504        }
505    }
506    if current_row < file_line_count {
507        writeln!(&mut output, "").unwrap();
508    }
509    output
510}