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,
116 …
117 }
118
119 impl User {
120 …
121 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 }
145 …
146 "#},
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> {
172 …
173 }
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> {
207 …
208 }
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}