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::{Modifiers, 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                    modifiers: Modifiers {
435                        cmd: true,
436                        ..Default::default()
437                    },
438                    ..Default::default()
439                },
440                cx,
441            );
442        });
443        // Assert no link highlights
444        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
445            struct A;
446            let variable = A;
447        "});
448
449        // Cmd+shift click without existing definition requests and jumps
450        let hover_point = cx.display_point(indoc! {"
451            struct A;
452            let vˇariable = A;
453        "});
454        let target_range = cx.lsp_range(indoc! {"
455            struct «A»;
456            let variable = A;
457        "});
458
459        let mut requests =
460            cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
461                Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
462                    lsp::LocationLink {
463                        origin_selection_range: None,
464                        target_uri: url,
465                        target_range,
466                        target_selection_range: target_range,
467                    },
468                ])))
469            });
470
471        cx.update_workspace(|workspace, cx| {
472            go_to_fetched_type_definition(
473                workspace,
474                &GoToFetchedTypeDefinition { point: hover_point },
475                cx,
476            );
477        });
478        requests.next().await;
479        cx.foreground().run_until_parked();
480
481        cx.assert_editor_state(indoc! {"
482            struct «Aˇ»;
483            let variable = A;
484        "});
485    }
486
487    #[gpui::test]
488    async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
489        let mut cx = EditorLspTestContext::new_rust(
490            lsp::ServerCapabilities {
491                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
492                ..Default::default()
493            },
494            cx,
495        )
496        .await;
497
498        cx.set_state(indoc! {"
499            fn ˇtest() { do_work(); }
500            fn do_work() { test(); }
501        "});
502
503        // Basic hold cmd, expect highlight in region if response contains definition
504        let hover_point = cx.display_point(indoc! {"
505            fn test() { do_wˇork(); }
506            fn do_work() { test(); }
507        "});
508        let symbol_range = cx.lsp_range(indoc! {"
509            fn test() { «do_work»(); }
510            fn do_work() { test(); }
511        "});
512        let target_range = cx.lsp_range(indoc! {"
513            fn test() { do_work(); }
514            fn «do_work»() { test(); }
515        "});
516
517        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
518            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
519                lsp::LocationLink {
520                    origin_selection_range: Some(symbol_range),
521                    target_uri: url.clone(),
522                    target_range,
523                    target_selection_range: target_range,
524                },
525            ])))
526        });
527
528        cx.update_editor(|editor, cx| {
529            update_go_to_definition_link(
530                editor,
531                &UpdateGoToDefinitionLink {
532                    point: Some(hover_point),
533                    cmd_held: true,
534                    shift_held: false,
535                },
536                cx,
537            );
538        });
539        requests.next().await;
540        cx.foreground().run_until_parked();
541        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
542            fn test() { «do_work»(); }
543            fn do_work() { test(); }
544        "});
545
546        // Unpress cmd causes highlight to go away
547        cx.update_editor(|editor, cx| {
548            editor.modifiers_changed(&Default::default(), cx);
549        });
550
551        // Assert no link highlights
552        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
553            fn test() { do_work(); }
554            fn do_work() { test(); }
555        "});
556
557        // Response without source range still highlights word
558        cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None);
559        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
560            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
561                lsp::LocationLink {
562                    // No origin range
563                    origin_selection_range: None,
564                    target_uri: url.clone(),
565                    target_range,
566                    target_selection_range: target_range,
567                },
568            ])))
569        });
570        cx.update_editor(|editor, cx| {
571            update_go_to_definition_link(
572                editor,
573                &UpdateGoToDefinitionLink {
574                    point: Some(hover_point),
575                    cmd_held: true,
576                    shift_held: false,
577                },
578                cx,
579            );
580        });
581        requests.next().await;
582        cx.foreground().run_until_parked();
583
584        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
585            fn test() { «do_work»(); }
586            fn do_work() { test(); }
587        "});
588
589        // Moving mouse to location with no response dismisses highlight
590        let hover_point = cx.display_point(indoc! {"
591            fˇn test() { do_work(); }
592            fn do_work() { test(); }
593        "});
594        let mut requests = cx
595            .lsp
596            .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
597                // No definitions returned
598                Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
599            });
600        cx.update_editor(|editor, cx| {
601            update_go_to_definition_link(
602                editor,
603                &UpdateGoToDefinitionLink {
604                    point: Some(hover_point),
605                    cmd_held: true,
606                    shift_held: false,
607                },
608                cx,
609            );
610        });
611        requests.next().await;
612        cx.foreground().run_until_parked();
613
614        // Assert no link highlights
615        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
616            fn test() { do_work(); }
617            fn do_work() { test(); }
618        "});
619
620        // Move mouse without cmd and then pressing cmd triggers highlight
621        let hover_point = cx.display_point(indoc! {"
622            fn test() { do_work(); }
623            fn do_work() { teˇst(); }
624        "});
625        cx.update_editor(|editor, cx| {
626            update_go_to_definition_link(
627                editor,
628                &UpdateGoToDefinitionLink {
629                    point: Some(hover_point),
630                    cmd_held: false,
631                    shift_held: false,
632                },
633                cx,
634            );
635        });
636        cx.foreground().run_until_parked();
637
638        // Assert no link highlights
639        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
640            fn test() { do_work(); }
641            fn do_work() { test(); }
642        "});
643
644        let symbol_range = cx.lsp_range(indoc! {"
645            fn test() { do_work(); }
646            fn do_work() { «test»(); }
647        "});
648        let target_range = cx.lsp_range(indoc! {"
649            fn «test»() { do_work(); }
650            fn do_work() { test(); }
651        "});
652
653        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
654            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
655                lsp::LocationLink {
656                    origin_selection_range: Some(symbol_range),
657                    target_uri: url,
658                    target_range,
659                    target_selection_range: target_range,
660                },
661            ])))
662        });
663        cx.update_editor(|editor, cx| {
664            editor.modifiers_changed(
665                &ModifiersChangedEvent {
666                    modifiers: Modifiers {
667                        cmd: true,
668                        ..Default::default()
669                    },
670                },
671                cx,
672            );
673        });
674        requests.next().await;
675        cx.foreground().run_until_parked();
676
677        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
678            fn test() { do_work(); }
679            fn do_work() { «test»(); }
680        "});
681
682        // Deactivating the window dismisses the highlight
683        cx.update_workspace(|workspace, cx| {
684            workspace.on_window_activation_changed(false, cx);
685        });
686        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
687            fn test() { do_work(); }
688            fn do_work() { test(); }
689        "});
690
691        // Moving the mouse restores the highlights.
692        cx.update_editor(|editor, cx| {
693            update_go_to_definition_link(
694                editor,
695                &UpdateGoToDefinitionLink {
696                    point: Some(hover_point),
697                    cmd_held: true,
698                    shift_held: false,
699                },
700                cx,
701            );
702        });
703        cx.foreground().run_until_parked();
704        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
705            fn test() { do_work(); }
706            fn do_work() { «test»(); }
707        "});
708
709        // Moving again within the same symbol range doesn't re-request
710        let hover_point = cx.display_point(indoc! {"
711            fn test() { do_work(); }
712            fn do_work() { tesˇt(); }
713        "});
714        cx.update_editor(|editor, cx| {
715            update_go_to_definition_link(
716                editor,
717                &UpdateGoToDefinitionLink {
718                    point: Some(hover_point),
719                    cmd_held: true,
720                    shift_held: false,
721                },
722                cx,
723            );
724        });
725        cx.foreground().run_until_parked();
726        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
727            fn test() { do_work(); }
728            fn do_work() { «test»(); }
729        "});
730
731        // Cmd click with existing definition doesn't re-request and dismisses highlight
732        cx.update_workspace(|workspace, cx| {
733            go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
734        });
735        // Assert selection moved to to definition
736        cx.lsp
737            .handle_request::<GotoDefinition, _, _>(move |_, _| async move {
738                // Empty definition response to make sure we aren't hitting the lsp and using
739                // the cached location instead
740                Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
741            });
742        cx.assert_editor_state(indoc! {"
743            fn «testˇ»() { do_work(); }
744            fn do_work() { test(); }
745        "});
746
747        // Assert no link highlights after jump
748        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
749            fn test() { do_work(); }
750            fn do_work() { test(); }
751        "});
752
753        // Cmd click without existing definition requests and jumps
754        let hover_point = cx.display_point(indoc! {"
755            fn test() { do_wˇork(); }
756            fn do_work() { test(); }
757        "});
758        let target_range = cx.lsp_range(indoc! {"
759            fn test() { do_work(); }
760            fn «do_work»() { test(); }
761        "});
762
763        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
764            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
765                lsp::LocationLink {
766                    origin_selection_range: None,
767                    target_uri: url,
768                    target_range,
769                    target_selection_range: target_range,
770                },
771            ])))
772        });
773        cx.update_workspace(|workspace, cx| {
774            go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
775        });
776        requests.next().await;
777        cx.foreground().run_until_parked();
778        cx.assert_editor_state(indoc! {"
779            fn test() { do_work(); }
780            fn «do_workˇ»() { test(); }
781        "});
782
783        // 1. We have a pending selection, mouse point is over a symbol that we have a response for, hitting cmd and nothing happens
784        // 2. Selection is completed, hovering
785        let hover_point = cx.display_point(indoc! {"
786            fn test() { do_wˇork(); }
787            fn do_work() { test(); }
788        "});
789        let target_range = cx.lsp_range(indoc! {"
790            fn test() { do_work(); }
791            fn «do_work»() { test(); }
792        "});
793        let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
794            Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
795                lsp::LocationLink {
796                    origin_selection_range: None,
797                    target_uri: url,
798                    target_range,
799                    target_selection_range: target_range,
800                },
801            ])))
802        });
803
804        // create a pending selection
805        let selection_range = cx.ranges(indoc! {"
806            fn «test() { do_w»ork(); }
807            fn do_work() { test(); }
808        "})[0]
809            .clone();
810        cx.update_editor(|editor, cx| {
811            let snapshot = editor.buffer().read(cx).snapshot(cx);
812            let anchor_range = snapshot.anchor_before(selection_range.start)
813                ..snapshot.anchor_after(selection_range.end);
814            editor.change_selections(Some(crate::Autoscroll::fit()), cx, |s| {
815                s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character)
816            });
817        });
818        cx.update_editor(|editor, cx| {
819            update_go_to_definition_link(
820                editor,
821                &UpdateGoToDefinitionLink {
822                    point: Some(hover_point),
823                    cmd_held: true,
824                    shift_held: false,
825                },
826                cx,
827            );
828        });
829        cx.foreground().run_until_parked();
830        assert!(requests.try_next().is_err());
831        cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
832            fn test() { do_work(); }
833            fn do_work() { test(); }
834        "});
835        cx.foreground().run_until_parked();
836    }
837}