mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-28 21:32:39 +00:00
Merge pull request #1420 from zed-industries/goto-type-definition
Draft: Add "go to type definition" action
This commit is contained in:
commit
8cf56f8c6f
13 changed files with 791 additions and 363 deletions
|
@ -192,6 +192,7 @@
|
|||
"shift-f8": "editor::GoToPrevDiagnostic",
|
||||
"f2": "editor::Rename",
|
||||
"f12": "editor::GoToDefinition",
|
||||
"cmd-f12": "editor::GoToTypeDefinition",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
|
|
|
@ -187,6 +187,7 @@ actions!(
|
|||
SelectLargerSyntaxNode,
|
||||
SelectSmallerSyntaxNode,
|
||||
GoToDefinition,
|
||||
GoToTypeDefinition,
|
||||
MoveToEnclosingBracket,
|
||||
UndoSelection,
|
||||
RedoSelection,
|
||||
|
@ -297,6 +298,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||
cx.add_action(Editor::go_to_diagnostic);
|
||||
cx.add_action(Editor::go_to_prev_diagnostic);
|
||||
cx.add_action(Editor::go_to_definition);
|
||||
cx.add_action(Editor::go_to_type_definition);
|
||||
cx.add_action(Editor::page_up);
|
||||
cx.add_action(Editor::page_down);
|
||||
cx.add_action(Editor::fold);
|
||||
|
@ -895,6 +897,11 @@ pub struct NavigationData {
|
|||
|
||||
pub struct EditorCreated(pub ViewHandle<Editor>);
|
||||
|
||||
enum GotoDefinitionKind {
|
||||
Symbol,
|
||||
Type,
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn single_line(
|
||||
field_editor_style: Option<GetFieldEditorTheme>,
|
||||
|
@ -4693,6 +4700,22 @@ impl Editor {
|
|||
workspace: &mut Workspace,
|
||||
_: &GoToDefinition,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
Self::go_to_definition_of_kind(GotoDefinitionKind::Symbol, workspace, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_type_definition(
|
||||
workspace: &mut Workspace,
|
||||
_: &GoToTypeDefinition,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
Self::go_to_definition_of_kind(GotoDefinitionKind::Type, workspace, cx);
|
||||
}
|
||||
|
||||
fn go_to_definition_of_kind(
|
||||
kind: GotoDefinitionKind,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let active_item = workspace.active_item(cx);
|
||||
let editor_handle = if let Some(editor) = active_item
|
||||
|
@ -4714,7 +4737,11 @@ impl Editor {
|
|||
};
|
||||
|
||||
let project = workspace.project().clone();
|
||||
let definitions = project.update(cx, |project, cx| project.definition(&buffer, head, cx));
|
||||
let definitions = project.update(cx, |project, cx| match kind {
|
||||
GotoDefinitionKind::Symbol => project.definition(&buffer, head, cx),
|
||||
GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
|
||||
});
|
||||
|
||||
cx.spawn(|workspace, mut cx| async move {
|
||||
let definitions = definitions.await?;
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
|
|
|
@ -6,7 +6,9 @@ use super::{
|
|||
use crate::{
|
||||
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
|
||||
hover_popover::HoverAt,
|
||||
link_go_to_definition::{CmdChanged, GoToFetchedDefinition, UpdateGoToDefinitionLink},
|
||||
link_go_to_definition::{
|
||||
CmdShiftChanged, GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
|
||||
},
|
||||
mouse_context_menu::DeployMouseContextMenu,
|
||||
EditorStyle,
|
||||
};
|
||||
|
@ -122,7 +124,12 @@ impl EditorElement {
|
|||
if cmd && paint.text_bounds.contains_point(position) {
|
||||
let (point, overshoot) = paint.point_for_position(&self.snapshot(cx), layout, position);
|
||||
if overshoot.is_zero() {
|
||||
cx.dispatch_action(GoToFetchedDefinition { point });
|
||||
if shift {
|
||||
cx.dispatch_action(GoToFetchedTypeDefinition { point });
|
||||
} else {
|
||||
cx.dispatch_action(GoToFetchedDefinition { point });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -238,8 +245,12 @@ impl EditorElement {
|
|||
|
||||
fn mouse_moved(
|
||||
&self,
|
||||
position: Vector2F,
|
||||
cmd: bool,
|
||||
MouseMovedEvent {
|
||||
cmd,
|
||||
shift,
|
||||
position,
|
||||
..
|
||||
}: MouseMovedEvent,
|
||||
layout: &LayoutState,
|
||||
paint: &PaintState,
|
||||
cx: &mut EventContext,
|
||||
|
@ -260,6 +271,7 @@ impl EditorElement {
|
|||
cx.dispatch_action(UpdateGoToDefinitionLink {
|
||||
point,
|
||||
cmd_held: cmd,
|
||||
shift_held: shift,
|
||||
});
|
||||
|
||||
if paint
|
||||
|
@ -283,8 +295,11 @@ impl EditorElement {
|
|||
true
|
||||
}
|
||||
|
||||
fn modifiers_changed(&self, cmd: bool, cx: &mut EventContext) -> bool {
|
||||
cx.dispatch_action(CmdChanged { cmd_down: cmd });
|
||||
fn modifiers_changed(&self, event: ModifiersChangedEvent, cx: &mut EventContext) -> bool {
|
||||
cx.dispatch_action(CmdShiftChanged {
|
||||
cmd_down: event.cmd,
|
||||
shift_down: event.shift,
|
||||
});
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -1534,32 +1549,34 @@ impl Element for EditorElement {
|
|||
paint,
|
||||
cx,
|
||||
),
|
||||
|
||||
Event::MouseDown(MouseButtonEvent {
|
||||
button: MouseButton::Right,
|
||||
position,
|
||||
..
|
||||
}) => self.mouse_right_down(*position, layout, paint, cx),
|
||||
|
||||
Event::MouseUp(MouseButtonEvent {
|
||||
button: MouseButton::Left,
|
||||
position,
|
||||
..
|
||||
}) => self.mouse_up(*position, cx),
|
||||
|
||||
Event::MouseMoved(MouseMovedEvent {
|
||||
pressed_button: Some(MouseButton::Left),
|
||||
position,
|
||||
..
|
||||
}) => self.mouse_dragged(*position, layout, paint, cx),
|
||||
|
||||
Event::ScrollWheel(ScrollWheelEvent {
|
||||
position,
|
||||
delta,
|
||||
precise,
|
||||
}) => self.scroll(*position, *delta, *precise, layout, paint, cx),
|
||||
Event::ModifiersChanged(ModifiersChangedEvent { cmd, .. }) => {
|
||||
self.modifiers_changed(*cmd, cx)
|
||||
}
|
||||
Event::MouseMoved(MouseMovedEvent { position, cmd, .. }) => {
|
||||
self.mouse_moved(*position, *cmd, layout, paint, cx)
|
||||
}
|
||||
|
||||
&Event::ModifiersChanged(event) => self.modifiers_changed(event, cx),
|
||||
|
||||
&Event::MouseMoved(event) => self.mouse_moved(event, layout, paint, cx),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
@ -8,18 +8,21 @@ use util::TryFutureExt;
|
|||
use workspace::Workspace;
|
||||
|
||||
use crate::{
|
||||
Anchor, DisplayPoint, Editor, EditorSnapshot, Event, GoToDefinition, Select, SelectPhase,
|
||||
Anchor, DisplayPoint, Editor, EditorSnapshot, Event, GoToDefinition, GoToTypeDefinition,
|
||||
Select, SelectPhase,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct UpdateGoToDefinitionLink {
|
||||
pub point: Option<DisplayPoint>,
|
||||
pub cmd_held: bool,
|
||||
pub shift_held: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct CmdChanged {
|
||||
pub struct CmdShiftChanged {
|
||||
pub cmd_down: bool,
|
||||
pub shift_down: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
|
@ -27,28 +30,44 @@ pub struct GoToFetchedDefinition {
|
|||
pub point: DisplayPoint,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct GoToFetchedTypeDefinition {
|
||||
pub point: DisplayPoint,
|
||||
}
|
||||
|
||||
impl_internal_actions!(
|
||||
editor,
|
||||
[UpdateGoToDefinitionLink, CmdChanged, GoToFetchedDefinition]
|
||||
[
|
||||
UpdateGoToDefinitionLink,
|
||||
CmdShiftChanged,
|
||||
GoToFetchedDefinition,
|
||||
GoToFetchedTypeDefinition
|
||||
]
|
||||
);
|
||||
|
||||
pub fn init(cx: &mut MutableAppContext) {
|
||||
cx.add_action(update_go_to_definition_link);
|
||||
cx.add_action(cmd_changed);
|
||||
cx.add_action(cmd_shift_changed);
|
||||
cx.add_action(go_to_fetched_definition);
|
||||
cx.add_action(go_to_fetched_type_definition);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LinkGoToDefinitionState {
|
||||
pub last_mouse_location: Option<Anchor>,
|
||||
pub symbol_range: Option<Range<Anchor>>,
|
||||
pub kind: Option<LinkDefinitionKind>,
|
||||
pub definitions: Vec<LocationLink>,
|
||||
pub task: Option<Task<Option<()>>>,
|
||||
}
|
||||
|
||||
pub fn update_go_to_definition_link(
|
||||
editor: &mut Editor,
|
||||
&UpdateGoToDefinitionLink { point, cmd_held }: &UpdateGoToDefinitionLink,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point,
|
||||
cmd_held,
|
||||
shift_held,
|
||||
}: &UpdateGoToDefinitionLink,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
// Store new mouse point as an anchor
|
||||
|
@ -72,7 +91,13 @@ pub fn update_go_to_definition_link(
|
|||
editor.link_go_to_definition_state.last_mouse_location = point.clone();
|
||||
if cmd_held {
|
||||
if let Some(point) = point {
|
||||
show_link_definition(editor, point, snapshot, cx);
|
||||
let kind = if shift_held {
|
||||
LinkDefinitionKind::Type
|
||||
} else {
|
||||
LinkDefinitionKind::Symbol
|
||||
};
|
||||
|
||||
show_link_definition(kind, editor, point, snapshot, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -80,9 +105,12 @@ pub fn update_go_to_definition_link(
|
|||
hide_link_definition(editor, cx);
|
||||
}
|
||||
|
||||
pub fn cmd_changed(
|
||||
pub fn cmd_shift_changed(
|
||||
editor: &mut Editor,
|
||||
&CmdChanged { cmd_down }: &CmdChanged,
|
||||
&CmdShiftChanged {
|
||||
cmd_down,
|
||||
shift_down,
|
||||
}: &CmdShiftChanged,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
if let Some(point) = editor
|
||||
|
@ -92,19 +120,37 @@ pub fn cmd_changed(
|
|||
{
|
||||
if cmd_down {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
show_link_definition(editor, point.clone(), snapshot, cx);
|
||||
let kind = if shift_down {
|
||||
LinkDefinitionKind::Type
|
||||
} else {
|
||||
LinkDefinitionKind::Symbol
|
||||
};
|
||||
|
||||
show_link_definition(kind, editor, point.clone(), snapshot, cx);
|
||||
} else {
|
||||
hide_link_definition(editor, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum LinkDefinitionKind {
|
||||
Symbol,
|
||||
Type,
|
||||
}
|
||||
|
||||
pub fn show_link_definition(
|
||||
definition_kind: LinkDefinitionKind,
|
||||
editor: &mut Editor,
|
||||
trigger_point: Anchor,
|
||||
snapshot: EditorSnapshot,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let same_kind = editor.link_go_to_definition_state.kind == Some(definition_kind);
|
||||
if !same_kind {
|
||||
hide_link_definition(editor, cx);
|
||||
}
|
||||
|
||||
if editor.pending_rename.is_some() {
|
||||
return;
|
||||
}
|
||||
|
@ -135,17 +181,20 @@ pub fn show_link_definition(
|
|||
return;
|
||||
};
|
||||
|
||||
// Don't request again if the location is within the symbol region of a previous request
|
||||
// Don't request again if the location is within the symbol region of a previous request with the same kind
|
||||
if let Some(symbol_range) = &editor.link_go_to_definition_state.symbol_range {
|
||||
if symbol_range
|
||||
let point_after_start = symbol_range
|
||||
.start
|
||||
.cmp(&trigger_point, &snapshot.buffer_snapshot)
|
||||
.is_le()
|
||||
&& symbol_range
|
||||
.end
|
||||
.cmp(&trigger_point, &snapshot.buffer_snapshot)
|
||||
.is_ge()
|
||||
{
|
||||
.is_le();
|
||||
|
||||
let point_before_end = symbol_range
|
||||
.end
|
||||
.cmp(&trigger_point, &snapshot.buffer_snapshot)
|
||||
.is_ge();
|
||||
|
||||
let point_within_range = point_after_start && point_before_end;
|
||||
if point_within_range && same_kind {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -154,8 +203,14 @@ pub fn show_link_definition(
|
|||
async move {
|
||||
// query the LSP for definition info
|
||||
let definition_request = cx.update(|cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.definition(&buffer, buffer_position.clone(), cx)
|
||||
project.update(cx, |project, cx| match definition_kind {
|
||||
LinkDefinitionKind::Symbol => {
|
||||
project.definition(&buffer, buffer_position.clone(), cx)
|
||||
}
|
||||
|
||||
LinkDefinitionKind::Type => {
|
||||
project.type_definition(&buffer, buffer_position.clone(), cx)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -181,6 +236,7 @@ pub fn show_link_definition(
|
|||
this.update(&mut cx, |this, cx| {
|
||||
// Clear any existing highlights
|
||||
this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
|
||||
this.link_go_to_definition_state.kind = Some(definition_kind);
|
||||
this.link_go_to_definition_state.symbol_range = result
|
||||
.as_ref()
|
||||
.and_then(|(symbol_range, _)| symbol_range.clone());
|
||||
|
@ -258,7 +314,24 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
|||
|
||||
pub fn go_to_fetched_definition(
|
||||
workspace: &mut Workspace,
|
||||
GoToFetchedDefinition { point }: &GoToFetchedDefinition,
|
||||
&GoToFetchedDefinition { point }: &GoToFetchedDefinition,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, workspace, point, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_fetched_type_definition(
|
||||
workspace: &mut Workspace,
|
||||
&GoToFetchedTypeDefinition { point }: &GoToFetchedTypeDefinition,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, workspace, point, cx);
|
||||
}
|
||||
|
||||
fn go_to_fetched_definition_of_kind(
|
||||
kind: LinkDefinitionKind,
|
||||
workspace: &mut Workspace,
|
||||
point: DisplayPoint,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let active_item = workspace.active_item(cx);
|
||||
|
@ -271,13 +344,14 @@ pub fn go_to_fetched_definition(
|
|||
return;
|
||||
};
|
||||
|
||||
let definitions = editor_handle.update(cx, |editor, cx| {
|
||||
let (cached_definitions, cached_definitions_kind) = editor_handle.update(cx, |editor, cx| {
|
||||
let definitions = editor.link_go_to_definition_state.definitions.clone();
|
||||
hide_link_definition(editor, cx);
|
||||
definitions
|
||||
(definitions, editor.link_go_to_definition_state.kind)
|
||||
});
|
||||
|
||||
if !definitions.is_empty() {
|
||||
let is_correct_kind = cached_definitions_kind == Some(kind);
|
||||
if !cached_definitions.is_empty() && is_correct_kind {
|
||||
editor_handle.update(cx, |editor, cx| {
|
||||
if !editor.focused {
|
||||
cx.focus_self();
|
||||
|
@ -285,7 +359,7 @@ pub fn go_to_fetched_definition(
|
|||
}
|
||||
});
|
||||
|
||||
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
|
||||
Editor::navigate_to_definitions(workspace, editor_handle, cached_definitions, cx);
|
||||
} else {
|
||||
editor_handle.update(cx, |editor, cx| {
|
||||
editor.select(
|
||||
|
@ -298,7 +372,13 @@ pub fn go_to_fetched_definition(
|
|||
);
|
||||
});
|
||||
|
||||
Editor::go_to_definition(workspace, &GoToDefinition, cx);
|
||||
match kind {
|
||||
LinkDefinitionKind::Symbol => Editor::go_to_definition(workspace, &GoToDefinition, cx),
|
||||
|
||||
LinkDefinitionKind::Type => {
|
||||
Editor::go_to_type_definition(workspace, &GoToTypeDefinition, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,11 +386,128 @@ pub fn go_to_fetched_definition(
|
|||
mod tests {
|
||||
use futures::StreamExt;
|
||||
use indoc::indoc;
|
||||
use lsp::request::{GotoDefinition, GotoTypeDefinition};
|
||||
|
||||
use crate::test::EditorLspTestContext;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
struct A;
|
||||
let v|ariable = A;
|
||||
"});
|
||||
|
||||
// Basic hold cmd+shift, expect highlight in region if response contains type definition
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
struct A;
|
||||
let v|ariable = A;
|
||||
"});
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
struct A;
|
||||
let [variable] = A;
|
||||
"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
struct [A];
|
||||
let variable = A;
|
||||
"});
|
||||
|
||||
let mut requests =
|
||||
cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
|
||||
// Press cmd+shift to trigger highlight
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
shift_held: true,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
struct A;
|
||||
let [variable] = A;
|
||||
"});
|
||||
|
||||
// Unpress shift causes highlight to go away (normal goto-definition is not valid here)
|
||||
cx.update_editor(|editor, cx| {
|
||||
cmd_shift_changed(
|
||||
editor,
|
||||
&CmdShiftChanged {
|
||||
cmd_down: true,
|
||||
shift_down: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
// Assert no link highlights
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
struct A;
|
||||
let variable = A;
|
||||
"});
|
||||
|
||||
// Cmd+shift click without existing definition requests and jumps
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
struct A;
|
||||
let v|ariable = A;
|
||||
"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
struct [A];
|
||||
let variable = A;
|
||||
"});
|
||||
|
||||
let mut requests =
|
||||
cx.handle_request::<GotoTypeDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoTypeDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: None,
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
|
||||
cx.update_workspace(|workspace, cx| {
|
||||
go_to_fetched_type_definition(
|
||||
workspace,
|
||||
&GoToFetchedTypeDefinition { point: hover_point },
|
||||
cx,
|
||||
);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
struct [A};
|
||||
let variable = A;
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
|
@ -327,7 +524,8 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
// Basic hold cmd, expect highlight in region if response contains definition
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
|
@ -335,38 +533,41 @@ mod tests {
|
|||
do_w|ork();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
|
||||
test();
|
||||
"});
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
[do_work]();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn [do_work]()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
|
||||
let mut requests =
|
||||
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
shift_held: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
@ -378,11 +579,19 @@ mod tests {
|
|||
[do_work]();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
// Unpress cmd causes highlight to go away
|
||||
cx.update_editor(|editor, cx| {
|
||||
cmd_changed(editor, &CmdChanged { cmd_down: false }, cx);
|
||||
cmd_shift_changed(
|
||||
editor,
|
||||
&CmdShiftChanged {
|
||||
cmd_down: false,
|
||||
shift_down: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
// Assert no link highlights
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
|
@ -390,28 +599,29 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
// Response without source range still highlights word
|
||||
cx.update_editor(|editor, _| editor.link_go_to_definition_state.last_mouse_location = None);
|
||||
let mut requests =
|
||||
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
// No origin range
|
||||
origin_selection_range: None,
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
// No origin range
|
||||
origin_selection_range: None,
|
||||
target_uri: url.clone(),
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
shift_held: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
@ -424,7 +634,8 @@ mod tests {
|
|||
[do_work]();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
// Moving mouse to location with no response dismisses highlight
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
|
@ -432,19 +643,21 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
let mut requests =
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
// No definitions returned
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
|
||||
});
|
||||
test();
|
||||
"});
|
||||
let mut requests = cx
|
||||
.lsp
|
||||
.handle_request::<GotoDefinition, _, _>(move |_, _| async move {
|
||||
// No definitions returned
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
shift_held: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
@ -458,7 +671,8 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
// Move mouse without cmd and then pressing cmd triggers highlight
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
|
@ -466,13 +680,15 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
te|st();"});
|
||||
te|st();
|
||||
"});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: false,
|
||||
shift_held: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
@ -485,34 +701,43 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
[test]();"});
|
||||
[test]();
|
||||
"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
fn [test]()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
let mut requests =
|
||||
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: Some(symbol_range),
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
cmd_changed(editor, &CmdChanged { cmd_down: true }, cx);
|
||||
cmd_shift_changed(
|
||||
editor,
|
||||
&CmdShiftChanged {
|
||||
cmd_down: true,
|
||||
shift_down: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
@ -522,7 +747,8 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
[test]();"});
|
||||
[test]();
|
||||
"});
|
||||
|
||||
// Moving within symbol range doesn't re-request
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
|
@ -530,13 +756,15 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
tes|t();"});
|
||||
tes|t();
|
||||
"});
|
||||
cx.update_editor(|editor, cx| {
|
||||
update_go_to_definition_link(
|
||||
editor,
|
||||
&UpdateGoToDefinitionLink {
|
||||
point: Some(hover_point),
|
||||
cmd_held: true,
|
||||
shift_held: false,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
@ -547,7 +775,8 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
[test]();"});
|
||||
[test]();
|
||||
"});
|
||||
|
||||
// Cmd click with existing definition doesn't re-request and dismisses highlight
|
||||
cx.update_workspace(|workspace, cx| {
|
||||
|
@ -555,7 +784,7 @@ mod tests {
|
|||
});
|
||||
// Assert selection moved to to definition
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::GotoDefinition, _, _>(move |_, _| async move {
|
||||
.handle_request::<GotoDefinition, _, _>(move |_, _| async move {
|
||||
// Empty definition response to make sure we aren't hitting the lsp and using
|
||||
// the cached location instead
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
|
||||
|
@ -565,14 +794,16 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
// Assert no link highlights after jump
|
||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
// Cmd click without existing definition requests and jumps
|
||||
let hover_point = cx.display_point(indoc! {"
|
||||
|
@ -580,25 +811,26 @@ mod tests {
|
|||
do_w|ork();
|
||||
|
||||
fn do_work()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
let target_range = cx.lsp_range(indoc! {"
|
||||
fn test()
|
||||
do_work();
|
||||
|
||||
fn [do_work]()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
|
||||
let mut requests =
|
||||
cx.handle_request::<lsp::request::GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: None,
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
let mut requests = cx.handle_request::<GotoDefinition, _, _>(move |url, _, _| async move {
|
||||
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![
|
||||
lsp::LocationLink {
|
||||
origin_selection_range: None,
|
||||
target_uri: url,
|
||||
target_range,
|
||||
target_selection_range: target_range,
|
||||
},
|
||||
])))
|
||||
});
|
||||
cx.update_workspace(|workspace, cx| {
|
||||
go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
|
||||
});
|
||||
|
@ -610,6 +842,7 @@ mod tests {
|
|||
do_work();
|
||||
|
||||
fn [do_work}()
|
||||
test();"});
|
||||
test();
|
||||
"});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ use context_menu::ContextMenuItem;
|
|||
use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext};
|
||||
|
||||
use crate::{
|
||||
DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, Rename, SelectMode,
|
||||
ToggleCodeActions,
|
||||
DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, GoToTypeDefinition,
|
||||
Rename, SelectMode, ToggleCodeActions,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
|
@ -50,6 +50,7 @@ pub fn deploy_context_menu(
|
|||
vec![
|
||||
ContextMenuItem::item("Rename Symbol", Rename),
|
||||
ContextMenuItem::item("Go To Definition", GoToDefinition),
|
||||
ContextMenuItem::item("Go To Type Definition", GoToTypeDefinition),
|
||||
ContextMenuItem::item("Find All References", FindAllReferences),
|
||||
ContextMenuItem::item(
|
||||
"Code Actions",
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct KeyUpEvent {
|
|||
pub keystroke: Keystroke,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ModifiersChangedEvent {
|
||||
pub ctrl: bool,
|
||||
pub alt: bool,
|
||||
|
@ -19,7 +19,7 @@ pub struct ModifiersChangedEvent {
|
|||
pub cmd: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct ScrollWheelEvent {
|
||||
pub position: Vector2F,
|
||||
pub delta: Vector2F,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub use lsp_types::request::*;
|
||||
pub use lsp_types::*;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
|
|
@ -8,11 +8,11 @@ use gpui::{AppContext, AsyncAppContext, ModelHandle};
|
|||
use language::{
|
||||
point_from_lsp, point_to_lsp,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, Anchor, Bias, Buffer, PointUtf16, ToPointUtf16,
|
||||
range_from_lsp, Anchor, Bias, Buffer, CachedLspAdapter, PointUtf16, ToPointUtf16,
|
||||
};
|
||||
use lsp::{DocumentHighlightKind, ServerCapabilities};
|
||||
use lsp::{DocumentHighlightKind, LanguageServer, ServerCapabilities};
|
||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||
use std::{cmp::Reverse, ops::Range, path::Path};
|
||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub(crate) trait LspCommand: 'static + Sized {
|
||||
|
@ -75,6 +75,10 @@ pub(crate) struct GetDefinition {
|
|||
pub position: PointUtf16,
|
||||
}
|
||||
|
||||
pub(crate) struct GetTypeDefinition {
|
||||
pub position: PointUtf16,
|
||||
}
|
||||
|
||||
pub(crate) struct GetReferences {
|
||||
pub position: PointUtf16,
|
||||
}
|
||||
|
@ -238,13 +242,7 @@ impl LspCommand for PerformRename {
|
|||
mut cx: AsyncAppContext,
|
||||
) -> Result<ProjectTransaction> {
|
||||
if let Some(edit) = message {
|
||||
let (lsp_adapter, lsp_server) = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
project
|
||||
.language_server_for_buffer(buffer.read(cx), cx)
|
||||
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
||||
})
|
||||
.ok_or_else(|| anyhow!("no language server found for buffer"))?;
|
||||
let (lsp_adapter, lsp_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
|
||||
Project::deserialize_workspace_edit(
|
||||
project,
|
||||
edit,
|
||||
|
@ -352,83 +350,9 @@ impl LspCommand for GetDefinition {
|
|||
message: Option<lsp::GotoDefinitionResponse>,
|
||||
project: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
let mut definitions = Vec::new();
|
||||
let (lsp_adapter, language_server) = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
project
|
||||
.language_server_for_buffer(buffer.read(cx), cx)
|
||||
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
||||
})
|
||||
.ok_or_else(|| anyhow!("no language server found for buffer"))?;
|
||||
|
||||
if let Some(message) = message {
|
||||
let mut unresolved_links = Vec::new();
|
||||
match message {
|
||||
lsp::GotoDefinitionResponse::Scalar(loc) => {
|
||||
unresolved_links.push((None, loc.uri, loc.range));
|
||||
}
|
||||
lsp::GotoDefinitionResponse::Array(locs) => {
|
||||
unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
|
||||
}
|
||||
lsp::GotoDefinitionResponse::Link(links) => {
|
||||
unresolved_links.extend(links.into_iter().map(|l| {
|
||||
(
|
||||
l.origin_selection_range,
|
||||
l.target_uri,
|
||||
l.target_selection_range,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
for (origin_range, target_uri, target_range) in unresolved_links {
|
||||
let target_buffer_handle = project
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.open_local_buffer_via_lsp(
|
||||
target_uri,
|
||||
language_server.server_id(),
|
||||
lsp_adapter.name.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
cx.read(|cx| {
|
||||
let origin_location = origin_range.map(|origin_range| {
|
||||
let origin_buffer = buffer.read(cx);
|
||||
let origin_start = origin_buffer
|
||||
.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
|
||||
let origin_end = origin_buffer
|
||||
.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
|
||||
Location {
|
||||
buffer: buffer.clone(),
|
||||
range: origin_buffer.anchor_after(origin_start)
|
||||
..origin_buffer.anchor_before(origin_end),
|
||||
}
|
||||
});
|
||||
|
||||
let target_buffer = target_buffer_handle.read(cx);
|
||||
let target_start = target_buffer
|
||||
.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
|
||||
let target_end = target_buffer
|
||||
.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
|
||||
let target_location = Location {
|
||||
buffer: target_buffer_handle,
|
||||
range: target_buffer.anchor_after(target_start)
|
||||
..target_buffer.anchor_before(target_end),
|
||||
};
|
||||
|
||||
definitions.push(LocationLink {
|
||||
origin: origin_location,
|
||||
target: target_location,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(definitions)
|
||||
location_links_from_lsp(message, project, buffer, cx).await
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
|
||||
|
@ -469,32 +393,7 @@ impl LspCommand for GetDefinition {
|
|||
_: &clock::Global,
|
||||
cx: &AppContext,
|
||||
) -> proto::GetDefinitionResponse {
|
||||
let links = response
|
||||
.into_iter()
|
||||
.map(|definition| {
|
||||
let origin = definition.origin.map(|origin| {
|
||||
let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx);
|
||||
proto::Location {
|
||||
start: Some(serialize_anchor(&origin.range.start)),
|
||||
end: Some(serialize_anchor(&origin.range.end)),
|
||||
buffer: Some(buffer),
|
||||
}
|
||||
});
|
||||
|
||||
let buffer =
|
||||
project.serialize_buffer_for_peer(&definition.target.buffer, peer_id, cx);
|
||||
let target = proto::Location {
|
||||
start: Some(serialize_anchor(&definition.target.range.start)),
|
||||
end: Some(serialize_anchor(&definition.target.range.end)),
|
||||
buffer: Some(buffer),
|
||||
};
|
||||
|
||||
proto::LocationLink {
|
||||
origin,
|
||||
target: Some(target),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let links = location_links_to_proto(response, project, peer_id, cx);
|
||||
proto::GetDefinitionResponse { links }
|
||||
}
|
||||
|
||||
|
@ -503,61 +402,9 @@ impl LspCommand for GetDefinition {
|
|||
message: proto::GetDefinitionResponse,
|
||||
project: ModelHandle<Project>,
|
||||
_: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
let mut links = Vec::new();
|
||||
for link in message.links {
|
||||
let origin = match link.origin {
|
||||
Some(origin) => {
|
||||
let buffer = origin
|
||||
.buffer
|
||||
.ok_or_else(|| anyhow!("missing origin buffer"))?;
|
||||
let buffer = project
|
||||
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
|
||||
.await?;
|
||||
let start = origin
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin start"))?;
|
||||
let end = origin
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
|
||||
.await;
|
||||
Some(Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
|
||||
let buffer = target.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
|
||||
let buffer = project
|
||||
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
|
||||
.await?;
|
||||
let start = target
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target start"))?;
|
||||
let end = target
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
|
||||
.await;
|
||||
let target = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
|
||||
links.push(LocationLink { origin, target })
|
||||
}
|
||||
Ok(links)
|
||||
location_links_from_proto(message.links, project, cx).await
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
|
||||
|
@ -565,6 +412,281 @@ impl LspCommand for GetDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetTypeDefinition {
|
||||
type Response = Vec<LocationLink>;
|
||||
type LspRequest = lsp::request::GotoTypeDefinition;
|
||||
type ProtoRequest = proto::GetTypeDefinition;
|
||||
|
||||
fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoTypeDefinitionParams {
|
||||
lsp::GotoTypeDefinitionParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: lsp::Url::from_file_path(path).unwrap(),
|
||||
},
|
||||
position: point_to_lsp(self.position),
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_from_lsp(
|
||||
self,
|
||||
message: Option<lsp::GotoTypeDefinitionResponse>,
|
||||
project: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
location_links_from_lsp(message, project, buffer, cx).await
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
|
||||
proto::GetTypeDefinition {
|
||||
project_id,
|
||||
buffer_id: buffer.remote_id(),
|
||||
position: Some(language::proto::serialize_anchor(
|
||||
&buffer.anchor_before(self.position),
|
||||
)),
|
||||
version: serialize_version(&buffer.version()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn from_proto(
|
||||
message: proto::GetTypeDefinition,
|
||||
_: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
let position = message
|
||||
.position
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid position"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(message.version))
|
||||
})
|
||||
.await;
|
||||
Ok(Self {
|
||||
position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer)),
|
||||
})
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
response: Vec<LocationLink>,
|
||||
project: &mut Project,
|
||||
peer_id: PeerId,
|
||||
_: &clock::Global,
|
||||
cx: &AppContext,
|
||||
) -> proto::GetTypeDefinitionResponse {
|
||||
let links = location_links_to_proto(response, project, peer_id, cx);
|
||||
proto::GetTypeDefinitionResponse { links }
|
||||
}
|
||||
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetTypeDefinitionResponse,
|
||||
project: ModelHandle<Project>,
|
||||
_: ModelHandle<Buffer>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
location_links_from_proto(message.links, project, cx).await
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> u64 {
|
||||
message.buffer_id
|
||||
}
|
||||
}
|
||||
|
||||
fn language_server_for_buffer(
|
||||
project: &ModelHandle<Project>,
|
||||
buffer: &ModelHandle<Buffer>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
|
||||
project
|
||||
.read_with(cx, |project, cx| {
|
||||
project
|
||||
.language_server_for_buffer(buffer.read(cx), cx)
|
||||
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
||||
})
|
||||
.ok_or_else(|| anyhow!("no language server found for buffer"))
|
||||
}
|
||||
|
||||
async fn location_links_from_proto(
|
||||
proto_links: Vec<proto::LocationLink>,
|
||||
project: ModelHandle<Project>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
let mut links = Vec::new();
|
||||
|
||||
for link in proto_links {
|
||||
let origin = match link.origin {
|
||||
Some(origin) => {
|
||||
let buffer = origin
|
||||
.buffer
|
||||
.ok_or_else(|| anyhow!("missing origin buffer"))?;
|
||||
let buffer = project
|
||||
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
|
||||
.await?;
|
||||
let start = origin
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin start"))?;
|
||||
let end = origin
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing origin end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
|
||||
.await;
|
||||
Some(Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let target = link.target.ok_or_else(|| anyhow!("missing target"))?;
|
||||
let buffer = target.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
|
||||
let buffer = project
|
||||
.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
|
||||
.await?;
|
||||
let start = target
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target start"))?;
|
||||
let end = target
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("missing target end"))?;
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| buffer.wait_for_anchors([&start, &end]))
|
||||
.await;
|
||||
let target = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
|
||||
links.push(LocationLink { origin, target })
|
||||
}
|
||||
|
||||
Ok(links)
|
||||
}
|
||||
|
||||
async fn location_links_from_lsp(
|
||||
message: Option<lsp::GotoDefinitionResponse>,
|
||||
project: ModelHandle<Project>,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<LocationLink>> {
|
||||
let message = match message {
|
||||
Some(message) => message,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let mut unresolved_links = Vec::new();
|
||||
match message {
|
||||
lsp::GotoDefinitionResponse::Scalar(loc) => {
|
||||
unresolved_links.push((None, loc.uri, loc.range));
|
||||
}
|
||||
|
||||
lsp::GotoDefinitionResponse::Array(locs) => {
|
||||
unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
|
||||
}
|
||||
|
||||
lsp::GotoDefinitionResponse::Link(links) => {
|
||||
unresolved_links.extend(links.into_iter().map(|l| {
|
||||
(
|
||||
l.origin_selection_range,
|
||||
l.target_uri,
|
||||
l.target_selection_range,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let (lsp_adapter, language_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
|
||||
let mut definitions = Vec::new();
|
||||
for (origin_range, target_uri, target_range) in unresolved_links {
|
||||
let target_buffer_handle = project
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.open_local_buffer_via_lsp(
|
||||
target_uri,
|
||||
language_server.server_id(),
|
||||
lsp_adapter.name.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
cx.read(|cx| {
|
||||
let origin_location = origin_range.map(|origin_range| {
|
||||
let origin_buffer = buffer.read(cx);
|
||||
let origin_start =
|
||||
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
|
||||
let origin_end =
|
||||
origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
|
||||
Location {
|
||||
buffer: buffer.clone(),
|
||||
range: origin_buffer.anchor_after(origin_start)
|
||||
..origin_buffer.anchor_before(origin_end),
|
||||
}
|
||||
});
|
||||
|
||||
let target_buffer = target_buffer_handle.read(cx);
|
||||
let target_start =
|
||||
target_buffer.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
|
||||
let target_end =
|
||||
target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
|
||||
let target_location = Location {
|
||||
buffer: target_buffer_handle,
|
||||
range: target_buffer.anchor_after(target_start)
|
||||
..target_buffer.anchor_before(target_end),
|
||||
};
|
||||
|
||||
definitions.push(LocationLink {
|
||||
origin: origin_location,
|
||||
target: target_location,
|
||||
})
|
||||
});
|
||||
}
|
||||
Ok(definitions)
|
||||
}
|
||||
|
||||
fn location_links_to_proto(
|
||||
links: Vec<LocationLink>,
|
||||
project: &mut Project,
|
||||
peer_id: PeerId,
|
||||
cx: &AppContext,
|
||||
) -> Vec<proto::LocationLink> {
|
||||
links
|
||||
.into_iter()
|
||||
.map(|definition| {
|
||||
let origin = definition.origin.map(|origin| {
|
||||
let buffer = project.serialize_buffer_for_peer(&origin.buffer, peer_id, cx);
|
||||
proto::Location {
|
||||
start: Some(serialize_anchor(&origin.range.start)),
|
||||
end: Some(serialize_anchor(&origin.range.end)),
|
||||
buffer: Some(buffer),
|
||||
}
|
||||
});
|
||||
|
||||
let buffer = project.serialize_buffer_for_peer(&definition.target.buffer, peer_id, cx);
|
||||
let target = proto::Location {
|
||||
start: Some(serialize_anchor(&definition.target.range.start)),
|
||||
end: Some(serialize_anchor(&definition.target.range.end)),
|
||||
buffer: Some(buffer),
|
||||
};
|
||||
|
||||
proto::LocationLink {
|
||||
origin,
|
||||
target: Some(target),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetReferences {
|
||||
type Response = Vec<Location>;
|
||||
|
@ -595,13 +717,8 @@ impl LspCommand for GetReferences {
|
|||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Location>> {
|
||||
let mut references = Vec::new();
|
||||
let (lsp_adapter, language_server) = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
project
|
||||
.language_server_for_buffer(buffer.read(cx), cx)
|
||||
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
||||
})
|
||||
.ok_or_else(|| anyhow!("no language server found for buffer"))?;
|
||||
let (lsp_adapter, language_server) =
|
||||
language_server_for_buffer(&project, &buffer, &mut cx)?;
|
||||
|
||||
if let Some(locations) = locations {
|
||||
for lsp_location in locations {
|
||||
|
|
|
@ -3250,6 +3250,16 @@ impl Project {
|
|||
self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
|
||||
}
|
||||
|
||||
pub fn type_definition<T: ToPointUtf16>(
|
||||
&self,
|
||||
buffer: &ModelHandle<Buffer>,
|
||||
position: T,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<LocationLink>>> {
|
||||
let position = position.to_point_utf16(buffer.read(cx));
|
||||
self.request_lsp(buffer.clone(), GetTypeDefinition { position }, cx)
|
||||
}
|
||||
|
||||
pub fn references<T: ToPointUtf16>(
|
||||
&self,
|
||||
buffer: &ModelHandle<Buffer>,
|
||||
|
|
|
@ -26,85 +26,87 @@ message Envelope {
|
|||
|
||||
GetDefinition get_definition = 20;
|
||||
GetDefinitionResponse get_definition_response = 21;
|
||||
GetReferences get_references = 22;
|
||||
GetReferencesResponse get_references_response = 23;
|
||||
GetDocumentHighlights get_document_highlights = 24;
|
||||
GetDocumentHighlightsResponse get_document_highlights_response = 25;
|
||||
GetProjectSymbols get_project_symbols = 26;
|
||||
GetProjectSymbolsResponse get_project_symbols_response = 27;
|
||||
OpenBufferForSymbol open_buffer_for_symbol = 28;
|
||||
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
|
||||
GetTypeDefinition get_type_definition = 22;
|
||||
GetTypeDefinitionResponse get_type_definition_response = 23;
|
||||
GetReferences get_references = 24;
|
||||
GetReferencesResponse get_references_response = 25;
|
||||
GetDocumentHighlights get_document_highlights = 26;
|
||||
GetDocumentHighlightsResponse get_document_highlights_response = 27;
|
||||
GetProjectSymbols get_project_symbols = 28;
|
||||
GetProjectSymbolsResponse get_project_symbols_response = 29;
|
||||
OpenBufferForSymbol open_buffer_for_symbol = 30;
|
||||
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 31;
|
||||
|
||||
UpdateProject update_project = 30;
|
||||
RegisterProjectActivity register_project_activity = 31;
|
||||
UpdateWorktree update_worktree = 32;
|
||||
UpdateWorktreeExtensions update_worktree_extensions = 33;
|
||||
UpdateProject update_project = 32;
|
||||
RegisterProjectActivity register_project_activity = 33;
|
||||
UpdateWorktree update_worktree = 34;
|
||||
UpdateWorktreeExtensions update_worktree_extensions = 35;
|
||||
|
||||
CreateProjectEntry create_project_entry = 34;
|
||||
RenameProjectEntry rename_project_entry = 35;
|
||||
CopyProjectEntry copy_project_entry = 36;
|
||||
DeleteProjectEntry delete_project_entry = 37;
|
||||
ProjectEntryResponse project_entry_response = 38;
|
||||
CreateProjectEntry create_project_entry = 36;
|
||||
RenameProjectEntry rename_project_entry = 37;
|
||||
CopyProjectEntry copy_project_entry = 38;
|
||||
DeleteProjectEntry delete_project_entry = 39;
|
||||
ProjectEntryResponse project_entry_response = 40;
|
||||
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 39;
|
||||
StartLanguageServer start_language_server = 40;
|
||||
UpdateLanguageServer update_language_server = 41;
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 41;
|
||||
StartLanguageServer start_language_server = 42;
|
||||
UpdateLanguageServer update_language_server = 43;
|
||||
|
||||
OpenBufferById open_buffer_by_id = 42;
|
||||
OpenBufferByPath open_buffer_by_path = 43;
|
||||
OpenBufferResponse open_buffer_response = 44;
|
||||
UpdateBuffer update_buffer = 45;
|
||||
UpdateBufferFile update_buffer_file = 46;
|
||||
SaveBuffer save_buffer = 47;
|
||||
BufferSaved buffer_saved = 48;
|
||||
BufferReloaded buffer_reloaded = 49;
|
||||
ReloadBuffers reload_buffers = 50;
|
||||
ReloadBuffersResponse reload_buffers_response = 51;
|
||||
FormatBuffers format_buffers = 52;
|
||||
FormatBuffersResponse format_buffers_response = 53;
|
||||
GetCompletions get_completions = 54;
|
||||
GetCompletionsResponse get_completions_response = 55;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 56;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 57;
|
||||
GetCodeActions get_code_actions = 58;
|
||||
GetCodeActionsResponse get_code_actions_response = 59;
|
||||
GetHover get_hover = 60;
|
||||
GetHoverResponse get_hover_response = 61;
|
||||
ApplyCodeAction apply_code_action = 62;
|
||||
ApplyCodeActionResponse apply_code_action_response = 63;
|
||||
PrepareRename prepare_rename = 64;
|
||||
PrepareRenameResponse prepare_rename_response = 65;
|
||||
PerformRename perform_rename = 66;
|
||||
PerformRenameResponse perform_rename_response = 67;
|
||||
SearchProject search_project = 68;
|
||||
SearchProjectResponse search_project_response = 69;
|
||||
OpenBufferById open_buffer_by_id = 44;
|
||||
OpenBufferByPath open_buffer_by_path = 45;
|
||||
OpenBufferResponse open_buffer_response = 46;
|
||||
UpdateBuffer update_buffer = 47;
|
||||
UpdateBufferFile update_buffer_file = 48;
|
||||
SaveBuffer save_buffer = 49;
|
||||
BufferSaved buffer_saved = 50;
|
||||
BufferReloaded buffer_reloaded = 51;
|
||||
ReloadBuffers reload_buffers = 52;
|
||||
ReloadBuffersResponse reload_buffers_response = 53;
|
||||
FormatBuffers format_buffers = 54;
|
||||
FormatBuffersResponse format_buffers_response = 55;
|
||||
GetCompletions get_completions = 56;
|
||||
GetCompletionsResponse get_completions_response = 57;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 58;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 59;
|
||||
GetCodeActions get_code_actions = 60;
|
||||
GetCodeActionsResponse get_code_actions_response = 61;
|
||||
GetHover get_hover = 62;
|
||||
GetHoverResponse get_hover_response = 63;
|
||||
ApplyCodeAction apply_code_action = 64;
|
||||
ApplyCodeActionResponse apply_code_action_response = 65;
|
||||
PrepareRename prepare_rename = 66;
|
||||
PrepareRenameResponse prepare_rename_response = 67;
|
||||
PerformRename perform_rename = 68;
|
||||
PerformRenameResponse perform_rename_response = 69;
|
||||
SearchProject search_project = 70;
|
||||
SearchProjectResponse search_project_response = 71;
|
||||
|
||||
GetChannels get_channels = 70;
|
||||
GetChannelsResponse get_channels_response = 71;
|
||||
JoinChannel join_channel = 72;
|
||||
JoinChannelResponse join_channel_response = 73;
|
||||
LeaveChannel leave_channel = 74;
|
||||
SendChannelMessage send_channel_message = 75;
|
||||
SendChannelMessageResponse send_channel_message_response = 76;
|
||||
ChannelMessageSent channel_message_sent = 77;
|
||||
GetChannelMessages get_channel_messages = 78;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 79;
|
||||
GetChannels get_channels = 72;
|
||||
GetChannelsResponse get_channels_response = 73;
|
||||
JoinChannel join_channel = 74;
|
||||
JoinChannelResponse join_channel_response = 75;
|
||||
LeaveChannel leave_channel = 76;
|
||||
SendChannelMessage send_channel_message = 77;
|
||||
SendChannelMessageResponse send_channel_message_response = 78;
|
||||
ChannelMessageSent channel_message_sent = 79;
|
||||
GetChannelMessages get_channel_messages = 80;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 81;
|
||||
|
||||
UpdateContacts update_contacts = 80;
|
||||
UpdateInviteInfo update_invite_info = 81;
|
||||
ShowContacts show_contacts = 82;
|
||||
UpdateContacts update_contacts = 82;
|
||||
UpdateInviteInfo update_invite_info = 83;
|
||||
ShowContacts show_contacts = 84;
|
||||
|
||||
GetUsers get_users = 83;
|
||||
FuzzySearchUsers fuzzy_search_users = 84;
|
||||
UsersResponse users_response = 85;
|
||||
RequestContact request_contact = 86;
|
||||
RespondToContactRequest respond_to_contact_request = 87;
|
||||
RemoveContact remove_contact = 88;
|
||||
GetUsers get_users = 85;
|
||||
FuzzySearchUsers fuzzy_search_users = 86;
|
||||
UsersResponse users_response = 87;
|
||||
RequestContact request_contact = 88;
|
||||
RespondToContactRequest respond_to_contact_request = 89;
|
||||
RemoveContact remove_contact = 90;
|
||||
|
||||
Follow follow = 89;
|
||||
FollowResponse follow_response = 90;
|
||||
UpdateFollowers update_followers = 91;
|
||||
Unfollow unfollow = 92;
|
||||
Follow follow = 91;
|
||||
FollowResponse follow_response = 92;
|
||||
UpdateFollowers update_followers = 93;
|
||||
Unfollow unfollow = 94;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,6 +265,17 @@ message GetDefinitionResponse {
|
|||
repeated LocationLink links = 1;
|
||||
}
|
||||
|
||||
message GetTypeDefinition {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
Anchor position = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
}
|
||||
|
||||
message GetTypeDefinitionResponse {
|
||||
repeated LocationLink links = 1;
|
||||
}
|
||||
|
||||
message GetReferences {
|
||||
uint64 project_id = 1;
|
||||
uint64 buffer_id = 2;
|
||||
|
|
|
@ -106,6 +106,8 @@ messages!(
|
|||
(GetCompletionsResponse, Background),
|
||||
(GetDefinition, Background),
|
||||
(GetDefinitionResponse, Background),
|
||||
(GetTypeDefinition, Background),
|
||||
(GetTypeDefinitionResponse, Background),
|
||||
(GetDocumentHighlights, Background),
|
||||
(GetDocumentHighlightsResponse, Background),
|
||||
(GetReferences, Background),
|
||||
|
@ -183,6 +185,7 @@ request_messages!(
|
|||
(GetHover, GetHoverResponse),
|
||||
(GetCompletions, GetCompletionsResponse),
|
||||
(GetDefinition, GetDefinitionResponse),
|
||||
(GetTypeDefinition, GetTypeDefinitionResponse),
|
||||
(GetDocumentHighlights, GetDocumentHighlightsResponse),
|
||||
(GetReferences, GetReferencesResponse),
|
||||
(GetProjectSymbols, GetProjectSymbolsResponse),
|
||||
|
@ -226,6 +229,7 @@ entity_messages!(
|
|||
GetCodeActions,
|
||||
GetCompletions,
|
||||
GetDefinition,
|
||||
GetTypeDefinition,
|
||||
GetDocumentHighlights,
|
||||
GetHover,
|
||||
GetReferences,
|
||||
|
|
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
|||
pub use peer::*;
|
||||
mod macros;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 28;
|
||||
pub const PROTOCOL_VERSION: u32 = 29;
|
||||
|
|
|
@ -274,6 +274,10 @@ pub fn menus() -> Vec<Menu<'static>> {
|
|||
name: "Go to Definition",
|
||||
action: Box::new(editor::GoToDefinition),
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Go to Type Definition",
|
||||
action: Box::new(editor::GoToTypeDefinition),
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Go to References",
|
||||
action: Box::new(editor::FindAllReferences),
|
||||
|
|
Loading…
Reference in a new issue