link_go_to_definition.rs

  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, Event, GoToDefinition, GoToTypeDefinition,
 12    Select, 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(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.clone(), 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 => {
208                        project.definition(&buffer, buffer_position.clone(), cx)
209                    }
210
211                    LinkDefinitionKind::Type => {
212                        project.type_definition(&buffer, buffer_position.clone(), cx)
213                    }
214                })
215            });
216
217            let result = definition_request.await.ok().map(|definition_result| {
218                (
219                    definition_result.iter().find_map(|link| {
220                        link.origin.as_ref().map(|origin| {
221                            let start = snapshot
222                                .buffer_snapshot
223                                .anchor_in_excerpt(excerpt_id.clone(), origin.range.start);
224                            let end = snapshot
225                                .buffer_snapshot
226                                .anchor_in_excerpt(excerpt_id.clone(), origin.range.end);
227
228                            start..end
229                        })
230                    }),
231                    definition_result,
232                )
233            });
234
235            if let Some(this) = this.upgrade(&cx) {
236                this.update(&mut cx, |this, cx| {
237                    // Clear any existing highlights
238                    this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
239                    this.link_go_to_definition_state.kind = Some(definition_kind);
240                    this.link_go_to_definition_state.symbol_range = result
241                        .as_ref()
242                        .and_then(|(symbol_range, _)| symbol_range.clone());
243
244                    if let Some((symbol_range, definitions)) = result {
245                        this.link_go_to_definition_state.definitions = definitions.clone();
246
247                        let buffer_snapshot = buffer.read(cx).snapshot();
248                        // Only show highlight if there exists a definition to jump to that doesn't contain
249                        // the current location.
250                        if definitions.iter().any(|definition| {
251                            let target = &definition.target;
252                            if target.buffer == buffer {
253                                let range = &target.range;
254                                // Expand range by one character as lsp definition ranges include positions adjacent
255                                // but not contained by the symbol range
256                                let start = buffer_snapshot.clip_offset(
257                                    range.start.to_offset(&buffer_snapshot).saturating_sub(1),
258                                    Bias::Left,
259                                );
260                                let end = buffer_snapshot.clip_offset(
261                                    range.end.to_offset(&buffer_snapshot) + 1,
262                                    Bias::Right,
263                                );
264                                let offset = buffer_position.to_offset(&buffer_snapshot);
265                                !(start <= offset && end >= offset)
266                            } else {
267                                true
268                            }
269                        }) {
270                            // If no symbol range returned from language server, use the surrounding word.
271                            let highlight_range = symbol_range.unwrap_or_else(|| {
272                                let snapshot = &snapshot.buffer_snapshot;
273                                let (offset_range, _) = snapshot.surrounding_word(trigger_point);
274
275                                snapshot.anchor_before(offset_range.start)
276                                    ..snapshot.anchor_after(offset_range.end)
277                            });
278
279                            // Highlight symbol using theme link definition highlight style
280                            let style = cx.global::<Settings>().theme.editor.link_definition;
281                            this.highlight_text::<LinkGoToDefinitionState>(
282                                vec![highlight_range],
283                                style,
284                                cx,
285                            )
286                        } else {
287                            hide_link_definition(this, cx);
288                        }
289                    }
290                })
291            }
292
293            Ok::<_, anyhow::Error>(())
294        }
295        .log_err()
296    });
297
298    editor.link_go_to_definition_state.task = Some(task);
299}
300
301pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
302    if editor.link_go_to_definition_state.symbol_range.is_some()
303        || !editor.link_go_to_definition_state.definitions.is_empty()
304    {
305        editor.link_go_to_definition_state.symbol_range.take();
306        editor.link_go_to_definition_state.definitions.clear();
307        cx.notify();
308    }
309
310    editor.link_go_to_definition_state.task = None;
311
312    editor.clear_text_highlights::<LinkGoToDefinitionState>(cx);
313}
314
315pub fn go_to_fetched_definition(
316    workspace: &mut Workspace,
317    &GoToFetchedDefinition { point }: &GoToFetchedDefinition,
318    cx: &mut ViewContext<Workspace>,
319) {
320    go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, workspace, point, cx);
321}
322
323pub fn go_to_fetched_type_definition(
324    workspace: &mut Workspace,
325    &GoToFetchedTypeDefinition { point }: &GoToFetchedTypeDefinition,
326    cx: &mut ViewContext<Workspace>,
327) {
328    go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, workspace, point, cx);
329}
330
331fn go_to_fetched_definition_of_kind(
332    kind: LinkDefinitionKind,
333    workspace: &mut Workspace,
334    point: DisplayPoint,
335    cx: &mut ViewContext<Workspace>,
336) {
337    let active_item = workspace.active_item(cx);
338    let editor_handle = if let Some(editor) = active_item
339        .as_ref()
340        .and_then(|item| item.act_as::<Editor>(cx))
341    {
342        editor
343    } else {
344        return;
345    };
346
347    let (cached_definitions, cached_definitions_kind) = editor_handle.update(cx, |editor, cx| {
348        let definitions = editor.link_go_to_definition_state.definitions.clone();
349        hide_link_definition(editor, cx);
350        (definitions, editor.link_go_to_definition_state.kind)
351    });
352
353    let is_correct_kind = cached_definitions_kind == Some(kind);
354    if !cached_definitions.is_empty() && is_correct_kind {
355        editor_handle.update(cx, |editor, cx| {
356            if !editor.focused {
357                cx.focus_self();
358                cx.emit(Event::Activate);
359            }
360        });
361
362        Editor::navigate_to_definitions(workspace, editor_handle, cached_definitions, cx);
363    } else {
364        editor_handle.update(cx, |editor, cx| {
365            editor.select(
366                &Select(SelectPhase::Begin {
367                    position: point.clone(),
368                    add: false,
369                    click_count: 1,
370                }),
371                cx,
372            );
373        });
374
375        match kind {
376            LinkDefinitionKind::Symbol => Editor::go_to_definition(workspace, &GoToDefinition, cx),
377
378            LinkDefinitionKind::Type => {
379                Editor::go_to_type_definition(workspace, &GoToTypeDefinition, cx)
380            }
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use futures::StreamExt;
388    use indoc::indoc;
389    use lsp::request::{GotoDefinition, GotoTypeDefinition};
390
391    use crate::test::EditorLspTestContext;
392
393    use super::*;
394
395    #[gpui::test]
396    async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
397        let mut cx = EditorLspTestContext::new_rust(
398            lsp::ServerCapabilities {
399                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
400                ..Default::default()
401            },
402            cx,
403        )
404        .await;
405
406        cx.set_state(indoc! {"
407            struct A;
408            let vˇariable = A;
409        "});
410
411        // Basic hold cmd+shift, expect highlight in region if response contains type definition
412        let hover_point = cx.display_point(indoc! {"
413            struct A;
414            let vˇariable = A;
415        "});
416        let symbol_range = cx.lsp_range(indoc! {"
417            struct A;
418            let «variable» = A;
419        "});
420        let target_range = cx.lsp_range(indoc! {"
421            struct «A»;
422            let variable = A;
423        "});
424
425        let mut requests =
426            cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
427                Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
428                    lsp::LocationLink {
429                        origin_selection_range: Some(symbol_range),
430                        target_uri: url.clone(),
431                        target_range,
432                        target_selection_range: target_range,
433                    },
434                ])))
435            });
436
437        // Press cmd+shift to trigger highlight
438        cx.update_editor(|editor, cx| {
439            update_go_to_definition_link(
440                editor,
441                &UpdateGoToDefinitionLink {
442                    point: Some(hover_point),
443                    cmd_held: true,
444                    shift_held: true,
445                },
446                cx,
447            );
448        });
449        requests.next().await;
450        cx.foreground().run_until_parked();
451        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
452            struct A;
453            let «variable» = A;
454        "});
455
456        // Unpress shift causes highlight to go away (normal goto-definition is not valid here)
457        cx.update_editor(|editor, cx| {
458            cmd_shift_changed(
459                editor,
460                &CmdShiftChanged {
461                    cmd_down: true,
462                    shift_down: false,
463                },
464                cx,
465            );
466        });
467        // Assert no link highlights
468        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
469            struct A;
470            let variable = A;
471        "});
472
473        // Cmd+shift click without existing definition requests and jumps
474        let hover_point = cx.display_point(indoc! {"
475            struct A;
476            let vˇariable = A;
477        "});
478        let target_range = cx.lsp_range(indoc! {"
479            struct «A»;
480            let variable = A;
481        "});
482
483        let mut requests =
484            cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
485                Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
486                    lsp::LocationLink {
487                        origin_selection_range: None,
488                        target_uri: url,
489                        target_range,
490                        target_selection_range: target_range,
491                    },
492                ])))
493            });
494
495        cx.update_workspace(|workspace, cx| {
496            go_to_fetched_type_definition(
497                workspace,
498                &GoToFetchedTypeDefinition { point: hover_point },
499                cx,
500            );
501        });
502        requests.next().await;
503        cx.foreground().run_until_parked();
504
505        cx.assert_editor_state(indoc! {"
506            struct «Aˇ»;
507            let variable = A;
508        "});
509    }
510
511    #[gpui::test]
512    async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
513        let mut cx = EditorLspTestContext::new_rust(
514            lsp::ServerCapabilities {
515                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
516                ..Default::default()
517            },
518            cx,
519        )
520        .await;
521
522        cx.set_state(indoc! {"
523            fn ˇtest() { do_work(); }
524            fn do_work() { test(); }
525        "});
526
527        // Basic hold cmd, expect highlight in region if response contains definition
528        let hover_point = cx.display_point(indoc! {"
529            fn test() { do_wˇork(); }
530            fn do_work() { test(); }
531        "});
532        let symbol_range = cx.lsp_range(indoc! {"
533            fn test() { «do_work»(); }
534            fn do_work() { test(); }
535        "});
536        let target_range = cx.lsp_range(indoc! {"
537            fn test() { do_work(); }
538            fn «do_work»() { test(); }
539        "});
540
541        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
542            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
543                lsp::LocationLink {
544                    origin_selection_range: Some(symbol_range),
545                    target_uri: url.clone(),
546                    target_range,
547                    target_selection_range: target_range,
548                },
549            ])))
550        });
551
552        cx.update_editor(|editor, cx| {
553            update_go_to_definition_link(
554                editor,
555                &UpdateGoToDefinitionLink {
556                    point: Some(hover_point),
557                    cmd_held: true,
558                    shift_held: false,
559                },
560                cx,
561            );
562        });
563        requests.next().await;
564        cx.foreground().run_until_parked();
565        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
566            fn test() { «do_work»(); }
567            fn do_work() { test(); }
568        "});
569
570        // Unpress cmd causes highlight to go away
571        cx.update_editor(|editor, cx| {
572            cmd_shift_changed(
573                editor,
574                &CmdShiftChanged {
575                    cmd_down: false,
576                    shift_down: false,
577                },
578                cx,
579            );
580        });
581
582        // Assert no link highlights
583        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
584            fn test() { do_work(); }
585            fn do_work() { test(); }
586        "});
587
588        // Response without source range still highlights word
589        cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None);
590        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
591            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
592                lsp::LocationLink {
593                    // No origin range
594                    origin_selection_range: None,
595                    target_uri: url.clone(),
596                    target_range,
597                    target_selection_range: target_range,
598                },
599            ])))
600        });
601        cx.update_editor(|editor, cx| {
602            update_go_to_definition_link(
603                editor,
604                &UpdateGoToDefinitionLink {
605                    point: Some(hover_point),
606                    cmd_held: true,
607                    shift_held: false,
608                },
609                cx,
610            );
611        });
612        requests.next().await;
613        cx.foreground().run_until_parked();
614
615        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
616            fn test() { «do_work»(); }
617            fn do_work() { test(); }
618        "});
619
620        // Moving mouse to location with no response dismisses highlight
621        let hover_point = cx.display_point(indoc! {"
622            fˇn test() { do_work(); }
623            fn do_work() { test(); }
624        "});
625        let mut requests = cx
626            .lsp
627            .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
628                // No definitions returned
629                Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
630            });
631        cx.update_editor(|editor, cx| {
632            update_go_to_definition_link(
633                editor,
634                &UpdateGoToDefinitionLink {
635                    point: Some(hover_point),
636                    cmd_held: true,
637                    shift_held: false,
638                },
639                cx,
640            );
641        });
642        requests.next().await;
643        cx.foreground().run_until_parked();
644
645        // Assert no link highlights
646        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
647            fn test() { do_work(); }
648            fn do_work() { test(); }
649        "});
650
651        // Move mouse without cmd and then pressing cmd triggers highlight
652        let hover_point = cx.display_point(indoc! {"
653            fn test() { do_work(); }
654            fn do_work() { teˇst(); }
655        "});
656        cx.update_editor(|editor, cx| {
657            update_go_to_definition_link(
658                editor,
659                &UpdateGoToDefinitionLink {
660                    point: Some(hover_point),
661                    cmd_held: false,
662                    shift_held: false,
663                },
664                cx,
665            );
666        });
667        cx.foreground().run_until_parked();
668
669        // Assert no link highlights
670        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
671            fn test() { do_work(); }
672            fn do_work() { test(); }
673        "});
674
675        let symbol_range = cx.lsp_range(indoc! {"
676            fn test() { do_work(); }
677            fn do_work() { «test»(); }
678        "});
679        let target_range = cx.lsp_range(indoc! {"
680            fn «test»() { do_work(); }
681            fn do_work() { test(); }
682        "});
683
684        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
685            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
686                lsp::LocationLink {
687                    origin_selection_range: Some(symbol_range),
688                    target_uri: url,
689                    target_range,
690                    target_selection_range: target_range,
691                },
692            ])))
693        });
694        cx.update_editor(|editor, cx| {
695            cmd_shift_changed(
696                editor,
697                &CmdShiftChanged {
698                    cmd_down: true,
699                    shift_down: false,
700                },
701                cx,
702            );
703        });
704        requests.next().await;
705        cx.foreground().run_until_parked();
706
707        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
708            fn test() { do_work(); }
709            fn do_work() { «test»(); }
710        "});
711
712        // Moving within symbol range doesn't re-request
713        let hover_point = cx.display_point(indoc! {"
714            fn test() { do_work(); }
715            fn do_work() { tesˇt(); }
716        "});
717        cx.update_editor(|editor, cx| {
718            update_go_to_definition_link(
719                editor,
720                &UpdateGoToDefinitionLink {
721                    point: Some(hover_point),
722                    cmd_held: true,
723                    shift_held: false,
724                },
725                cx,
726            );
727        });
728        cx.foreground().run_until_parked();
729        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
730            fn test() { do_work(); }
731            fn do_work() { «test»(); }
732        "});
733
734        // Cmd click with existing definition doesn't re-request and dismisses highlight
735        cx.update_workspace(|workspace, cx| {
736            go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
737        });
738        // Assert selection moved to to definition
739        cx.lsp
740            .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
741                // Empty definition response to make sure we aren't hitting the lsp and using
742                // the cached location instead
743                Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
744            });
745        cx.assert_editor_state(indoc! {"
746            fn «testˇ»() { do_work(); }
747            fn do_work() { test(); }
748        "});
749
750        // Assert no link highlights after jump
751        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
752            fn test() { do_work(); }
753            fn do_work() { test(); }
754        "});
755
756        // Cmd click without existing definition requests and jumps
757        let hover_point = cx.display_point(indoc! {"
758            fn test() { do_wˇork(); }
759            fn do_work() { test(); }
760        "});
761        let target_range = cx.lsp_range(indoc! {"
762            fn test() { do_work(); }
763            fn «do_work»() { test(); }
764        "});
765
766        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
767            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
768                lsp::LocationLink {
769                    origin_selection_range: None,
770                    target_uri: url,
771                    target_range,
772                    target_selection_range: target_range,
773                },
774            ])))
775        });
776        cx.update_workspace(|workspace, cx| {
777            go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
778        });
779        requests.next().await;
780        cx.foreground().run_until_parked();
781        cx.assert_editor_state(indoc! {"
782            fn test() { do_work(); }
783            fn «do_workˇ»() { test(); }
784        "});
785    }
786}