1use std::ops::Range;
2
3use gpui::{impl_internal_actions, MutableAppContext, Task, ViewContext};
4use language::{Bias, ToOffset};
5use project::LocationLink;
6use settings::Settings;
7use util::TryFutureExt;
8use workspace::Workspace;
9
10use crate::{
11 Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, Select,
12 SelectPhase,
13};
14
15#[derive(Clone, PartialEq)]
16pub struct UpdateGoToDefinitionLink {
17 pub point: Option<DisplayPoint>,
18 pub cmd_held: bool,
19 pub shift_held: bool,
20}
21
22#[derive(Clone, PartialEq)]
23pub struct CmdShiftChanged {
24 pub cmd_down: bool,
25 pub shift_down: bool,
26}
27
28#[derive(Clone, PartialEq)]
29pub struct GoToFetchedDefinition {
30 pub point: DisplayPoint,
31}
32
33#[derive(Clone, PartialEq)]
34pub struct GoToFetchedTypeDefinition {
35 pub point: DisplayPoint,
36}
37
38impl_internal_actions!(
39 editor,
40 [
41 UpdateGoToDefinitionLink,
42 CmdShiftChanged,
43 GoToFetchedDefinition,
44 GoToFetchedTypeDefinition
45 ]
46);
47
48pub fn init(cx: &mut MutableAppContext) {
49 cx.add_action(update_go_to_definition_link);
50 cx.add_action(cmd_shift_changed);
51 cx.add_action(go_to_fetched_definition);
52 cx.add_action(go_to_fetched_type_definition);
53}
54
55#[derive(Debug, Default)]
56pub struct LinkGoToDefinitionState {
57 pub last_mouse_location: Option<Anchor>,
58 pub symbol_range: Option<Range<Anchor>>,
59 pub kind: Option<LinkDefinitionKind>,
60 pub definitions: Vec<LocationLink>,
61 pub task: Option<Task<Option<()>>>,
62}
63
64pub fn update_go_to_definition_link(
65 editor: &mut Editor,
66 &UpdateGoToDefinitionLink {
67 point,
68 cmd_held,
69 shift_held,
70 }: &UpdateGoToDefinitionLink,
71 cx: &mut ViewContext<Editor>,
72) {
73 // Store new mouse point as an anchor
74 let snapshot = editor.snapshot(cx);
75 let point = point.map(|point| {
76 snapshot
77 .buffer_snapshot
78 .anchor_before(point.to_offset(&snapshot.display_snapshot, Bias::Left))
79 });
80
81 // If the new point is the same as the previously stored one, return early
82 if let (Some(a), Some(b)) = (
83 &point,
84 &editor.link_go_to_definition_state.last_mouse_location,
85 ) {
86 if a.cmp(b, &snapshot.buffer_snapshot).is_eq() {
87 return;
88 }
89 }
90
91 editor.link_go_to_definition_state.last_mouse_location = point.clone();
92 if cmd_held {
93 if let Some(point) = point {
94 let kind = if shift_held {
95 LinkDefinitionKind::Type
96 } else {
97 LinkDefinitionKind::Symbol
98 };
99
100 show_link_definition(kind, editor, point, snapshot, cx);
101 return;
102 }
103 }
104
105 hide_link_definition(editor, cx);
106}
107
108pub fn cmd_shift_changed(
109 editor: &mut Editor,
110 &CmdShiftChanged {
111 cmd_down,
112 shift_down,
113 }: &CmdShiftChanged,
114 cx: &mut ViewContext<Editor>,
115) {
116 if let Some(point) = editor
117 .link_go_to_definition_state
118 .last_mouse_location
119 .clone()
120 {
121 if cmd_down {
122 let snapshot = editor.snapshot(cx);
123 let kind = if shift_down {
124 LinkDefinitionKind::Type
125 } else {
126 LinkDefinitionKind::Symbol
127 };
128
129 show_link_definition(kind, editor, point, snapshot, cx);
130 } else {
131 hide_link_definition(editor, cx)
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq)]
137pub enum LinkDefinitionKind {
138 Symbol,
139 Type,
140}
141
142pub fn show_link_definition(
143 definition_kind: LinkDefinitionKind,
144 editor: &mut Editor,
145 trigger_point: Anchor,
146 snapshot: EditorSnapshot,
147 cx: &mut ViewContext<Editor>,
148) {
149 let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind);
150 if !same_kind {
151 hide_link_definition(editor, cx);
152 }
153
154 if editor.pending_rename.is_some() {
155 return;
156 }
157
158 let (buffer, buffer_position) = if let Some(output) = editor
159 .buffer
160 .read(cx)
161 .text_anchor_for_position(trigger_point.clone(), cx)
162 {
163 output
164 } else {
165 return;
166 };
167
168 let excerpt_id = if let Some((excerpt_id, _, _)) = editor
169 .buffer()
170 .read(cx)
171 .excerpt_containing(trigger_point.clone(), cx)
172 {
173 excerpt_id
174 } else {
175 return;
176 };
177
178 let project = if let Some(project) = editor.project.clone() {
179 project
180 } else {
181 return;
182 };
183
184 // Don't request again if the location is within the symbol region of a previous request with the same kind
185 if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range {
186 let point_after_start = symbol_range
187 .start
188 .cmp(&trigger_point, &snapshot.buffer_snapshot)
189 .is_le();
190
191 let point_before_end = symbol_range
192 .end
193 .cmp(&trigger_point, &snapshot.buffer_snapshot)
194 .is_ge();
195
196 let point_within_range = point_after_start && point_before_end;
197 if point_within_range && same_kind {
198 return;
199 }
200 }
201
202 let task = cx.spawn_weak(|this, mut cx| {
203 async move {
204 // query the LSP for definition info
205 let definition_request = cx.update(|cx| {
206 project.update(cx, |project, cx| match definition_kind {
207 LinkDefinitionKind::Symbol => project.definition(&buffer, buffer_position, cx),
208
209 LinkDefinitionKind::Type => {
210 project.type_definition(&buffer, buffer_position, cx)
211 }
212 })
213 });
214
215 let result = definition_request.await.ok().map(|definition_result| {
216 (
217 definition_result.iter().find_map(|link| {
218 link.origin.as_ref().map(|origin| {
219 let start = snapshot
220 .buffer_snapshot
221 .anchor_in_excerpt(excerpt_id.clone(), origin.range.start);
222 let end = snapshot
223 .buffer_snapshot
224 .anchor_in_excerpt(excerpt_id.clone(), origin.range.end);
225
226 start..end
227 })
228 }),
229 definition_result,
230 )
231 });
232
233 if let Some(this) = this.upgrade(&cx) {
234 this.update(&mut cx, |this, cx| {
235 // Clear any existing highlights
236 this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
237 this.link_go_to_definition_state.kind = Some(definition_kind);
238 this.link_go_to_definition_state.symbol_range = result
239 .as_ref()
240 .and_then(|(symbol_range, _)| symbol_range.clone());
241
242 if let Some((symbol_range, definitions)) = result {
243 this.link_go_to_definition_state.definitions = definitions.clone();
244
245 let buffer_snapshot = buffer.read(cx).snapshot();
246 // Only show highlight if there exists a definition to jump to that doesn't contain
247 // the current location.
248 if definitions.iter().any(|definition| {
249 let target = &definition.target;
250 if target.buffer == buffer {
251 let range = &target.range;
252 // Expand range by one character as lsp definition ranges include positions adjacent
253 // but not contained by the symbol range
254 let start = buffer_snapshot.clip_offset(
255 range.start.to_offset(&buffer_snapshot).saturating_sub(1),
256 Bias::Left,
257 );
258 let end = buffer_snapshot.clip_offset(
259 range.end.to_offset(&buffer_snapshot) + 1,
260 Bias::Right,
261 );
262 let offset = buffer_position.to_offset(&buffer_snapshot);
263 !(start <= offset && end >= offset)
264 } else {
265 true
266 }
267 }) {
268 // If no symbol range returned from language server, use the surrounding word.
269 let highlight_range = symbol_range.unwrap_or_else(|| {
270 let snapshot = &snapshot.buffer_snapshot;
271 let (offset_range, _) = snapshot.surrounding_word(trigger_point);
272
273 snapshot.anchor_before(offset_range.start)
274 ..snapshot.anchor_after(offset_range.end)
275 });
276
277 // Highlight symbol using theme link definition highlight style
278 let style = cx.global::<Settings>().theme.editor.link_definition;
279 this.highlight_text::<LinkGoToDefinitionState>(
280 vec![highlight_range],
281 style,
282 cx,
283 )
284 } else {
285 hide_link_definition(this, cx);
286 }
287 }
288 })
289 }
290
291 Ok::<_, anyhow::Error>(())
292 }
293 .log_err()
294 });
295
296 editor.link_go_to_definition_state.task = Some(task);
297}
298
299pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
300 if editor.link_go_to_definition_state.symbol_range.is_some()
301 || !editor.link_go_to_definition_state.definitions.is_empty()
302 {
303 editor.link_go_to_definition_state.symbol_range.take();
304 editor.link_go_to_definition_state.definitions.clear();
305 cx.notify();
306 }
307
308 editor.link_go_to_definition_state.task = None;
309
310 editor.clear_text_highlights::<LinkGoToDefinitionState>(cx);
311}
312
313pub fn go_to_fetched_definition(
314 workspace: &mut Workspace,
315 &GoToFetchedDefinition { point }: &GoToFetchedDefinition,
316 cx: &mut ViewContext<Workspace>,
317) {
318 go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, workspace, point, cx);
319}
320
321pub fn go_to_fetched_type_definition(
322 workspace: &mut Workspace,
323 &GoToFetchedTypeDefinition { point }: &GoToFetchedTypeDefinition,
324 cx: &mut ViewContext<Workspace>,
325) {
326 go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, workspace, point, cx);
327}
328
329fn go_to_fetched_definition_of_kind(
330 kind: LinkDefinitionKind,
331 workspace: &mut Workspace,
332 point: DisplayPoint,
333 cx: &mut ViewContext<Workspace>,
334) {
335 let active_item = workspace.active_item(cx);
336 let editor_handle = if let Some(editor) = active_item
337 .as_ref()
338 .and_then(|item| item.act_as::<Editor>(cx))
339 {
340 editor
341 } else {
342 return;
343 };
344
345 let (cached_definitions, cached_definitions_kind) = editor_handle.update(cx, |editor, cx| {
346 let definitions = editor.link_go_to_definition_state.definitions.clone();
347 hide_link_definition(editor, cx);
348 (definitions, editor.link_go_to_definition_state.kind)
349 });
350
351 let is_correct_kind = cached_definitions_kind == Some(kind);
352 if !cached_definitions.is_empty() && is_correct_kind {
353 editor_handle.update(cx, |editor, cx| {
354 if !editor.focused {
355 cx.focus_self();
356 }
357 });
358
359 Editor::navigate_to_definitions(workspace, editor_handle, cached_definitions, cx);
360 } else {
361 editor_handle.update(cx, |editor, cx| {
362 editor.select(
363 &Select(SelectPhase::Begin {
364 position: point,
365 add: false,
366 click_count: 1,
367 }),
368 cx,
369 );
370 });
371
372 match kind {
373 LinkDefinitionKind::Symbol => Editor::go_to_definition(workspace, &GoToDefinition, cx),
374
375 LinkDefinitionKind::Type => {
376 Editor::go_to_type_definition(workspace, &GoToTypeDefinition, cx)
377 }
378 }
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use futures::StreamExt;
385 use indoc::indoc;
386 use lsp::request::{GotoDefinition, GotoTypeDefinition};
387
388 use crate::test::EditorLspTestContext;
389
390 use super::*;
391
392 #[gpui::test]
393 async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
394 let mut cx = EditorLspTestContext::new_rust(
395 lsp::ServerCapabilities {
396 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
397 ..Default::default()
398 },
399 cx,
400 )
401 .await;
402
403 cx.set_state(indoc! {"
404 struct A;
405 let vˇariable = A;
406 "});
407
408 // Basic hold cmd+shift, expect highlight in region if response contains type definition
409 let hover_point = cx.display_point(indoc! {"
410 struct A;
411 let vˇariable = A;
412 "});
413 let symbol_range = cx.lsp_range(indoc! {"
414 struct A;
415 let «variable» = A;
416 "});
417 let target_range = cx.lsp_range(indoc! {"
418 struct «A»;
419 let variable = A;
420 "});
421
422 let mut requests =
423 cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
424 Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
425 lsp::LocationLink {
426 origin_selection_range: Some(symbol_range),
427 target_uri: url.clone(),
428 target_range,
429 target_selection_range: target_range,
430 },
431 ])))
432 });
433
434 // Press cmd+shift to trigger highlight
435 cx.update_editor(|editor, cx| {
436 update_go_to_definition_link(
437 editor,
438 &UpdateGoToDefinitionLink {
439 point: Some(hover_point),
440 cmd_held: true,
441 shift_held: true,
442 },
443 cx,
444 );
445 });
446 requests.next().await;
447 cx.foreground().run_until_parked();
448 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
449 struct A;
450 let «variable» = A;
451 "});
452
453 // Unpress shift causes highlight to go away (normal goto-definition is not valid here)
454 cx.update_editor(|editor, cx| {
455 cmd_shift_changed(
456 editor,
457 &CmdShiftChanged {
458 cmd_down: true,
459 shift_down: false,
460 },
461 cx,
462 );
463 });
464 // Assert no link highlights
465 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
466 struct A;
467 let variable = A;
468 "});
469
470 // Cmd+shift click without existing definition requests and jumps
471 let hover_point = cx.display_point(indoc! {"
472 struct A;
473 let vˇariable = A;
474 "});
475 let target_range = cx.lsp_range(indoc! {"
476 struct «A»;
477 let variable = A;
478 "});
479
480 let mut requests =
481 cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
482 Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
483 lsp::LocationLink {
484 origin_selection_range: None,
485 target_uri: url,
486 target_range,
487 target_selection_range: target_range,
488 },
489 ])))
490 });
491
492 cx.update_workspace(|workspace, cx| {
493 go_to_fetched_type_definition(
494 workspace,
495 &GoToFetchedTypeDefinition { point: hover_point },
496 cx,
497 );
498 });
499 requests.next().await;
500 cx.foreground().run_until_parked();
501
502 cx.assert_editor_state(indoc! {"
503 struct «Aˇ»;
504 let variable = A;
505 "});
506 }
507
508 #[gpui::test]
509 async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
510 let mut cx = EditorLspTestContext::new_rust(
511 lsp::ServerCapabilities {
512 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
513 ..Default::default()
514 },
515 cx,
516 )
517 .await;
518
519 cx.set_state(indoc! {"
520 fn ˇtest() { do_work(); }
521 fn do_work() { test(); }
522 "});
523
524 // Basic hold cmd, expect highlight in region if response contains definition
525 let hover_point = cx.display_point(indoc! {"
526 fn test() { do_wˇork(); }
527 fn do_work() { test(); }
528 "});
529 let symbol_range = cx.lsp_range(indoc! {"
530 fn test() { «do_work»(); }
531 fn do_work() { test(); }
532 "});
533 let target_range = cx.lsp_range(indoc! {"
534 fn test() { do_work(); }
535 fn «do_work»() { test(); }
536 "});
537
538 let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
539 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
540 lsp::LocationLink {
541 origin_selection_range: Some(symbol_range),
542 target_uri: url.clone(),
543 target_range,
544 target_selection_range: target_range,
545 },
546 ])))
547 });
548
549 cx.update_editor(|editor, cx| {
550 update_go_to_definition_link(
551 editor,
552 &UpdateGoToDefinitionLink {
553 point: Some(hover_point),
554 cmd_held: true,
555 shift_held: false,
556 },
557 cx,
558 );
559 });
560 requests.next().await;
561 cx.foreground().run_until_parked();
562 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
563 fn test() { «do_work»(); }
564 fn do_work() { test(); }
565 "});
566
567 // Unpress cmd causes highlight to go away
568 cx.update_editor(|editor, cx| {
569 cmd_shift_changed(
570 editor,
571 &CmdShiftChanged {
572 cmd_down: false,
573 shift_down: false,
574 },
575 cx,
576 );
577 });
578
579 // Assert no link highlights
580 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
581 fn test() { do_work(); }
582 fn do_work() { test(); }
583 "});
584
585 // Response without source range still highlights word
586 cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None);
587 let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
588 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
589 lsp::LocationLink {
590 // No origin range
591 origin_selection_range: None,
592 target_uri: url.clone(),
593 target_range,
594 target_selection_range: target_range,
595 },
596 ])))
597 });
598 cx.update_editor(|editor, cx| {
599 update_go_to_definition_link(
600 editor,
601 &UpdateGoToDefinitionLink {
602 point: Some(hover_point),
603 cmd_held: true,
604 shift_held: false,
605 },
606 cx,
607 );
608 });
609 requests.next().await;
610 cx.foreground().run_until_parked();
611
612 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
613 fn test() { «do_work»(); }
614 fn do_work() { test(); }
615 "});
616
617 // Moving mouse to location with no response dismisses highlight
618 let hover_point = cx.display_point(indoc! {"
619 fˇn test() { do_work(); }
620 fn do_work() { test(); }
621 "});
622 let mut requests = cx
623 .lsp
624 .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
625 // No definitions returned
626 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
627 });
628 cx.update_editor(|editor, cx| {
629 update_go_to_definition_link(
630 editor,
631 &UpdateGoToDefinitionLink {
632 point: Some(hover_point),
633 cmd_held: true,
634 shift_held: false,
635 },
636 cx,
637 );
638 });
639 requests.next().await;
640 cx.foreground().run_until_parked();
641
642 // Assert no link highlights
643 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
644 fn test() { do_work(); }
645 fn do_work() { test(); }
646 "});
647
648 // Move mouse without cmd and then pressing cmd triggers highlight
649 let hover_point = cx.display_point(indoc! {"
650 fn test() { do_work(); }
651 fn do_work() { teˇst(); }
652 "});
653 cx.update_editor(|editor, cx| {
654 update_go_to_definition_link(
655 editor,
656 &UpdateGoToDefinitionLink {
657 point: Some(hover_point),
658 cmd_held: false,
659 shift_held: false,
660 },
661 cx,
662 );
663 });
664 cx.foreground().run_until_parked();
665
666 // Assert no link highlights
667 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
668 fn test() { do_work(); }
669 fn do_work() { test(); }
670 "});
671
672 let symbol_range = cx.lsp_range(indoc! {"
673 fn test() { do_work(); }
674 fn do_work() { «test»(); }
675 "});
676 let target_range = cx.lsp_range(indoc! {"
677 fn «test»() { do_work(); }
678 fn do_work() { test(); }
679 "});
680
681 let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
682 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
683 lsp::LocationLink {
684 origin_selection_range: Some(symbol_range),
685 target_uri: url,
686 target_range,
687 target_selection_range: target_range,
688 },
689 ])))
690 });
691 cx.update_editor(|editor, cx| {
692 cmd_shift_changed(
693 editor,
694 &CmdShiftChanged {
695 cmd_down: true,
696 shift_down: false,
697 },
698 cx,
699 );
700 });
701 requests.next().await;
702 cx.foreground().run_until_parked();
703
704 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
705 fn test() { do_work(); }
706 fn do_work() { «test»(); }
707 "});
708
709 // Deactivating the window dismisses the highlight
710 cx.update_workspace(|workspace, cx| {
711 workspace.on_window_activation_changed(false, cx);
712 });
713 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
714 fn test() { do_work(); }
715 fn do_work() { test(); }
716 "});
717
718 // Moving the mouse restores the highlights.
719 cx.update_editor(|editor, cx| {
720 update_go_to_definition_link(
721 editor,
722 &UpdateGoToDefinitionLink {
723 point: Some(hover_point),
724 cmd_held: true,
725 shift_held: false,
726 },
727 cx,
728 );
729 });
730 cx.foreground().run_until_parked();
731 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
732 fn test() { do_work(); }
733 fn do_work() { «test»(); }
734 "});
735
736 // Moving again within the same symbol range doesn't re-request
737 let hover_point = cx.display_point(indoc! {"
738 fn test() { do_work(); }
739 fn do_work() { tesˇt(); }
740 "});
741 cx.update_editor(|editor, cx| {
742 update_go_to_definition_link(
743 editor,
744 &UpdateGoToDefinitionLink {
745 point: Some(hover_point),
746 cmd_held: true,
747 shift_held: false,
748 },
749 cx,
750 );
751 });
752 cx.foreground().run_until_parked();
753 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
754 fn test() { do_work(); }
755 fn do_work() { «test»(); }
756 "});
757
758 // Cmd click with existing definition doesn't re-request and dismisses highlight
759 cx.update_workspace(|workspace, cx| {
760 go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
761 });
762 // Assert selection moved to to definition
763 cx.lsp
764 .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
765 // Empty definition response to make sure we aren't hitting the lsp and using
766 // the cached location instead
767 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
768 });
769 cx.assert_editor_state(indoc! {"
770 fn «testˇ»() { do_work(); }
771 fn do_work() { test(); }
772 "});
773
774 // Assert no link highlights after jump
775 cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
776 fn test() { do_work(); }
777 fn do_work() { test(); }
778 "});
779
780 // Cmd click without existing definition requests and jumps
781 let hover_point = cx.display_point(indoc! {"
782 fn test() { do_wˇork(); }
783 fn do_work() { test(); }
784 "});
785 let target_range = cx.lsp_range(indoc! {"
786 fn test() { do_work(); }
787 fn «do_work»() { test(); }
788 "});
789
790 let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
791 Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
792 lsp::LocationLink {
793 origin_selection_range: None,
794 target_uri: url,
795 target_range,
796 target_selection_range: target_range,
797 },
798 ])))
799 });
800 cx.update_workspace(|workspace, cx| {
801 go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
802 });
803 requests.next().await;
804 cx.foreground().run_until_parked();
805 cx.assert_editor_state(indoc! {"
806 fn test() { do_work(); }
807 fn «do_workˇ»() { test(); }
808 "});
809 }
810}