diff --git a/Cargo.lock b/Cargo.lock index 4ab894d73fe764abbd4cc0a2ffa871dbe7106ee8..be461064fd8a27e0148743d2d8a7441aad7c7dc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3563,9 +3563,11 @@ dependencies = [ name = "project_symbols" version = "0.1.0" dependencies = [ + "anyhow", "editor", "fuzzy", "gpui", + "language", "ordered-float", "postage", "project", diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 432963a57f012214138490f5f2196babd7180de9..14f0c2817a8fa4437b09c353b31ac02d02c68d1c 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -342,7 +342,7 @@ impl LspCommand for GetDefinition { for (target_uri, target_range) in unresolved_locations { let target_buffer_handle = project .update(&mut cx, |this, cx| { - this.open_local_buffer_from_lsp_path( + this.open_local_buffer_via_lsp( target_uri, language.name().to_string(), language_server.clone(), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 81d6c7cb4d3e0baa5d18c7066199436a374e857e..f916f4079e119bc2eb9290f5911b0b0a6ab2970a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -118,8 +118,10 @@ pub struct Definition { pub target_range: Range, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Symbol { + pub worktree_id: WorktreeId, + pub language_name: String, pub label: CodeLabel, pub lsp_symbol: lsp::SymbolInformation, } @@ -193,6 +195,7 @@ impl Project { client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_lsp_command::); client.add_entity_request_handler(Self::handle_get_project_symbols); + client.add_entity_request_handler(Self::handle_open_buffer_for_symbol); client.add_entity_request_handler(Self::handle_open_buffer); client.add_entity_request_handler(Self::handle_save_buffer); } @@ -653,7 +656,7 @@ impl Project { }) } - fn open_local_buffer_from_lsp_path( + fn open_local_buffer_via_lsp( &mut self, abs_path: lsp::Url, lang_name: String, @@ -1223,22 +1226,18 @@ impl Project { self.request_lsp(buffer.clone(), GetDefinition { position }, cx) } - pub fn symbols( - &self, - query: &str, - cx: &mut ModelContext, - ) -> Task>>> { + pub fn symbols(&self, query: &str, cx: &mut ModelContext) -> Task>> { if self.is_local() { let mut language_servers = HashMap::default(); - for ((_, language_name), language_server) in self.language_servers.iter() { + for ((worktree_id, language_name), language_server) in self.language_servers.iter() { let language = self.languages.get_language(language_name).unwrap(); language_servers .entry(Arc::as_ptr(language_server)) - .or_insert((language_server.clone(), language.clone())); + .or_insert((language_server.clone(), *worktree_id, language.clone())); } let mut requests = Vec::new(); - for (language_server, _) in language_servers.values() { + for (language_server, _, _) in language_servers.values() { requests.push(language_server.request::( lsp::WorkspaceSymbolParams { query: query.to_string(), @@ -1249,17 +1248,21 @@ impl Project { cx.foreground().spawn(async move { let responses = futures::future::try_join_all(requests).await?; - let mut symbols = HashMap::default(); - for ((_, language), lsp_symbols) in language_servers.into_values().zip(responses) { - let language_symbols = symbols - .entry(language.name().to_string()) - .or_insert(Vec::new()); - for lsp_symbol in lsp_symbols.into_iter().flatten() { + let mut symbols = Vec::new(); + for ((_, worktree_id, language), lsp_symbols) in + language_servers.into_values().zip(responses) + { + symbols.extend(lsp_symbols.into_iter().flatten().map(|lsp_symbol| { let label = language .label_for_symbol(&lsp_symbol) .unwrap_or_else(|| CodeLabel::plain(lsp_symbol.name.clone(), None)); - language_symbols.push(Symbol { label, lsp_symbol }); - } + Symbol { + worktree_id, + language_name: language.name().to_string(), + label, + lsp_symbol, + } + })); } Ok(symbols) }) @@ -1270,40 +1273,15 @@ impl Project { }); cx.spawn_weak(|this, cx| async move { let response = request.await?; - let mut symbols = HashMap::default(); + let mut symbols = Vec::new(); if let Some(this) = this.upgrade(&cx) { this.read_with(&cx, |this, _| { - let mut serialized_symbols = response.symbols.into_iter(); - for (language_name, symbol_count) in response - .languages - .into_iter() - .zip(response.symbol_counts_per_language) - { - let language = this.languages.get_language(&language_name); - let language_symbols = - symbols.entry(language_name).or_insert(Vec::new()); - language_symbols.extend( - serialized_symbols - .by_ref() - .take(symbol_count as usize) - .filter_map(|serialized_symbol| { - let lsp_symbol = - serde_json::from_slice(&serialized_symbol.lsp_symbol) - .log_err()?; - Some(Symbol { - label: language - .and_then(|language| { - language.label_for_symbol(&lsp_symbol) - }) - .unwrap_or(CodeLabel::plain( - lsp_symbol.name.clone(), - None, - )), - lsp_symbol, - }) - }), - ); - } + symbols.extend( + response + .symbols + .into_iter() + .filter_map(|symbol| this.deserialize_symbol(symbol).log_err()), + ); }) } Ok(symbols) @@ -1313,6 +1291,45 @@ impl Project { } } + pub fn open_buffer_for_symbol( + &mut self, + symbol: &Symbol, + cx: &mut ModelContext, + ) -> Task>> { + if self.is_local() { + let language_server = if let Some(server) = self + .language_servers + .get(&(symbol.worktree_id, symbol.language_name.clone())) + { + server.clone() + } else { + return Task::ready(Err(anyhow!( + "language server for worktree and language not found" + ))); + }; + + self.open_local_buffer_via_lsp( + symbol.lsp_symbol.location.uri.clone(), + symbol.language_name.clone(), + language_server, + cx, + ) + } else if let Some(project_id) = self.remote_id() { + let request = self.client.request(proto::OpenBufferForSymbol { + project_id, + symbol: Some(serialize_symbol(symbol)), + }); + cx.spawn(|this, mut cx| async move { + let response = request.await?; + let buffer = response.buffer.ok_or_else(|| anyhow!("invalid buffer"))?; + this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx)) + .await + }) + } else { + Task::ready(Err(anyhow!("project does not have a remote id"))) + } + } + pub fn completions( &self, source_buffer_handle: &ModelHandle, @@ -1785,7 +1802,7 @@ impl Project { lsp::DocumentChangeOperation::Edit(op) => { let buffer_to_edit = this .update(cx, |this, cx| { - this.open_local_buffer_from_lsp_path( + this.open_local_buffer_via_lsp( op.text_document.uri, language_name.clone(), language_server.clone(), @@ -2634,21 +2651,31 @@ impl Project { }) .await?; - let mut languages = Vec::new(); - let mut symbol_counts_per_language = Vec::new(); - let mut serialized_symbols = Vec::new(); - for (language_name, language_symbols) in symbols { - languages.push(language_name); - symbol_counts_per_language.push(language_symbols.len() as u64); - serialized_symbols.extend(language_symbols.into_iter().map(|symbol| proto::Symbol { - lsp_symbol: serde_json::to_vec(&symbol.lsp_symbol).unwrap(), - })); - } - Ok(proto::GetProjectSymbolsResponse { - languages, - symbol_counts_per_language, - symbols: serialized_symbols, + symbols: symbols.iter().map(serialize_symbol).collect(), + }) + } + + async fn handle_open_buffer_for_symbol( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let peer_id = envelope.original_sender_id()?; + let symbol = envelope + .payload + .symbol + .ok_or_else(|| anyhow!("invalid symbol"))?; + let symbol = this.read_with(&cx, |this, _| this.deserialize_symbol(symbol))?; + let buffer = this + .update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx)) + .await?; + + Ok(proto::OpenBufferForSymbolResponse { + buffer: Some(this.update(&mut cx, |this, cx| { + this.serialize_buffer_for_peer(&buffer, peer_id, cx) + })), }) } @@ -2813,6 +2840,21 @@ impl Project { }) } + fn deserialize_symbol(&self, serialized_symbol: proto::Symbol) -> Result { + let language = self + .languages + .get_language(&serialized_symbol.language_name); + let lsp_symbol = serde_json::from_slice(&serialized_symbol.lsp_symbol)?; + Ok(Symbol { + worktree_id: WorktreeId::from_proto(serialized_symbol.worktree_id), + language_name: serialized_symbol.language_name.clone(), + label: language + .and_then(|language| language.label_for_symbol(&lsp_symbol)) + .unwrap_or(CodeLabel::plain(lsp_symbol.name.clone(), None)), + lsp_symbol, + }) + } + async fn handle_close_buffer( this: ModelHandle, envelope: TypedEnvelope, @@ -3094,6 +3136,14 @@ impl From for fs::RemoveOptions { } } +fn serialize_symbol(symbol: &Symbol) -> proto::Symbol { + proto::Symbol { + worktree_id: symbol.worktree_id.to_proto(), + language_name: symbol.language_name.clone(), + lsp_symbol: serde_json::to_vec(&symbol.lsp_symbol).unwrap(), + } +} + #[cfg(test)] mod tests { use super::{Event, *}; diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index a1efa11380235967bae4343d747062754a2354e9..77e125e9bba45c5b7cb08231201a2d3cddf88d96 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -10,10 +10,12 @@ path = "src/project_symbols.rs" editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } gpui = { path = "../gpui" } +language = { path = "../language" } project = { path = "../project" } text = { path = "../text" } workspace = { path = "../workspace" } util = { path = "../util" } +anyhow = "1.0.38" ordered-float = "2.1.1" postage = { version = "0.4", features = ["futures-traits"] } smol = "1.2" diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 8ab578ac6f8a6364e1fa30e61235d6e6e764f5e4..9b60521c29e317591dbf45a4cdb24ce28afc7894 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,5 +1,6 @@ use editor::{ - combine_syntax_and_fuzzy_match_highlights, styled_runs_for_code_label, Editor, EditorSettings, + combine_syntax_and_fuzzy_match_highlights, items::BufferItemHandle, styled_runs_for_code_label, + Autoscroll, Bias, Editor, EditorSettings, }; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ @@ -9,6 +10,7 @@ use gpui::{ AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; +use language::range_from_lsp; use ordered_float::OrderedFloat; use postage::watch; use project::{Project, Symbol}; @@ -52,6 +54,7 @@ pub struct ProjectSymbolsView { pub enum Event { Dismissed, + Selected(Symbol), } impl Entity for ProjectSymbolsView { @@ -170,7 +173,13 @@ impl ProjectSymbolsView { } fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - cx.emit(Event::Dismissed); + if let Some(symbol) = self + .matches + .get(self.selected_match_index) + .map(|mat| self.symbols[mat.candidate_id].clone()) + { + cx.emit(Event::Selected(symbol)); + } } fn update_matches(&mut self, cx: &mut ViewContext) { @@ -180,12 +189,7 @@ impl ProjectSymbolsView { .project .update(cx, |project, cx| project.symbols(&query, cx)); self.pending_symbols_task = cx.spawn_weak(|this, mut cx| async move { - let symbols = symbols - .await - .log_err()? - .into_values() - .flatten() - .collect::>(); + let symbols = symbols.await.log_err()?; if let Some(this) = this.upgrade(&cx) { this.update(&mut cx, |this, cx| { this.match_candidates = symbols @@ -336,6 +340,34 @@ impl ProjectSymbolsView { ) { match event { Event::Dismissed => workspace.dismiss_modal(cx), + Event::Selected(symbol) => { + let buffer = workspace + .project() + .update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx)); + let symbol = symbol.clone(); + cx.spawn(|workspace, mut cx| async move { + let buffer = buffer.await?; + let range = range_from_lsp(symbol.lsp_symbol.location.range); + workspace.update(&mut cx, |workspace, cx| { + let start; + let end; + { + let buffer = buffer.read(cx); + start = buffer.clip_point_utf16(range.start, Bias::Left); + end = buffer.clip_point_utf16(range.end, Bias::Left); + } + + let editor = workspace.open_item(BufferItemHandle(buffer), cx); + let editor = editor.downcast::().unwrap(); + editor.update(cx, |editor, cx| { + editor.select_ranges([start..end], Some(Autoscroll::Center), cx); + }); + }); + Ok::<_, anyhow::Error>(()) + }) + .detach_and_log_err(cx); + workspace.dismiss_modal(cx); + } } } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index e0024dae7e8fe1c77530aa77be25a82b5c1fff4e..c6236474f298f62ff156fbb24ed8b9effecad9a1 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -25,53 +25,55 @@ message Envelope { GetDefinitionResponse get_definition_response = 19; GetProjectSymbols get_project_symbols = 20; GetProjectSymbolsResponse get_project_symbols_response = 21; - - RegisterWorktree register_worktree = 22; - UnregisterWorktree unregister_worktree = 23; - ShareWorktree share_worktree = 24; - UpdateWorktree update_worktree = 25; - UpdateDiagnosticSummary update_diagnostic_summary = 26; - DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 27; - DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 28; - - OpenBuffer open_buffer = 29; - OpenBufferResponse open_buffer_response = 30; - CloseBuffer close_buffer = 31; - UpdateBuffer update_buffer = 32; - UpdateBufferFile update_buffer_file = 33; - SaveBuffer save_buffer = 34; - BufferSaved buffer_saved = 35; - BufferReloaded buffer_reloaded = 36; - FormatBuffers format_buffers = 37; - FormatBuffersResponse format_buffers_response = 38; - GetCompletions get_completions = 39; - GetCompletionsResponse get_completions_response = 40; - ApplyCompletionAdditionalEdits apply_completion_additional_edits = 41; - ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 42; - GetCodeActions get_code_actions = 43; - GetCodeActionsResponse get_code_actions_response = 44; - ApplyCodeAction apply_code_action = 45; - ApplyCodeActionResponse apply_code_action_response = 46; - PrepareRename prepare_rename = 47; - PrepareRenameResponse prepare_rename_response = 48; - PerformRename perform_rename = 49; - PerformRenameResponse perform_rename_response = 50; - - GetChannels get_channels = 51; - GetChannelsResponse get_channels_response = 52; - JoinChannel join_channel = 53; - JoinChannelResponse join_channel_response = 54; - LeaveChannel leave_channel = 55; - SendChannelMessage send_channel_message = 56; - SendChannelMessageResponse send_channel_message_response = 57; - ChannelMessageSent channel_message_sent = 58; - GetChannelMessages get_channel_messages = 59; - GetChannelMessagesResponse get_channel_messages_response = 60; - - UpdateContacts update_contacts = 61; - - GetUsers get_users = 62; - GetUsersResponse get_users_response = 63; + OpenBufferForSymbol open_buffer_for_symbol = 22; + OpenBufferForSymbolResponse open_buffer_for_symbol_response = 23; + + RegisterWorktree register_worktree = 24; + UnregisterWorktree unregister_worktree = 25; + ShareWorktree share_worktree = 26; + UpdateWorktree update_worktree = 27; + UpdateDiagnosticSummary update_diagnostic_summary = 28; + DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 29; + DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 30; + + OpenBuffer open_buffer = 31; + OpenBufferResponse open_buffer_response = 32; + CloseBuffer close_buffer = 33; + UpdateBuffer update_buffer = 34; + UpdateBufferFile update_buffer_file = 35; + SaveBuffer save_buffer = 36; + BufferSaved buffer_saved = 37; + BufferReloaded buffer_reloaded = 38; + FormatBuffers format_buffers = 39; + FormatBuffersResponse format_buffers_response = 40; + GetCompletions get_completions = 41; + GetCompletionsResponse get_completions_response = 42; + ApplyCompletionAdditionalEdits apply_completion_additional_edits = 43; + ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 44; + GetCodeActions get_code_actions = 45; + GetCodeActionsResponse get_code_actions_response = 46; + ApplyCodeAction apply_code_action = 47; + ApplyCodeActionResponse apply_code_action_response = 48; + PrepareRename prepare_rename = 49; + PrepareRenameResponse prepare_rename_response = 50; + PerformRename perform_rename = 51; + PerformRenameResponse perform_rename_response = 52; + + GetChannels get_channels = 53; + GetChannelsResponse get_channels_response = 54; + JoinChannel join_channel = 55; + JoinChannelResponse join_channel_response = 56; + LeaveChannel leave_channel = 57; + SendChannelMessage send_channel_message = 58; + SendChannelMessageResponse send_channel_message_response = 59; + ChannelMessageSent channel_message_sent = 60; + GetChannelMessages get_channel_messages = 61; + GetChannelMessagesResponse get_channel_messages_response = 62; + + UpdateContacts update_contacts = 63; + + GetUsers get_users = 64; + GetUsersResponse get_users_response = 65; } } @@ -179,13 +181,22 @@ message GetProjectSymbols { } message GetProjectSymbolsResponse { - repeated string languages = 1; - repeated uint64 symbol_counts_per_language = 2; - repeated Symbol symbols = 3; + repeated Symbol symbols = 4; } message Symbol { - bytes lsp_symbol = 1; + uint64 worktree_id = 1; + string language_name = 2; + bytes lsp_symbol = 3; +} + +message OpenBufferForSymbol { + uint64 project_id = 1; + Symbol symbol = 2; +} + +message OpenBufferForSymbolResponse { + Buffer buffer = 1; } message OpenBuffer { diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index ae04c31cbddf250bb03904921b94e5269bd25990..e11586e9ddb378b391a5cea61505217d419cc388 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -158,7 +158,7 @@ messages!( (GetDefinition, Foreground), (GetDefinitionResponse, Foreground), (GetProjectSymbols, Background), - (GetProjectSymbolsResponse, Foreground), + (GetProjectSymbolsResponse, Background), (GetUsers, Foreground), (GetUsersResponse, Foreground), (JoinChannel, Foreground), @@ -168,6 +168,8 @@ messages!( (LeaveChannel, Foreground), (LeaveProject, Foreground), (OpenBuffer, Foreground), + (OpenBufferForSymbol, Foreground), + (OpenBufferForSymbolResponse, Foreground), (OpenBufferResponse, Foreground), (PerformRename, Background), (PerformRenameResponse, Background), @@ -211,6 +213,7 @@ request_messages!( (JoinChannel, JoinChannelResponse), (JoinProject, JoinProjectResponse), (OpenBuffer, OpenBufferResponse), + (OpenBufferForSymbol, OpenBufferForSymbolResponse), (Ping, Ack), (PerformRename, PerformRenameResponse), (PrepareRename, PrepareRenameResponse), @@ -243,6 +246,7 @@ entity_messages!( JoinProject, LeaveProject, OpenBuffer, + OpenBufferForSymbol, PerformRename, PrepareRename, RemoveProjectCollaborator, diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 575a0ccccc864786ea54a0e0e1ff1e4c4b362a8f..8de7da219a87c79caa7957a952d85ad8770d9546 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -80,6 +80,7 @@ impl Server { .add_message_handler(Server::disk_based_diagnostics_updated) .add_request_handler(Server::get_definition) .add_request_handler(Server::get_project_symbols) + .add_request_handler(Server::open_buffer_for_symbol) .add_request_handler(Server::open_buffer) .add_message_handler(Server::close_buffer) .add_request_handler(Server::update_buffer) @@ -602,6 +603,20 @@ impl Server { .await?) } + async fn open_buffer_for_symbol( + self: Arc, + request: TypedEnvelope, + ) -> tide::Result { + let host_connection_id = self + .state() + .read_project(request.payload.project_id, request.sender_id)? + .host_connection_id; + Ok(self + .peer + .forward_request(request.sender_id, host_connection_id, request.payload) + .await?) + } + async fn open_buffer( self: Arc, request: TypedEnvelope,