diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f916f4079e..e651c16a81 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -25,7 +25,7 @@ use smol::block_on; use std::{ convert::TryInto, ops::Range, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, sync::{atomic::AtomicBool, Arc}, time::Instant, }; @@ -120,8 +120,10 @@ pub struct Definition { #[derive(Clone, Debug)] pub struct Symbol { + pub source_worktree_id: WorktreeId, pub worktree_id: WorktreeId, pub language_name: String, + pub path: PathBuf, pub label: CodeLabel, pub lsp_symbol: lsp::SymbolInformation, } @@ -1230,14 +1232,24 @@ impl Project { if self.is_local() { let mut language_servers = HashMap::default(); 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(), *worktree_id, language.clone())); + if let Some((worktree, language)) = self + .worktree_for_id(*worktree_id, cx) + .and_then(|worktree| worktree.read(cx).as_local()) + .zip(self.languages.get_language(language_name)) + { + language_servers + .entry(Arc::as_ptr(language_server)) + .or_insert(( + language_server.clone(), + *worktree_id, + worktree.abs_path().clone(), + 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(), @@ -1246,24 +1258,48 @@ impl Project { )); } - cx.foreground().spawn(async move { + cx.spawn_weak(|this, cx| async move { let responses = futures::future::try_join_all(requests).await?; + 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)); - Symbol { - worktree_id, - language_name: language.name().to_string(), - label, - lsp_symbol, + if let Some(this) = this.upgrade(&cx) { + this.read_with(&cx, |this, cx| { + for ((_, source_worktree_id, worktree_abs_path, language), lsp_symbols) in + language_servers.into_values().zip(responses) + { + symbols.extend(lsp_symbols.into_iter().flatten().filter_map( + |lsp_symbol| { + let abs_path = lsp_symbol.location.uri.to_file_path().ok()?; + let mut worktree_id = source_worktree_id; + let path; + if let Some((worktree, rel_path)) = + this.find_local_worktree(&abs_path, cx) + { + worktree_id = worktree.read(cx).id(); + path = rel_path; + } else { + path = relativize_path(&worktree_abs_path, &abs_path); + } + + let label = + language.label_for_symbol(&lsp_symbol).unwrap_or_else( + || CodeLabel::plain(lsp_symbol.name.clone(), None), + ); + + Some(Symbol { + source_worktree_id, + worktree_id, + language_name: language.name().to_string(), + label, + path, + lsp_symbol, + }) + }, + )); } - })); + }) } + Ok(symbols) }) } else if let Some(project_id) = self.remote_id() { @@ -1299,7 +1335,7 @@ impl Project { if self.is_local() { let language_server = if let Some(server) = self .language_servers - .get(&(symbol.worktree_id, symbol.language_name.clone())) + .get(&(symbol.source_worktree_id, symbol.language_name.clone())) { server.clone() } else { @@ -1308,8 +1344,24 @@ impl Project { ))); }; + let worktree_abs_path = if let Some(worktree_abs_path) = self + .worktree_for_id(symbol.worktree_id, cx) + .and_then(|worktree| worktree.read(cx).as_local()) + .map(|local_worktree| local_worktree.abs_path()) + { + worktree_abs_path + } else { + return Task::ready(Err(anyhow!("worktree not found for symbol"))); + }; + let symbol_abs_path = worktree_abs_path.join(&symbol.path); + let symbol_uri = if let Ok(uri) = lsp::Url::from_file_path(symbol_abs_path) { + uri + } else { + return Task::ready(Err(anyhow!("invalid symbol path"))); + }; + self.open_local_buffer_via_lsp( - symbol.lsp_symbol.location.uri.clone(), + symbol_uri, symbol.language_name.clone(), language_server, cx, @@ -2846,11 +2898,13 @@ impl Project { .get_language(&serialized_symbol.language_name); let lsp_symbol = serde_json::from_slice(&serialized_symbol.lsp_symbol)?; Ok(Symbol { + source_worktree_id: WorktreeId::from_proto(serialized_symbol.source_worktree_id), 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)), + path: PathBuf::from(serialized_symbol.path), lsp_symbol, }) } @@ -3138,12 +3192,43 @@ impl From for fs::RemoveOptions { fn serialize_symbol(symbol: &Symbol) -> proto::Symbol { proto::Symbol { + source_worktree_id: symbol.source_worktree_id.to_proto(), worktree_id: symbol.worktree_id.to_proto(), language_name: symbol.language_name.clone(), + path: symbol.path.to_string_lossy().to_string(), lsp_symbol: serde_json::to_vec(&symbol.lsp_symbol).unwrap(), } } +fn relativize_path(base: &Path, path: &Path) -> PathBuf { + let mut path_components = path.components(); + let mut base_components = base.components(); + let mut components: Vec = Vec::new(); + loop { + match (path_components.next(), base_components.next()) { + (None, None) => break, + (Some(a), None) => { + components.push(a); + components.extend(path_components.by_ref()); + break; + } + (None, _) => components.push(Component::ParentDir), + (Some(a), Some(b)) if components.is_empty() && a == b => (), + (Some(a), Some(b)) if b == Component::CurDir => components.push(a), + (Some(a), Some(_)) => { + components.push(Component::ParentDir); + for _ in base_components { + components.push(Component::ParentDir); + } + components.push(a); + components.extend(path_components.by_ref()); + break; + } + } + } + components.iter().map(|c| c.as_os_str()).collect() +} + #[cfg(test)] mod tests { use super::{Event, *}; diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 9b60521c29..3cd3ef1132 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -306,14 +306,25 @@ impl ProjectSymbolsView { &settings.theme.editor.syntax, ); - Text::new(symbol.label.text.clone(), style.label.text.clone()) - .with_soft_wrap(false) - .with_highlights(combine_syntax_and_fuzzy_match_highlights( - &symbol.label.text, - style.label.text.clone().into(), - syntax_runs, - &string_match.positions, - )) + Flex::column() + .with_child( + Text::new(symbol.label.text.clone(), style.label.text.clone()) + .with_soft_wrap(false) + .with_highlights(combine_syntax_and_fuzzy_match_highlights( + &symbol.label.text, + style.label.text.clone().into(), + syntax_runs, + &string_match.positions, + )) + .boxed(), + ) + .with_child( + Label::new( + symbol.path.to_string_lossy().to_string(), + style.label.clone(), + ) + .boxed(), + ) .contained() .with_style(style.container) .boxed() diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index c6236474f2..0f001a26f7 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -185,9 +185,11 @@ message GetProjectSymbolsResponse { } message Symbol { - uint64 worktree_id = 1; - string language_name = 2; - bytes lsp_symbol = 3; + uint64 source_worktree_id = 1; + uint64 worktree_id = 2; + string language_name = 3; + string path = 4; + bytes lsp_symbol = 5; } message OpenBufferForSymbol {