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,
176 …
177 }
178
179 impl User {
180 …
181 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 }
205 …
206 "#},
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> {
232 …
233 }
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> {
267 …
268 }
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}