Cargo.lock 🔗
@@ -3563,9 +3563,11 @@ dependencies = [
name = "project_symbols"
version = "0.1.0"
dependencies = [
+ "anyhow",
"editor",
"fuzzy",
"gpui",
+ "language",
"ordered-float",
"postage",
"project",
Antonio Scandurra created
Cargo.lock | 2
crates/project/src/lsp_command.rs | 2
crates/project/src/project.rs | 180 +++++++++++++-------
crates/project_symbols/Cargo.toml | 2
crates/project_symbols/src/project_symbols.rs | 48 ++++
crates/rpc/proto/zed.proto | 113 +++++++-----
crates/rpc/src/proto.rs | 6
crates/server/src/rpc.rs | 15 +
8 files changed, 242 insertions(+), 126 deletions(-)
@@ -3563,9 +3563,11 @@ dependencies = [
name = "project_symbols"
version = "0.1.0"
dependencies = [
+ "anyhow",
"editor",
"fuzzy",
"gpui",
+ "language",
"ordered-float",
"postage",
"project",
@@ -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(),
@@ -118,8 +118,10 @@ pub struct Definition {
pub target_range: Range<language::Anchor>,
}
-#[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::<PrepareRename>);
client.add_entity_request_handler(Self::handle_lsp_command::<PerformRename>);
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<Self>,
- ) -> Task<Result<HashMap<String, Vec<Symbol>>>> {
+ pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
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::request::WorkspaceSymbol>(
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<Self>,
+ ) -> Task<Result<ModelHandle<Buffer>>> {
+ 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<T: ToPointUtf16>(
&self,
source_buffer_handle: &ModelHandle<Buffer>,
@@ -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<Self>,
+ envelope: TypedEnvelope<proto::OpenBufferForSymbol>,
+ _: Arc<Client>,
+ mut cx: AsyncAppContext,
+ ) -> Result<proto::OpenBufferForSymbolResponse> {
+ 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<Symbol> {
+ 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<Self>,
envelope: TypedEnvelope<proto::CloseBuffer>,
@@ -3094,6 +3136,14 @@ impl From<lsp::DeleteFileOptions> 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, *};
@@ -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"
@@ -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<Self>) {
- 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<Self>) {
@@ -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::<Vec<_>>();
+ 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::<Editor>().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);
+ }
}
}
}
@@ -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 {
@@ -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,
@@ -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<Server>,
+ request: TypedEnvelope<proto::OpenBufferForSymbol>,
+ ) -> tide::Result<proto::OpenBufferForSymbolResponse> {
+ 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<Server>,
request: TypedEnvelope<proto::OpenBuffer>,