link_go_to_definition.rs

  1use std::ops::Range;
  2
  3use gpui::{impl_internal_actions, AppContext, 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 AppContext) {
 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::{
362        platform::{self, Modifiers, ModifiersChangedEvent},
363        View,
364    };
365    use indoc::indoc;
366    use lsp::request::{GotoDefinition, GotoTypeDefinition};
367
368    use crate::test::editor_lsp_test_context::EditorLspTestContext;
369
370    use super::*;
371
372    #[gpui::test]
373    async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
374        let mut cx = EditorLspTestContext::new_rust(
375            lsp::ServerCapabilities {
376                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
377                ..Default::default()
378            },
379            cx,
380        )
381        .await;
382
383        cx.set_state(indoc! {"
384            struct A;
385            let vˇariable = A;
386        "});
387
388        // Basic hold cmd+shift, expect highlight in region if response contains type definition
389        let hover_point = cx.display_point(indoc! {"
390            struct A;
391            let vˇariable = A;
392        "});
393        let symbol_range = cx.lsp_range(indoc! {"
394            struct A;
395            let «variable» = A;
396        "});
397        let target_range = cx.lsp_range(indoc! {"
398            struct «A»;
399            let variable = A;
400        "});
401
402        let mut requests =
403            cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
404                Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
405                    lsp::LocationLink {
406                        origin_selection_range: Some(symbol_range),
407                        target_uri: url.clone(),
408                        target_range,
409                        target_selection_range: target_range,
410                    },
411                ])))
412            });
413
414        // Press cmd+shift to trigger highlight
415        cx.update_editor(|editor, cx| {
416            update_go_to_definition_link(
417                editor,
418                &UpdateGoToDefinitionLink {
419                    point: Some(hover_point),
420                    cmd_held: true,
421                    shift_held: true,
422                },
423                cx,
424            );
425        });
426        requests.next().await;
427        cx.foreground().run_until_parked();
428        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
429            struct A;
430            let «variable» = A;
431        "});
432
433        // Unpress shift causes highlight to go away (normal goto-definition is not valid here)
434        cx.update_editor(|editor, cx| {
435            editor.modifiers_changed(
436                &platform::ModifiersChangedEvent {
437                    modifiers: Modifiers {
438                        cmd: true,
439                        ..Default::default()
440                    },
441                    ..Default::default()
442                },
443                cx,
444            );
445        });
446        // Assert no link highlights
447        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
448            struct A;
449            let variable = A;
450        "});
451
452        // Cmd+shift click without existing definition requests and jumps
453        let hover_point = cx.display_point(indoc! {"
454            struct A;
455            let vˇariable = A;
456        "});
457        let target_range = cx.lsp_range(indoc! {"
458            struct «A»;
459            let variable = A;
460        "});
461
462        let mut requests =
463            cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
464                Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
465                    lsp::LocationLink {
466                        origin_selection_range: None,
467                        target_uri: url,
468                        target_range,
469                        target_selection_range: target_range,
470                    },
471                ])))
472            });
473
474        cx.update_workspace(|workspace, cx| {
475            go_to_fetched_type_definition(
476                workspace,
477                &GoToFetchedTypeDefinition { point: hover_point },
478                cx,
479            );
480        });
481        requests.next().await;
482        cx.foreground().run_until_parked();
483
484        cx.assert_editor_state(indoc! {"
485            struct «Aˇ»;
486            let variable = A;
487        "});
488    }
489
490    #[gpui::test]
491    async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
492        let mut cx = EditorLspTestContext::new_rust(
493            lsp::ServerCapabilities {
494                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
495                ..Default::default()
496            },
497            cx,
498        )
499        .await;
500
501        cx.set_state(indoc! {"
502            fn ˇtest() { do_work(); }
503            fn do_work() { test(); }
504        "});
505
506        // Basic hold cmd, expect highlight in region if response contains definition
507        let hover_point = cx.display_point(indoc! {"
508            fn test() { do_wˇork(); }
509            fn do_work() { test(); }
510        "});
511        let symbol_range = cx.lsp_range(indoc! {"
512            fn test() { «do_work»(); }
513            fn do_work() { test(); }
514        "});
515        let target_range = cx.lsp_range(indoc! {"
516            fn test() { do_work(); }
517            fn «do_work»() { test(); }
518        "});
519
520        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
521            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
522                lsp::LocationLink {
523                    origin_selection_range: Some(symbol_range),
524                    target_uri: url.clone(),
525                    target_range,
526                    target_selection_range: target_range,
527                },
528            ])))
529        });
530
531        cx.update_editor(|editor, cx| {
532            update_go_to_definition_link(
533                editor,
534                &UpdateGoToDefinitionLink {
535                    point: Some(hover_point),
536                    cmd_held: true,
537                    shift_held: false,
538                },
539                cx,
540            );
541        });
542        requests.next().await;
543        cx.foreground().run_until_parked();
544        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
545            fn test() { «do_work»(); }
546            fn do_work() { test(); }
547        "});
548
549        // Unpress cmd causes highlight to go away
550        cx.update_editor(|editor, cx| {
551            editor.modifiers_changed(&Default::default(), cx);
552        });
553
554        // Assert no link highlights
555        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
556            fn test() { do_work(); }
557            fn do_work() { test(); }
558        "});
559
560        // Response without source range still highlights word
561        cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None);
562        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
563            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
564                lsp::LocationLink {
565                    // No origin range
566                    origin_selection_range: None,
567                    target_uri: url.clone(),
568                    target_range,
569                    target_selection_range: target_range,
570                },
571            ])))
572        });
573        cx.update_editor(|editor, cx| {
574            update_go_to_definition_link(
575                editor,
576                &UpdateGoToDefinitionLink {
577                    point: Some(hover_point),
578                    cmd_held: true,
579                    shift_held: false,
580                },
581                cx,
582            );
583        });
584        requests.next().await;
585        cx.foreground().run_until_parked();
586
587        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
588            fn test() { «do_work»(); }
589            fn do_work() { test(); }
590        "});
591
592        // Moving mouse to location with no response dismisses highlight
593        let hover_point = cx.display_point(indoc! {"
594            fˇn test() { do_work(); }
595            fn do_work() { test(); }
596        "});
597        let mut requests = cx
598            .lsp
599            .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
600                // No definitions returned
601                Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
602            });
603        cx.update_editor(|editor, cx| {
604            update_go_to_definition_link(
605                editor,
606                &UpdateGoToDefinitionLink {
607                    point: Some(hover_point),
608                    cmd_held: true,
609                    shift_held: false,
610                },
611                cx,
612            );
613        });
614        requests.next().await;
615        cx.foreground().run_until_parked();
616
617        // Assert no link highlights
618        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
619            fn test() { do_work(); }
620            fn do_work() { test(); }
621        "});
622
623        // Move mouse without cmd and then pressing cmd triggers highlight
624        let hover_point = cx.display_point(indoc! {"
625            fn test() { do_work(); }
626            fn do_work() { teˇst(); }
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: false,
634                    shift_held: false,
635                },
636                cx,
637            );
638        });
639        cx.foreground().run_until_parked();
640
641        // Assert no link highlights
642        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
643            fn test() { do_work(); }
644            fn do_work() { test(); }
645        "});
646
647        let symbol_range = cx.lsp_range(indoc! {"
648            fn test() { do_work(); }
649            fn do_work() { «test»(); }
650        "});
651        let target_range = cx.lsp_range(indoc! {"
652            fn «test»() { do_work(); }
653            fn do_work() { test(); }
654        "});
655
656        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
657            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
658                lsp::LocationLink {
659                    origin_selection_range: Some(symbol_range),
660                    target_uri: url,
661                    target_range,
662                    target_selection_range: target_range,
663                },
664            ])))
665        });
666        cx.update_editor(|editor, cx| {
667            editor.modifiers_changed(
668                &ModifiersChangedEvent {
669                    modifiers: Modifiers {
670                        cmd: true,
671                        ..Default::default()
672                    },
673                },
674                cx,
675            );
676        });
677        requests.next().await;
678        cx.foreground().run_until_parked();
679
680        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
681            fn test() { do_work(); }
682            fn do_work() { «test»(); }
683        "});
684
685        // Deactivating the window dismisses the highlight
686        cx.update_workspace(|workspace, cx| {
687            workspace.on_window_activation_changed(false, cx);
688        });
689        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
690            fn test() { do_work(); }
691            fn do_work() { test(); }
692        "});
693
694        // Moving the mouse restores the highlights.
695        cx.update_editor(|editor, cx| {
696            update_go_to_definition_link(
697                editor,
698                &UpdateGoToDefinitionLink {
699                    point: Some(hover_point),
700                    cmd_held: true,
701                    shift_held: false,
702                },
703                cx,
704            );
705        });
706        cx.foreground().run_until_parked();
707        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
708            fn test() { do_work(); }
709            fn do_work() { «test»(); }
710        "});
711
712        // Moving again within the same 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        // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens
787        // 2. Selection is completed, hovering
788        let hover_point = cx.display_point(indoc! {"
789            fn test() { do_wˇork(); }
790            fn do_work() { test(); }
791        "});
792        let target_range = cx.lsp_range(indoc! {"
793            fn test() { do_work(); }
794            fn «do_work»() { test(); }
795        "});
796        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
797            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
798                lsp::LocationLink {
799                    origin_selection_range: None,
800                    target_uri: url,
801                    target_range,
802                    target_selection_range: target_range,
803                },
804            ])))
805        });
806
807        // create a pending selection
808        let selection_range = cx.ranges(indoc! {"
809            fn «test() { do_w»ork(); }
810            fn do_work() { test(); }
811        "})[0]
812            .clone();
813        cx.update_editor(|editor, cx| {
814            let snapshot = editor.buffer().read(cx).snapshot(cx);
815            let anchor_range = snapshot.anchor_before(selection_range.start)
816                ..snapshot.anchor_after(selection_range.end);
817            editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| {
818                s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character)
819            });
820        });
821        cx.update_editor(|editor, cx| {
822            update_go_to_definition_link(
823                editor,
824                &UpdateGoToDefinitionLink {
825                    point: Some(hover_point),
826                    cmd_held: true,
827                    shift_held: false,
828                },
829                cx,
830            );
831        });
832        cx.foreground().run_until_parked();
833        assert!(requests.try_next().is_err());
834        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
835            fn test() { do_work(); }
836            fn do_work() { test(); }
837        "});
838        cx.foreground().run_until_parked();
839    }
840}