gpui: Remove intermediate allocations when reconstructing text from a `TextLayout` (#45037)

tidely created

Closes #ISSUE

Remove some intermediate allocations when reconstructing text or wrapped
text from a `TextLayout`. Currently creates a intermediate `Vec<String>`
which gets joined, when you could join an `impl Iterator<Item = &str>`

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/gpui/src/elements/text.rs | 18 +++++++++++-------
crates/markdown/src/markdown.rs  |  9 ++++++---
2 files changed, 17 insertions(+), 10 deletions(-)

Detailed changes

crates/gpui/src/elements/text.rs 🔗

@@ -6,6 +6,7 @@ use crate::{
     register_tooltip_mouse_handlers, set_tooltip_on_window,
 };
 use anyhow::Context as _;
+use itertools::Itertools;
 use smallvec::SmallVec;
 use std::{
     borrow::Cow,
@@ -597,14 +598,14 @@ impl TextLayout {
             .unwrap()
             .lines
             .iter()
-            .map(|s| s.text.to_string())
-            .collect::<Vec<_>>()
+            .map(|s| &s.text)
             .join("\n")
     }
 
     /// The text for this layout (with soft-wraps as newlines)
     pub fn wrapped_text(&self) -> String {
-        let mut lines = Vec::new();
+        let mut accumulator = String::new();
+
         for wrapped in self.0.borrow().as_ref().unwrap().lines.iter() {
             let mut seen = 0;
             for boundary in wrapped.layout.wrap_boundaries.iter() {
@@ -612,13 +613,16 @@ impl TextLayout {
                     [boundary.glyph_ix]
                     .index;
 
-                lines.push(wrapped.text[seen..index].to_string());
+                accumulator.push_str(&wrapped.text[seen..index]);
+                accumulator.push('\n');
                 seen = index;
             }
-            lines.push(wrapped.text[seen..].to_string());
+            accumulator.push_str(&wrapped.text[seen..]);
+            accumulator.push('\n');
         }
-
-        lines.join("\n")
+        // Remove trailing newline
+        accumulator.pop();
+        accumulator
     }
 }
 

crates/markdown/src/markdown.rs 🔗

@@ -1919,7 +1919,7 @@ impl RenderedText {
     }
 
     fn text_for_range(&self, range: Range<usize>) -> String {
-        let mut ret = vec![];
+        let mut accumulator = String::new();
 
         for line in self.lines.iter() {
             if range.start > line.source_end {
@@ -1944,9 +1944,12 @@ impl RenderedText {
             }
             .min(text.len());
 
-            ret.push(text[start..end].to_string());
+            accumulator.push_str(&text[start..end]);
+            accumulator.push('\n');
         }
-        ret.join("\n")
+        // Remove trailing newline
+        accumulator.pop();
+        accumulator
     }
 
     fn link_for_position(&self, position: Point<Pixels>) -> Option<&RenderedLink> {