Rename `CompletionLabel` to `CodeLabel` and add `Project::symbols`

Antonio Scandurra created

This only works locally for now and we haven't implemented the
`RustLsp::label_for_symbol` method yet.

Change summary

crates/editor/src/editor.rs                   |  8 +-
crates/language/src/buffer.rs                 |  4 
crates/language/src/language.rs               | 34 +++++------
crates/language/src/proto.rs                  |  7 +
crates/project/src/project.rs                 | 61 ++++++++++++++++++--
crates/project_symbols/src/project_symbols.rs | 22 +++++-
crates/zed/src/language.rs                    | 22 ++----
7 files changed, 107 insertions(+), 51 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -31,7 +31,7 @@ use gpui::{
 use items::{BufferItemHandle, MultiBufferItemHandle};
 use itertools::Itertools as _;
 use language::{
-    AnchorRangeExt as _, BracketPair, Buffer, CodeAction, Completion, CompletionLabel, Diagnostic,
+    AnchorRangeExt as _, BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic,
     DiagnosticSeverity, Language, Point, Selection, SelectionGoal, TransactionId,
 };
 use multi_buffer::MultiBufferChunks;
@@ -600,7 +600,7 @@ impl CompletionsMenu {
                                 .with_highlights(combine_syntax_and_fuzzy_match_highlights(
                                     &completion.label.text,
                                     settings.style.text.color.into(),
-                                    styled_runs_for_completion_label(
+                                    styled_runs_for_code_label(
                                         &completion.label,
                                         settings.style.text.color,
                                         &settings.style.syntax,
@@ -5654,8 +5654,8 @@ pub fn combine_syntax_and_fuzzy_match_highlights(
     result
 }
 
-fn styled_runs_for_completion_label<'a>(
-    label: &'a CompletionLabel,
+pub fn styled_runs_for_code_label<'a>(
+    label: &'a CodeLabel,
     default_color: Color,
     syntax_theme: &'a theme::SyntaxTheme,
 ) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {

crates/language/src/buffer.rs 🔗

@@ -7,7 +7,7 @@ pub use crate::{
 use crate::{
     diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
     outline::OutlineItem,
-    range_from_lsp, CompletionLabel, Outline, ToLspPosition,
+    range_from_lsp, CodeLabel, Outline, ToLspPosition,
 };
 use anyhow::{anyhow, Result};
 use clock::ReplicaId;
@@ -117,7 +117,7 @@ pub struct Diagnostic {
 pub struct Completion {
     pub old_range: Range<Anchor>,
     pub new_text: String,
-    pub label: CompletionLabel,
+    pub label: CodeLabel,
     pub lsp_completion: lsp::CompletionItem,
 }
 

crates/language/src/language.rs 🔗

@@ -77,21 +77,19 @@ pub trait LspExt: 'static + Send + Sync {
     ) -> BoxFuture<'static, Result<PathBuf>>;
     fn cached_server_binary(&self, download_dir: Arc<Path>) -> BoxFuture<'static, Option<PathBuf>>;
     fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams);
-    fn label_for_completion(
-        &self,
-        _: &lsp::CompletionItem,
-        _: &Language,
-    ) -> Option<CompletionLabel> {
+    fn label_for_completion(&self, _: &lsp::CompletionItem, _: &Language) -> Option<CodeLabel> {
+        None
+    }
+    fn label_for_symbol(&self, _: &lsp::SymbolInformation, _: &Language) -> Option<CodeLabel> {
         None
     }
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
-pub struct CompletionLabel {
+pub struct CodeLabel {
     pub text: String,
     pub runs: Vec<(Range<usize>, HighlightId)>,
     pub filter_range: Range<usize>,
-    pub left_aligned_len: usize,
 }
 
 #[derive(Default, Deserialize)]
@@ -431,15 +429,16 @@ impl Language {
         }
     }
 
-    pub fn label_for_completion(
-        &self,
-        completion: &lsp::CompletionItem,
-    ) -> Option<CompletionLabel> {
+    pub fn label_for_completion(&self, completion: &lsp::CompletionItem) -> Option<CodeLabel> {
         self.lsp_ext
             .as_ref()?
             .label_for_completion(completion, self)
     }
 
+    pub fn label_for_symbol(&self, symbol: &lsp::SymbolInformation) -> Option<CodeLabel> {
+        self.lsp_ext.as_ref()?.label_for_symbol(symbol, self)
+    }
+
     pub fn highlight_text<'a>(
         &'a self,
         text: &'a Rope,
@@ -507,16 +506,15 @@ impl Grammar {
     }
 }
 
-impl CompletionLabel {
-    pub fn plain(completion: &lsp::CompletionItem) -> Self {
+impl CodeLabel {
+    pub fn plain(text: String, filter_text: Option<&str>) -> Self {
         let mut result = Self {
-            text: completion.label.clone(),
             runs: Vec::new(),
-            left_aligned_len: completion.label.len(),
-            filter_range: 0..completion.label.len(),
+            filter_range: 0..text.len(),
+            text,
         };
-        if let Some(filter_text) = &completion.filter_text {
-            if let Some(ix) = completion.label.find(filter_text) {
+        if let Some(filter_text) = filter_text {
+            if let Some(ix) = result.text.find(filter_text) {
                 result.filter_range = ix..ix + filter_text.len();
             }
         }

crates/language/src/proto.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    diagnostic_set::DiagnosticEntry, CodeAction, Completion, CompletionLabel, Diagnostic, Language,
+    diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, Diagnostic, Language,
     Operation,
 };
 use anyhow::{anyhow, Result};
@@ -421,7 +421,10 @@ pub fn deserialize_completion(
         new_text: completion.new_text,
         label: language
             .and_then(|l| l.label_for_completion(&lsp_completion))
-            .unwrap_or(CompletionLabel::plain(&lsp_completion)),
+            .unwrap_or(CodeLabel::plain(
+                lsp_completion.label.clone(),
+                lsp_completion.filter_text.as_deref(),
+            )),
         lsp_completion,
     })
 }

crates/project/src/project.rs 🔗

@@ -10,11 +10,11 @@ use collections::{hash_map, HashMap, HashSet};
 use futures::{future::Shared, Future, FutureExt};
 use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
 use gpui::{
-    fonts::HighlightStyle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
-    MutableAppContext, Task, UpgradeModelHandle, WeakModelHandle,
+    AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
+    UpgradeModelHandle, WeakModelHandle,
 };
 use language::{
-    range_from_lsp, Anchor, AnchorRangeExt, Bias, Buffer, CodeAction, Completion, CompletionLabel,
+    range_from_lsp, Anchor, AnchorRangeExt, Bias, Buffer, CodeAction, CodeLabel, Completion,
     Diagnostic, DiagnosticEntry, File as _, Language, LanguageRegistry, Operation, PointUtf16,
     ToLspPosition, ToOffset, ToPointUtf16, Transaction,
 };
@@ -119,8 +119,8 @@ pub struct Definition {
 }
 
 pub struct ProjectSymbol {
-    pub text: String,
-    pub highlight_ranges: Vec<(Range<usize>, HighlightStyle)>,
+    pub label: CodeLabel,
+    pub lsp_symbol: lsp::SymbolInformation,
 }
 
 #[derive(Default)]
@@ -1221,6 +1221,50 @@ impl Project {
         self.request_lsp(buffer.clone(), GetDefinition { position }, cx)
     }
 
+    pub fn symbols(
+        &self,
+        query: &str,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<ProjectSymbol>>> {
+        if self.is_local() {
+            let mut language_servers = HashMap::default();
+            for ((_, 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()));
+            }
+
+            let mut requests = Vec::new();
+            for (language_server, _) in language_servers.values() {
+                requests.push(language_server.request::<lsp::request::WorkspaceSymbol>(
+                    lsp::WorkspaceSymbolParams {
+                        query: query.to_string(),
+                        ..Default::default()
+                    },
+                ));
+            }
+
+            cx.foreground().spawn(async move {
+                let responses = futures::future::try_join_all(requests).await?;
+                let mut symbols = Vec::new();
+                for ((_, language), lsp_symbols) in language_servers.values().zip(responses) {
+                    for lsp_symbol in lsp_symbols.into_iter().flatten() {
+                        let label = language
+                            .label_for_symbol(&lsp_symbol)
+                            .unwrap_or_else(|| CodeLabel::plain(lsp_symbol.name.clone(), None));
+                        symbols.push(ProjectSymbol { label, lsp_symbol });
+                    }
+                }
+                Ok(symbols)
+            })
+        } else if let Some(project_id) = self.remote_id() {
+            todo!()
+        } else {
+            Task::ready(Ok(Default::default()))
+        }
+    }
+
     pub fn completions<T: ToPointUtf16>(
         &self,
         source_buffer_handle: &ModelHandle<Buffer>,
@@ -1300,7 +1344,12 @@ impl Project {
                                     label: language
                                         .as_ref()
                                         .and_then(|l| l.label_for_completion(&lsp_completion))
-                                        .unwrap_or_else(|| CompletionLabel::plain(&lsp_completion)),
+                                        .unwrap_or_else(|| {
+                                            CodeLabel::plain(
+                                                lsp_completion.label.clone(),
+                                                lsp_completion.filter_text.as_deref(),
+                                            )
+                                        }),
                                     lsp_completion,
                                 })
                             } else {

crates/project_symbols/src/project_symbols.rs 🔗

@@ -1,6 +1,8 @@
 use std::{cmp, sync::Arc};
 
-use editor::{combine_syntax_and_fuzzy_match_highlights, Editor, EditorSettings};
+use editor::{
+    combine_syntax_and_fuzzy_match_highlights, styled_runs_for_code_label, Editor, EditorSettings,
+};
 use fuzzy::StringMatch;
 use gpui::{
     action,
@@ -167,7 +169,12 @@ impl ProjectSymbolsView {
         cx.emit(Event::Dismissed);
     }
 
-    fn update_matches(&mut self, _: &mut ViewContext<Self>) {}
+    fn update_matches(&mut self, cx: &mut ViewContext<Self>) {
+        let query = self.query_editor.read(cx).text(cx);
+        self.project
+            .update(cx, |project, cx| project.symbols(&query, cx))
+            .detach_and_log_err(cx);
+    }
 
     fn render_matches(&self) -> ElementBox {
         if self.matches.is_empty() {
@@ -215,13 +222,18 @@ impl ProjectSymbolsView {
             &settings.theme.selector.item
         };
         let symbol = &self.symbols[string_match.candidate_id];
+        let syntax_runs = styled_runs_for_code_label(
+            &symbol.label,
+            style.label.text.color,
+            &settings.theme.editor.syntax,
+        );
 
-        Text::new(symbol.text.clone(), style.label.text.clone())
+        Text::new(symbol.label.text.clone(), style.label.text.clone())
             .with_soft_wrap(false)
             .with_highlights(combine_syntax_and_fuzzy_match_highlights(
-                &symbol.text,
+                &symbol.label.text,
                 style.label.text.clone().into(),
-                symbol.highlight_ranges.iter().cloned(),
+                syntax_runs,
                 &string_match.positions,
             ))
             .contained()

crates/zed/src/language.rs 🔗

@@ -159,7 +159,7 @@ impl LspExt for RustLsp {
         &self,
         completion: &lsp::CompletionItem,
         language: &Language,
-    ) -> Option<CompletionLabel> {
+    ) -> Option<CodeLabel> {
         match completion.kind {
             Some(lsp::CompletionItemKind::FIELD) if completion.detail.is_some() => {
                 let detail = completion.detail.as_ref().unwrap();
@@ -167,11 +167,10 @@ impl LspExt for RustLsp {
                 let text = format!("{}: {}", name, detail);
                 let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
                 let runs = language.highlight_text(&source, 11..11 + text.len());
-                return Some(CompletionLabel {
+                return Some(CodeLabel {
                     text,
                     runs,
                     filter_range: 0..name.len(),
-                    left_aligned_len: name.len(),
                 });
             }
             Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE)
@@ -182,11 +181,10 @@ impl LspExt for RustLsp {
                 let text = format!("{}: {}", name, detail);
                 let source = Rope::from(format!("let {} = ();", text).as_str());
                 let runs = language.highlight_text(&source, 4..4 + text.len());
-                return Some(CompletionLabel {
+                return Some(CodeLabel {
                     text,
                     runs,
                     filter_range: 0..name.len(),
-                    left_aligned_len: name.len(),
                 });
             }
             Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD)
@@ -201,8 +199,7 @@ impl LspExt for RustLsp {
                     let text = REGEX.replace(&completion.label, &detail[2..]).to_string();
                     let source = Rope::from(format!("fn {} {{}}", text).as_str());
                     let runs = language.highlight_text(&source, 3..3 + text.len());
-                    return Some(CompletionLabel {
-                        left_aligned_len: text.find("->").unwrap_or(text.len()),
+                    return Some(CodeLabel {
                         filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
                         text,
                         runs,
@@ -222,7 +219,7 @@ impl LspExt for RustLsp {
                     _ => None,
                 };
                 let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name?)?;
-                let mut label = CompletionLabel::plain(&completion);
+                let mut label = CodeLabel::plain(completion.label.clone(), None);
                 label.runs.push((
                     0..label.text.rfind('(').unwrap_or(label.text.len()),
                     highlight_id,
@@ -350,7 +347,7 @@ mod tests {
                 detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
                 ..Default::default()
             }),
-            Some(CompletionLabel {
+            Some(CodeLabel {
                 text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
                 filter_range: 0..5,
                 runs: vec![
@@ -361,7 +358,6 @@ mod tests {
                     (25..28, highlight_type),
                     (29..30, highlight_type),
                 ],
-                left_aligned_len: 22,
             })
         );
 
@@ -372,11 +368,10 @@ mod tests {
                 detail: Some("usize".to_string()),
                 ..Default::default()
             }),
-            Some(CompletionLabel {
+            Some(CodeLabel {
                 text: "len: usize".to_string(),
                 filter_range: 0..3,
                 runs: vec![(0..3, highlight_field), (5..10, highlight_type),],
-                left_aligned_len: 3,
             })
         );
 
@@ -387,7 +382,7 @@ mod tests {
                 detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
                 ..Default::default()
             }),
-            Some(CompletionLabel {
+            Some(CodeLabel {
                 text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
                 filter_range: 0..5,
                 runs: vec![
@@ -398,7 +393,6 @@ mod tests {
                     (25..28, highlight_type),
                     (29..30, highlight_type),
                 ],
-                left_aligned_len: 22,
             })
         );
     }