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,
175 …
176 }
177
178 impl User {
179 …
180 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 }
204 …
205 "#},
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> {
231 …
232 }
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> {
266 …
267 }
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}