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