Reduce amount of allocations in RustLsp label handling (#35786)

Lukas Wirth created

There can be a lot of completions after all


Release Notes:

- N/A

Change summary

crates/diagnostics/src/diagnostics_tests.rs |  2 
crates/editor/src/display_map/inlay_map.rs  | 16 ++--
crates/editor/src/inlay_hint_cache.rs       |  2 
crates/editor/src/signature_help.rs         |  2 
crates/languages/src/rust.rs                | 63 +++++-----------------
crates/project/src/project.rs               |  9 +-
crates/project/src/project_tests.rs         |  1 
crates/rope/src/rope.rs                     |  8 ++
crates/text/src/text.rs                     |  2 
9 files changed, 40 insertions(+), 65 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics_tests.rs 🔗

@@ -876,7 +876,7 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
                             vec![Inlay::edit_prediction(
                                 post_inc(&mut next_inlay_id),
                                 snapshot.buffer_snapshot.anchor_before(position),
-                                format!("Test inlay {next_inlay_id}"),
+                                Rope::from_iter(["Test inlay ", "next_inlay_id"]),
                             )],
                             cx,
                         );

crates/editor/src/display_map/inlay_map.rs 🔗

@@ -48,16 +48,16 @@ pub struct Inlay {
 impl Inlay {
     pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self {
         let mut text = hint.text();
-        if hint.padding_right && !text.ends_with(' ') {
-            text.push(' ');
+        if hint.padding_right && text.chars_at(text.len().saturating_sub(1)).next() != Some(' ') {
+            text.push(" ");
         }
-        if hint.padding_left && !text.starts_with(' ') {
-            text.insert(0, ' ');
+        if hint.padding_left && text.chars_at(0).next() != Some(' ') {
+            text.push_front(" ");
         }
         Self {
             id: InlayId::Hint(id),
             position,
-            text: text.into(),
+            text,
             color: None,
         }
     }
@@ -737,13 +737,13 @@ impl InlayMap {
                     Inlay::mock_hint(
                         post_inc(next_inlay_id),
                         snapshot.buffer.anchor_at(position, bias),
-                        text.clone(),
+                        &text,
                     )
                 } else {
                     Inlay::edit_prediction(
                         post_inc(next_inlay_id),
                         snapshot.buffer.anchor_at(position, bias),
-                        text.clone(),
+                        &text,
                     )
                 };
                 let inlay_id = next_inlay.id;
@@ -1694,7 +1694,7 @@ mod tests {
                     (offset, inlay.clone())
                 })
                 .collect::<Vec<_>>();
-            let mut expected_text = Rope::from(buffer_snapshot.text());
+            let mut expected_text = Rope::from(&buffer_snapshot.text());
             for (offset, inlay) in inlays.iter().rev() {
                 expected_text.replace(*offset..*offset, &inlay.text.to_string());
             }

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -3546,7 +3546,7 @@ pub mod tests {
             let excerpt_hints = excerpt_hints.read();
             for id in &excerpt_hints.ordered_hints {
                 let hint = &excerpt_hints.hints_by_id[id];
-                let mut label = hint.text();
+                let mut label = hint.text().to_string();
                 if hint.padding_left {
                     label.insert(0, ' ');
                 }

crates/editor/src/signature_help.rs 🔗

@@ -191,7 +191,7 @@ impl Editor {
 
                         if let Some(language) = language {
                             for signature in &mut signature_help.signatures {
-                                let text = Rope::from(signature.label.to_string());
+                                let text = Rope::from(signature.label.as_ref());
                                 let highlights = language
                                     .highlight_text(&text, 0..signature.label.len())
                                     .into_iter()

crates/languages/src/rust.rs 🔗

@@ -341,7 +341,7 @@ impl LspAdapter for RustLspAdapter {
                 let name = &completion.label;
                 let text = format!("{name}: {signature}");
                 let prefix = "struct S { ";
-                let source = Rope::from(format!("{prefix}{text} }}"));
+                let source = Rope::from_iter([prefix, &text, " }"]);
                 let runs =
                     language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
                 mk_label(text, runs)
@@ -353,7 +353,7 @@ impl LspAdapter for RustLspAdapter {
                 let name = &completion.label;
                 let text = format!("{name}: {signature}",);
                 let prefix = "let ";
-                let source = Rope::from(format!("{prefix}{text} = ();"));
+                let source = Rope::from_iter([prefix, &text, " = ();"]);
                 let runs =
                     language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
                 mk_label(text, runs)
@@ -387,7 +387,7 @@ impl LspAdapter for RustLspAdapter {
                         &completion.label
                     };
                     let text = format!("{label}{suffix}");
-                    let source = Rope::from(format!("{prefix} {text} {{}}"));
+                    let source = Rope::from_iter([prefix, " ", &text, " {}"]);
                     let run_start = prefix.len() + 1;
                     let runs = language.highlight_text(&source, run_start..run_start + text.len());
                     mk_label(text, runs)
@@ -450,55 +450,22 @@ impl LspAdapter for RustLspAdapter {
         kind: lsp::SymbolKind,
         language: &Arc<Language>,
     ) -> Option<CodeLabel> {
-        let (text, filter_range, display_range) = match kind {
-            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
-                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 {} {{}}", 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 {} {{}}", 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 {} {{}}", 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 {}: () = ();", 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 {} {{}}", 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 {} {{}}", name);
-                let filter_range = 5..5 + name.len();
-                let display_range = 0..filter_range.end;
-                (text, filter_range, display_range)
-            }
+        let (prefix, suffix) = match kind {
+            lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => ("fn ", " () {}"),
+            lsp::SymbolKind::STRUCT => ("struct ", " {}"),
+            lsp::SymbolKind::ENUM => ("enum ", " {}"),
+            lsp::SymbolKind::INTERFACE => ("trait ", " {}"),
+            lsp::SymbolKind::CONSTANT => ("const ", ": () = ();"),
+            lsp::SymbolKind::MODULE => ("mod ", " {}"),
+            lsp::SymbolKind::TYPE_PARAMETER => ("type ", " {}"),
             _ => return None,
         };
 
+        let filter_range = prefix.len()..prefix.len() + name.len();
+        let display_range = 0..filter_range.end;
         Some(CodeLabel {
-            runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
-            text: text[display_range].to_string(),
+            runs: language.highlight_text(&Rope::from_iter([prefix, name, suffix]), display_range),
+            text: format!("{prefix}{name}"),
             filter_range,
         })
     }

crates/project/src/project.rs 🔗

@@ -73,7 +73,6 @@ use gpui::{
     App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla, SharedString,
     Task, WeakEntity, Window,
 };
-use itertools::Itertools;
 use language::{
     Buffer, BufferEvent, Capability, CodeLabel, CursorShape, DiagnosticSourceKind, Language,
     LanguageName, LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainList,
@@ -113,7 +112,7 @@ use std::{
 
 use task_store::TaskStore;
 use terminals::Terminals;
-use text::{Anchor, BufferId, OffsetRangeExt, Point};
+use text::{Anchor, BufferId, OffsetRangeExt, Point, Rope};
 use toolchain_store::EmptyToolchainStore;
 use util::{
     ResultExt as _,
@@ -668,10 +667,10 @@ pub enum ResolveState {
 }
 
 impl InlayHint {
-    pub fn text(&self) -> String {
+    pub fn text(&self) -> Rope {
         match &self.label {
-            InlayHintLabel::String(s) => s.to_owned(),
-            InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""),
+            InlayHintLabel::String(s) => Rope::from(s),
+            InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &*part.value).collect(),
         }
     }
 }

crates/project/src/project_tests.rs 🔗

@@ -18,6 +18,7 @@ use git::{
 use git2::RepositoryInitOptions;
 use gpui::{App, BackgroundExecutor, SemanticVersion, UpdateGlobal};
 use http_client::Url;
+use itertools::Itertools;
 use language::{
     Diagnostic, DiagnosticEntry, DiagnosticSet, DiskState, FakeLspAdapter, LanguageConfig,
     LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,

crates/rope/src/rope.rs 🔗

@@ -471,11 +471,19 @@ impl<'a> FromIterator<&'a str> for Rope {
 }
 
 impl From<String> for Rope {
+    #[inline(always)]
     fn from(text: String) -> Self {
         Rope::from(text.as_str())
     }
 }
 
+impl From<&String> for Rope {
+    #[inline(always)]
+    fn from(text: &String) -> Self {
+        Rope::from(text.as_str())
+    }
+}
+
 impl fmt::Display for Rope {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         for chunk in self.chunks() {

crates/text/src/text.rs 🔗

@@ -713,7 +713,7 @@ impl Buffer {
         let mut base_text = base_text.into();
         let line_ending = LineEnding::detect(&base_text);
         LineEnding::normalize(&mut base_text);
-        Self::new_normalized(replica_id, remote_id, line_ending, Rope::from(base_text))
+        Self::new_normalized(replica_id, remote_id, line_ending, Rope::from(&*base_text))
     }
 
     pub fn new_normalized(