Don't serialize the full LSP symbol when collaborating

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                                    |  1 
crates/language/src/language.rs               |  6 +-
crates/project/src/project.rs                 | 45 +++++++++++++----
crates/project_symbols/Cargo.toml             |  1 
crates/project_symbols/src/project_symbols.rs |  6 -
crates/rpc/proto/zed.proto                    | 12 ++++
crates/zed/src/language.rs                    | 52 +++++++-------------
7 files changed, 68 insertions(+), 55 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3567,7 +3567,6 @@ dependencies = [
  "editor",
  "fuzzy",
  "gpui",
- "language",
  "ordered-float",
  "postage",
  "project",

crates/language/src/language.rs 🔗

@@ -80,7 +80,7 @@ pub trait LspExt: 'static + Send + Sync {
     fn label_for_completion(&self, _: &lsp::CompletionItem, _: &Language) -> Option<CodeLabel> {
         None
     }
-    fn label_for_symbol(&self, _: &lsp::SymbolInformation, _: &Language) -> Option<CodeLabel> {
+    fn label_for_symbol(&self, _: &str, _: lsp::SymbolKind, _: &Language) -> Option<CodeLabel> {
         None
     }
 }
@@ -435,8 +435,8 @@ impl Language {
             .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 label_for_symbol(&self, name: &str, kind: lsp::SymbolKind) -> Option<CodeLabel> {
+        self.lsp_ext.as_ref()?.label_for_symbol(name, kind, self)
     }
 
     pub fn highlight_text<'a>(

crates/project/src/project.rs 🔗

@@ -24,6 +24,7 @@ use postage::{broadcast, prelude::Stream, sink::Sink, watch};
 use smol::block_on;
 use std::{
     convert::TryInto,
+    mem,
     ops::Range,
     path::{Component, Path, PathBuf},
     sync::{atomic::AtomicBool, Arc},
@@ -125,7 +126,9 @@ pub struct Symbol {
     pub language_name: String,
     pub path: PathBuf,
     pub label: CodeLabel,
-    pub lsp_symbol: lsp::SymbolInformation,
+    pub name: String,
+    pub kind: lsp::SymbolKind,
+    pub range: Range<PointUtf16>,
 }
 
 #[derive(Default)]
@@ -1281,18 +1284,21 @@ impl Project {
                                         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),
-                                        );
+                                    let label = language
+                                        .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind)
+                                        .unwrap_or_else(|| {
+                                            CodeLabel::plain(lsp_symbol.name.clone(), None)
+                                        });
 
                                     Some(Symbol {
                                         source_worktree_id,
                                         worktree_id,
                                         language_name: language.name().to_string(),
+                                        name: lsp_symbol.name,
+                                        kind: lsp_symbol.kind,
                                         label,
                                         path,
-                                        lsp_symbol,
+                                        range: range_from_lsp(lsp_symbol.location.range),
                                     })
                                 },
                             ));
@@ -2896,16 +2902,24 @@ impl Project {
         let language = self
             .languages
             .get_language(&serialized_symbol.language_name);
-        let lsp_symbol = serde_json::from_slice(&serialized_symbol.lsp_symbol)?;
+        let start = serialized_symbol
+            .start
+            .ok_or_else(|| anyhow!("invalid start"))?;
+        let end = serialized_symbol
+            .end
+            .ok_or_else(|| anyhow!("invalid end"))?;
+        let kind = unsafe { mem::transmute(serialized_symbol.kind) };
         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)),
+                .and_then(|language| language.label_for_symbol(&serialized_symbol.name, kind))
+                .unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None)),
+            name: serialized_symbol.name,
             path: PathBuf::from(serialized_symbol.path),
-            lsp_symbol,
+            range: PointUtf16::new(start.row, start.column)..PointUtf16::new(end.row, end.column),
+            kind,
         })
     }
 
@@ -3195,8 +3209,17 @@ fn serialize_symbol(symbol: &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(),
+        name: symbol.name.clone(),
+        kind: unsafe { mem::transmute(symbol.kind) },
         path: symbol.path.to_string_lossy().to_string(),
-        lsp_symbol: serde_json::to_vec(&symbol.lsp_symbol).unwrap(),
+        start: Some(proto::Point {
+            row: symbol.range.start.row,
+            column: symbol.range.start.column,
+        }),
+        end: Some(proto::Point {
+            row: symbol.range.end.row,
+            column: symbol.range.end.column,
+        }),
     }
 }
 

crates/project_symbols/Cargo.toml 🔗

@@ -10,7 +10,6 @@ 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" }

crates/project_symbols/src/project_symbols.rs 🔗

@@ -10,7 +10,6 @@ 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};
@@ -358,14 +357,13 @@ impl ProjectSymbolsView {
                 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);
+                            start = buffer.clip_point_utf16(symbol.range.start, Bias::Left);
+                            end = buffer.clip_point_utf16(symbol.range.end, Bias::Left);
                         }
 
                         let editor = workspace.open_item(BufferItemHandle(buffer), cx);

crates/rpc/proto/zed.proto 🔗

