diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 72252edd71..28eec35def 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -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", diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 819aa5ccd5..558a6bfd98 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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); +enum GotoDefinitionKind { + Symbol, + Type, +} + impl Editor { pub fn single_line( field_editor_style: Option, @@ -4693,6 +4700,22 @@ impl Editor { workspace: &mut Workspace, _: &GoToDefinition, cx: &mut ViewContext, + ) { + Self::go_to_definition_of_kind(GotoDefinitionKind::Symbol, workspace, cx); + } + + pub fn go_to_type_definition( + workspace: &mut Workspace, + _: &GoToTypeDefinition, + cx: &mut ViewContext, + ) { + Self::go_to_definition_of_kind(GotoDefinitionKind::Type, workspace, cx); + } + + fn go_to_definition_of_kind( + kind: GotoDefinitionKind, + workspace: &mut Workspace, + cx: &mut ViewContext, ) { 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| { diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index ce2faf8fa6..513a9ed99c 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -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", diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 8c689808ae..5c8916e34e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -1,3 +1,4 @@ +pub use lsp_types::request::*; pub use lsp_types::*; use anyhow::{anyhow, Context, Result}; diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 0c30ee2924..6ec52451a1 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -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, } @@ -565,6 +569,243 @@ impl LspCommand for GetDefinition { } } +#[async_trait(?Send)] +impl LspCommand for GetTypeDefinition { + type Response = Vec; + 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, + project: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result> { + 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::GotoTypeDefinitionResponse::Scalar(loc) => { + unresolved_links.push((None, loc.uri, loc.range)); + } + lsp::GotoTypeDefinitionResponse::Array(locs) => { + unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range))); + } + lsp::GotoTypeDefinitionResponse::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) + } + + 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, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result { + 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, + project: &mut Project, + peer_id: PeerId, + _: &clock::Global, + cx: &AppContext, + ) -> proto::GetTypeDefinitionResponse { + 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(); + proto::GetTypeDefinitionResponse { links } + } + + async fn response_from_proto( + self, + message: proto::GetTypeDefinitionResponse, + project: ModelHandle, + _: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result> { + 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) + } + + fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> u64 { + message.buffer_id + } +} + #[async_trait(?Send)] impl LspCommand for GetReferences { type Response = Vec; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 898dbb5a2f..8adc10ba55 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3250,6 +3250,16 @@ impl Project { self.request_lsp(buffer.clone(), GetDefinition { position }, cx) } + pub fn type_definition( + &self, + buffer: &ModelHandle, + position: T, + cx: &mut ModelContext, + ) -> Task>> { + let position = position.to_point_utf16(buffer.read(cx)); + self.request_lsp(buffer.clone(), GetTypeDefinition { position }, cx) + } + pub fn references( &self, buffer: &ModelHandle, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 35f3049edb..f52815a8be 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -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; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index e3844a8692..8cd5ca36fb 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -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, diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 015ac10707..c4017015f9 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -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; diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index e8297a1727..73817ca2e3 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -274,6 +274,10 @@ pub fn menus() -> Vec> { 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),