@@ -188,8 +188,11 @@ message Symbol {
     uint64 source_worktree_id = 1;
     uint64 worktree_id = 2;
     string language_name = 3;
-    string path = 4;
-    bytes lsp_symbol = 5;
+    string name = 4;
+    int32 kind = 5;
+    string path = 6;
+    Point start = 7;
+    Point end = 8;
 }
 
 message OpenBufferForSymbol {
@@ -620,6 +623,11 @@ message Range {
     uint64 end = 2;
 }
 
+message Point {
+    uint32 row = 1;
+    uint32 column = 2;
+}
+
 message Nonce {
     uint64 upper_half = 1;
     uint64 lower_half = 2;

crates/zed/src/language.rs 🔗

@@ -233,49 +233,50 @@ impl LspExt for RustLsp {
 
     fn label_for_symbol(
         &self,
-        symbol: &lsp::SymbolInformation,
+        name: &str,
+        kind: lsp::SymbolKind,
         language: &Language,
     ) -> Option<CodeLabel> {
-        let (text, filter_range, display_range) = match symbol.kind {
+        let (text, filter_range, display_range) = match kind {
             lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
-                let text = format!("fn {} () {{}}", symbol.name);
-                let filter_range = 3..3 + symbol.name.len();
+                let text = format!("fn {} () {{}}", name);
+                let filter_range = 3..3 + name.len();
                 let display_range = 0..filter_range.end;
                 (text, filter_range, display_range)
             }
             lsp::SymbolKind::STRUCT => {
-                let text = format!("struct {} {{}}", symbol.name);
-                let filter_range = 7..7 + symbol.name.len();
+                let text = format!("struct {} {{}}", name);
+                let filter_range = 7..7 + name.len();
                 let display_range = 0..filter_range.end;
                 (text, filter_range, display_range)
             }
             lsp::SymbolKind::ENUM => {
-                let text = format!("enum {} {{}}", symbol.name);
-                let filter_range = 5..5 + symbol.name.len();
+                let text = format!("enum {} {{}}", name);
+                let filter_range = 5..5 + name.len();
                 let display_range = 0..filter_range.end;
                 (text, filter_range, display_range)
             }
             lsp::SymbolKind::INTERFACE => {
-                let text = format!("trait {} {{}}", symbol.name);
-                let filter_range = 6..6 + symbol.name.len();
+                let text = format!("trait {} {{}}", name);
+                let filter_range = 6..6 + name.len();
                 let display_range = 0..filter_range.end;
                 (text, filter_range, display_range)
             }
             lsp::SymbolKind::CONSTANT => {
-                let text = format!("const {}: () = ();", symbol.name);
-                let filter_range = 6..6 + symbol.name.len();
+                let text = format!("const {}: () = ();", name);
+                let filter_range = 6..6 + name.len();
                 let display_range = 0..filter_range.end;
                 (text, filter_range, display_range)
             }
             lsp::SymbolKind::MODULE => {
-                let text = format!("mod {} {{}}", symbol.name);
-                let filter_range = 4..4 + symbol.name.len();
+                let text = format!("mod {} {{}}", name);
+                let filter_range = 4..4 + name.len();
                 let display_range = 0..filter_range.end;
                 (text, filter_range, display_range)
             }
             lsp::SymbolKind::TYPE_PARAMETER => {
-                let text = format!("type {} {{}}", symbol.name);
-                let filter_range = 5..5 + symbol.name.len();
+                let text = format!("type {} {{}}", name);
+                let filter_range = 5..5 + name.len();
                 let display_range = 0..filter_range.end;
                 (text, filter_range, display_range)
             }
@@ -456,7 +457,6 @@ mod tests {
     }
 
     #[test]
-    #[allow(deprecated)]
     fn test_rust_label_for_symbol() {
         let language = rust();
         let grammar = language.grammar().unwrap();
@@ -474,14 +474,7 @@ mod tests {
         let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
 
         assert_eq!(
-            language.label_for_symbol(&lsp::SymbolInformation {
-                kind: lsp::SymbolKind::FUNCTION,
-                name: "hello".to_string(),
-                location: lsp::Location::new("file://a".parse().unwrap(), Default::default()),
-                container_name: Default::default(),
-                deprecated: Default::default(),
-                tags: Default::default(),
-            }),
+            language.label_for_symbol("hello", lsp::SymbolKind::FUNCTION),
             Some(CodeLabel {
                 text: "fn hello".to_string(),
                 filter_range: 3..8,
@@ -490,14 +483,7 @@ mod tests {
         );
 
         assert_eq!(
-            language.label_for_symbol(&lsp::SymbolInformation {
-                kind: lsp::SymbolKind::TYPE_PARAMETER,
-                name: "World".to_string(),
-                location: lsp::Location::new("file://a".parse().unwrap(), Default::default()),
-                container_name: Default::default(),
-                deprecated: Default::default(),
-                tags: Default::default(),
-            }),
+            language.label_for_symbol("World", lsp::SymbolKind::TYPE_PARAMETER),
             Some(CodeLabel {
                 text: "type World".to_string(),
                 filter_range: 5..10,