editor: Shrink size of `Inlay` slightly (#39089)

Lukas Wirth created

And some other smaller cleanup things I noticed while reading through
some stuff

Release Notes:

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

Change summary

crates/agent_ui/src/acp/message_editor.rs   |  2 
crates/editor/src/display_map/fold_map.rs   |  6 -
crates/editor/src/display_map/inlay_map.rs  | 91 ++++++++++++----------
crates/editor/src/display_map/invisibles.rs | 11 +-
crates/editor/src/display_map/tab_map.rs    | 82 +++++++++-----------
crates/editor/src/editor.rs                 | 16 ++--
crates/editor/src/hover_links.rs            |  2 
crates/editor/src/inlay_hint_cache.rs       |  2 
crates/multi_buffer/src/multi_buffer.rs     |  6 
crates/text/src/anchor.rs                   | 23 ++---
10 files changed, 119 insertions(+), 122 deletions(-)

Detailed changes

crates/agent_ui/src/acp/message_editor.rs 🔗

@@ -76,7 +76,7 @@ pub enum MessageEditorEvent {
 
 impl EventEmitter<MessageEditorEvent> for MessageEditor {}
 
-const COMMAND_HINT_INLAY_ID: usize = 0;
+const COMMAND_HINT_INLAY_ID: u32 = 0;
 
 impl MessageEditor {
     pub fn new(

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

@@ -1443,11 +1443,7 @@ impl<'a> Iterator for FoldChunks<'a> {
                 [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0];
 
             let bit_end = (chunk_end - buffer_chunk_start).0;
-            let mask = if bit_end >= 128 {
-                u128::MAX
-            } else {
-                (1u128 << bit_end) - 1
-            };
+            let mask = 1u128.unbounded_shl(bit_end as u32).wrapping_sub(1);
 
             chunk.tabs = (chunk.tabs >> (self.inlay_offset - buffer_chunk_start).0) & mask;
             chunk.chars = (chunk.chars >> (self.inlay_offset - buffer_chunk_start).0) & mask;

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

@@ -8,7 +8,7 @@ use multi_buffer::{
 use std::{
     cmp,
     ops::{Add, AddAssign, Range, Sub, SubAssign},
-    sync::Arc,
+    sync::{Arc, OnceLock},
 };
 use sum_tree::{Bias, Cursor, Dimensions, SumTree};
 use text::{ChunkBitmaps, Patch, Rope};
@@ -41,12 +41,17 @@ enum Transform {
 pub struct Inlay {
     pub id: InlayId,
     pub position: Anchor,
-    pub text: text::Rope,
-    color: Option<Hsla>,
+    pub content: InlayContent,
+}
+
+#[derive(Debug, Clone)]
+pub enum InlayContent {
+    Text(text::Rope),
+    Color(Hsla),
 }
 
 impl Inlay {
-    pub fn hint(id: usize, position: Anchor, hint: &project::InlayHint) -> Self {
+    pub fn hint(id: u32, position: Anchor, hint: &project::InlayHint) -> Self {
         let mut text = hint.text();
         if hint.padding_right && text.reversed_chars_at(text.len()).next() != Some(' ') {
             text.push(" ");
@@ -57,51 +62,57 @@ impl Inlay {
         Self {
             id: InlayId::Hint(id),
             position,
-            text,
-            color: None,
+            content: InlayContent::Text(text),
         }
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn mock_hint(id: usize, position: Anchor, text: impl Into<Rope>) -> Self {
+    pub fn mock_hint(id: u32, position: Anchor, text: impl Into<Rope>) -> Self {
         Self {
             id: InlayId::Hint(id),
             position,
-            text: text.into(),
-            color: None,
+            content: InlayContent::Text(text.into()),
         }
     }
 
-    pub fn color(id: usize, position: Anchor, color: Rgba) -> Self {
+    pub fn color(id: u32, position: Anchor, color: Rgba) -> Self {
         Self {
             id: InlayId::Color(id),
             position,
-            text: Rope::from("◼"),
-            color: Some(Hsla::from(color)),
+            content: InlayContent::Color(color.into()),
         }
     }
 
-    pub fn edit_prediction<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
+    pub fn edit_prediction<T: Into<Rope>>(id: u32, position: Anchor, text: T) -> Self {
         Self {
             id: InlayId::EditPrediction(id),
             position,
-            text: text.into(),
-            color: None,
+            content: InlayContent::Text(text.into()),
         }
     }
 
-    pub fn debugger<T: Into<Rope>>(id: usize, position: Anchor, text: T) -> Self {
+    pub fn debugger<T: Into<Rope>>(id: u32, position: Anchor, text: T) -> Self {
         Self {
             id: InlayId::DebuggerValue(id),
             position,
-            text: text.into(),
-            color: None,
+            content: InlayContent::Text(text.into()),
+        }
+    }
+
+    pub fn text(&self) -> &Rope {
+        static COLOR_TEXT: OnceLock<Rope> = OnceLock::new();
+        match &self.content {
+            InlayContent::Text(text) => text,
+            InlayContent::Color(_) => COLOR_TEXT.get_or_init(|| Rope::from("◼")),
         }
     }
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn get_color(&self) -> Option<Hsla> {
-        self.color
+        match self.content {
+            InlayContent::Color(color) => Some(color),
+            _ => None,
+        }
     }
 }
 
@@ -116,7 +127,7 @@ impl sum_tree::Item for Transform {
             },
             Transform::Inlay(inlay) => TransformSummary {
                 input: TextSummary::default(),
-                output: inlay.text.summary(),
+                output: inlay.text().summary(),
             },
         }
     }
@@ -354,7 +365,7 @@ impl<'a> Iterator for InlayChunks<'a> {
                 let mut renderer = None;
                 let mut highlight_style = match inlay.id {
                     InlayId::EditPrediction(_) => self.highlight_styles.edit_prediction.map(|s| {
-                        if inlay.text.chars().all(|c| c.is_whitespace()) {
+                        if inlay.text().chars().all(|c| c.is_whitespace()) {
                             s.whitespace
                         } else {
                             s.insertion
@@ -363,7 +374,7 @@ impl<'a> Iterator for InlayChunks<'a> {
                     InlayId::Hint(_) => self.highlight_styles.inlay_hint,
                     InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint,
                     InlayId::Color(_) => {
-                        if let Some(color) = inlay.color {
+                        if let InlayContent::Color(color) = inlay.content {
                             renderer = Some(ChunkRenderer {
                                 id: ChunkRendererId::Inlay(inlay.id),
                                 render: Arc::new(move |cx| {
@@ -410,7 +421,7 @@ impl<'a> Iterator for InlayChunks<'a> {
                     let start = offset_in_inlay;
                     let end = cmp::min(self.max_output_offset, self.transforms.end().0)
                         - self.transforms.start().0;
-                    let chunks = inlay.text.chunks_in_range(start.0..end.0);
+                    let chunks = inlay.text().chunks_in_range(start.0..end.0);
                     text::ChunkWithBitmaps(chunks)
                 });
                 let ChunkBitmaps {
@@ -706,7 +717,7 @@ impl InlayMap {
 
         for inlay_to_insert in to_insert {
             // Avoid inserting empty inlays.
-            if inlay_to_insert.text.is_empty() {
+            if inlay_to_insert.text().is_empty() {
                 continue;
             }
 
@@ -744,7 +755,7 @@ impl InlayMap {
     #[cfg(test)]
     pub(crate) fn randomly_mutate(
         &mut self,
-        next_inlay_id: &mut usize,
+        next_inlay_id: &mut u32,
         rng: &mut rand::rngs::StdRng,
     ) -> (InlaySnapshot, Vec<InlayEdit>) {
         use rand::prelude::*;
@@ -822,7 +833,7 @@ impl InlaySnapshot {
                 InlayPoint(cursor.start().1.0 + (buffer_end - buffer_start))
             }
             Some(Transform::Inlay(inlay)) => {
-                let overshoot = inlay.text.offset_to_point(overshoot);
+                let overshoot = inlay.text().offset_to_point(overshoot);
                 InlayPoint(cursor.start().1.0 + overshoot)
             }
             None => self.max_point(),
@@ -852,7 +863,7 @@ impl InlaySnapshot {
                 InlayOffset(cursor.start().1.0 + (buffer_offset_end - buffer_offset_start))
             }
             Some(Transform::Inlay(inlay)) => {
-                let overshoot = inlay.text.point_to_offset(overshoot);
+                let overshoot = inlay.text().point_to_offset(overshoot);
                 InlayOffset(cursor.start().1.0 + overshoot)
             }
             None => self.len(),
@@ -1064,7 +1075,7 @@ impl InlaySnapshot {
             Some(Transform::Inlay(inlay)) => {
                 let suffix_start = overshoot;
                 let suffix_end = cmp::min(cursor.end().0, range.end).0 - cursor.start().0.0;
-                summary = inlay.text.cursor(suffix_start).summary(suffix_end);
+                summary = inlay.text().cursor(suffix_start).summary(suffix_end);
                 cursor.next();
             }
             None => {}
@@ -1086,7 +1097,7 @@ impl InlaySnapshot {
                 }
                 Some(Transform::Inlay(inlay)) => {
                     let prefix_end = overshoot;
-                    summary += inlay.text.cursor(0).summary::<TextSummary>(prefix_end);
+                    summary += inlay.text().cursor(0).summary::<TextSummary>(prefix_end);
                 }
                 None => {}
             }
@@ -1269,7 +1280,7 @@ mod tests {
                     resolve_state: ResolveState::Resolved,
                 },
             )
-            .text
+            .text()
             .to_string(),
             "a",
             "Should not pad label if not requested"
@@ -1289,7 +1300,7 @@ mod tests {
                     resolve_state: ResolveState::Resolved,
                 },
             )
-            .text
+            .text()
             .to_string(),
             " a ",
             "Should pad label for every side requested"
@@ -1309,7 +1320,7 @@ mod tests {
                     resolve_state: ResolveState::Resolved,
                 },
             )
-            .text
+            .text()
             .to_string(),
             " a ",
             "Should not change already padded label"
@@ -1329,7 +1340,7 @@ mod tests {
                     resolve_state: ResolveState::Resolved,
                 },
             )
-            .text
+            .text()
             .to_string(),
             " a ",
             "Should not change already padded label"
@@ -1352,7 +1363,7 @@ mod tests {
                     resolve_state: ResolveState::Resolved,
                 },
             )
-            .text
+            .text()
             .to_string(),
             " 🎨 ",
             "Should pad single emoji correctly"
@@ -1750,7 +1761,7 @@ mod tests {
                 .collect::<Vec<_>>();
             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());
+                expected_text.replace(*offset..*offset, &inlay.text().to_string());
             }
             assert_eq!(inlay_snapshot.text(), expected_text.to_string());
 
@@ -1803,7 +1814,7 @@ mod tests {
                         .into_iter()
                         .filter_map(|i| {
                             let (_, inlay) = &inlays[i];
-                            let inlay_text_len = inlay.text.len();
+                            let inlay_text_len = inlay.text().len();
                             match inlay_text_len {
                                 0 => None,
                                 1 => Some(InlayHighlight {
@@ -1812,7 +1823,7 @@ mod tests {
                                     range: 0..1,
                                 }),
                                 n => {
-                                    let inlay_text = inlay.text.to_string();
+                                    let inlay_text = inlay.text().to_string();
                                     let mut highlight_end = rng.random_range(1..n);
                                     let mut highlight_start = rng.random_range(0..highlight_end);
                                     while !inlay_text.is_char_boundary(highlight_end) {
@@ -2138,8 +2149,7 @@ mod tests {
         let inlay = Inlay {
             id: InlayId::Hint(0),
             position,
-            text: text::Rope::from(inlay_text),
-            color: None,
+            content: InlayContent::Text(text::Rope::from(inlay_text)),
         };
 
         let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);
@@ -2253,8 +2263,7 @@ mod tests {
             let inlay = Inlay {
                 id: InlayId::Hint(0),
                 position,
-                text: text::Rope::from(test_case.inlay_text),
-                color: None,
+                content: InlayContent::Text(text::Rope::from(test_case.inlay_text)),
             };
 
             let (inlay_snapshot, _) = inlay_map.splice(&[], vec![inlay]);

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

@@ -53,9 +53,12 @@ pub fn replacement(c: char) -> Option<&'static str> {
     } else if contains(c, PRESERVE) {
         None
     } else {
-        Some("\u{2007}") // fixed width space
+        Some(FIXED_WIDTH_SPACE)
     }
 }
+
+const FIXED_WIDTH_SPACE: &str = "\u{2007}";
+
 // IDEOGRAPHIC SPACE is common alongside Chinese and other wide character sets.
 // We don't highlight this for now (as it already shows up wide in the editor),
 // but could if we tracked state in the classifier.
@@ -117,11 +120,11 @@ const PRESERVE: &[(char, char)] = &[
 ];
 
 fn contains(c: char, list: &[(char, char)]) -> bool {
-    for (start, end) in list {
-        if c < *start {
+    for &(start, end) in list {
+        if c < start {
             return false;
         }
-        if c <= *end {
+        if c <= end {
             return true;
         }
     }

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

@@ -54,9 +54,7 @@ impl TabMap {
             new_snapshot.version += 1;
         }
 
-        let mut tab_edits = Vec::with_capacity(fold_edits.len());
-
-        if old_snapshot.tab_size == new_snapshot.tab_size {
+        let tab_edits = if old_snapshot.tab_size == new_snapshot.tab_size {
             // Expand each edit to include the next tab on the same line as the edit,
             // and any subsequent tabs on that line that moved across the tab expansion
             // boundary.
@@ -112,7 +110,7 @@ impl TabMap {
             let _old_alloc_ptr = fold_edits.as_ptr();
             // Combine any edits that overlap due to the expansion.
             let mut fold_edits = fold_edits.into_iter();
-            let fold_edits = if let Some(mut first_edit) = fold_edits.next() {
+            if let Some(mut first_edit) = fold_edits.next() {
                 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
                 #[allow(clippy::filter_map_identity)]
                 let mut v: Vec<_> = fold_edits
@@ -132,29 +130,30 @@ impl TabMap {
                     .collect();
                 v.push(first_edit);
                 debug_assert_eq!(v.as_ptr(), _old_alloc_ptr, "Fold edits were reallocated");
-                v
+                v.into_iter()
+                    .map(|fold_edit| {
+                        let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot);
+                        let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot);
+                        let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
+                        let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
+                        TabEdit {
+                            old: old_snapshot.to_tab_point(old_start)
+                                ..old_snapshot.to_tab_point(old_end),
+                            new: new_snapshot.to_tab_point(new_start)
+                                ..new_snapshot.to_tab_point(new_end),
+                        }
+                    })
+                    .collect()
             } else {
                 vec![]
-            };
-
-            for fold_edit in fold_edits {
-                let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot);
-                let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot);
-                let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
-                let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
-                tab_edits.push(TabEdit {
-                    old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end),
-                    new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end),
-                });
             }
         } else {
             new_snapshot.version += 1;
-            tab_edits.push(TabEdit {
+            vec![TabEdit {
                 old: TabPoint::zero()..old_snapshot.max_point(),
                 new: TabPoint::zero()..new_snapshot.max_point(),
-            });
-        }
-
+            }]
+        };
         *old_snapshot = new_snapshot;
         (old_snapshot.clone(), tab_edits)
     }
@@ -195,37 +194,28 @@ impl TabSnapshot {
             .fold_snapshot
             .text_summary_for_range(input_start..input_end);
 
-        let mut first_line_chars = 0;
         let line_end = if range.start.row() == range.end.row() {
             range.end
         } else {
             self.max_point()
         };
-        for c in self
+        let first_line_chars = self
             .chunks(range.start..line_end, false, Highlights::default())
             .flat_map(|chunk| chunk.text.chars())
-        {
-            if c == '\n' {
-                break;
-            }
-            first_line_chars += 1;
-        }
+            .take_while(|&c| c != '\n')
+            .count() as u32;
 
-        let mut last_line_chars = 0;
-        if range.start.row() == range.end.row() {
-            last_line_chars = first_line_chars;
+        let last_line_chars = if range.start.row() == range.end.row() {
+            first_line_chars
         } else {
-            for _ in self
-                .chunks(
-                    TabPoint::new(range.end.row(), 0)..range.end,
-                    false,
-                    Highlights::default(),
-                )
-                .flat_map(|chunk| chunk.text.chars())
-            {
-                last_line_chars += 1;
-            }
-        }
+            self.chunks(
+                TabPoint::new(range.end.row(), 0)..range.end,
+                false,
+                Highlights::default(),
+            )
+            .flat_map(|chunk| chunk.text.chars())
+            .count() as u32
+        };
 
         TextSummary {
             lines: range.end.0 - range.start.0,
@@ -514,15 +504,17 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
 
 pub struct TabChunks<'a> {
     snapshot: &'a TabSnapshot,
+    max_expansion_column: u32,
+    max_output_position: Point,
+    tab_size: NonZeroU32,
+    // region: iteration state
     fold_chunks: FoldChunks<'a>,
     chunk: Chunk<'a>,
     column: u32,
-    max_expansion_column: u32,
     output_position: Point,
     input_column: u32,
-    max_output_position: Point,
-    tab_size: NonZeroU32,
     inside_leading_tab: bool,
+    // endregion: iteration state
 }
 
 impl TabChunks<'_> {

crates/editor/src/editor.rs 🔗

@@ -279,15 +279,15 @@ impl InlineValueCache {
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum InlayId {
-    EditPrediction(usize),
-    DebuggerValue(usize),
+    EditPrediction(u32),
+    DebuggerValue(u32),
     // LSP
-    Hint(usize),
-    Color(usize),
+    Hint(u32),
+    Color(u32),
 }
 
 impl InlayId {
-    fn id(&self) -> usize {
+    fn id(&self) -> u32 {
         match self {
             Self::EditPrediction(id) => *id,
             Self::DebuggerValue(id) => *id,
@@ -1118,7 +1118,8 @@ pub struct Editor {
     edit_prediction_indent_conflict: bool,
     edit_prediction_requires_modifier_in_indent_conflict: bool,
     inlay_hint_cache: InlayHintCache,
-    next_inlay_id: usize,
+    next_inlay_id: u32,
+    next_color_inlay_id: u32,
     _subscriptions: Vec<Subscription>,
     pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
     gutter_dimensions: GutterDimensions,
@@ -1182,7 +1183,6 @@ pub struct Editor {
     pub change_list: ChangeList,
     inline_value_cache: InlineValueCache,
     selection_drag_state: SelectionDragState,
-    next_color_inlay_id: usize,
     colors: Option<LspColorData>,
     folding_newlines: Task<()>,
     pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
@@ -20531,7 +20531,7 @@ impl Editor {
                                     Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
                                     hint.text(),
                                 );
-                                if !inlay.text.chars().contains(&'\n') {
+                                if !inlay.text().chars().contains(&'\n') {
                                     new_inlays.push(inlay);
                                 }
                             });

crates/editor/src/hover_links.rs 🔗

@@ -370,7 +370,7 @@ pub fn update_inlay_link_and_hover_points(
                                                 inlay: hovered_hint.id,
                                                 inlay_position: hovered_hint.position,
                                                 range: extra_shift_left
-                                                    ..hovered_hint.text.len() + extra_shift_right,
+                                                    ..hovered_hint.text().len() + extra_shift_right,
                                             },
                                         },
                                         window,

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -3571,7 +3571,7 @@ pub mod tests {
         editor
             .visible_inlay_hints(cx)
             .into_iter()
-            .map(|hint| hint.text.to_string())
+            .map(|hint| hint.text().to_string())
             .collect()
     }
 }

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -54,7 +54,7 @@ use util::post_inc;
 const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize];
 
 #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ExcerptId(usize);
+pub struct ExcerptId(u32);
 
 /// One or more [`Buffers`](Buffer) being edited in a single view.
 ///
@@ -7202,7 +7202,7 @@ impl ExcerptId {
     }
 
     pub fn max() -> Self {
-        Self(usize::MAX)
+        Self(u32::MAX)
     }
 
     pub fn to_proto(self) -> u64 {
@@ -7222,7 +7222,7 @@ impl ExcerptId {
 
 impl From<ExcerptId> for usize {
     fn from(val: ExcerptId) -> Self {
-        val.0
+        val.0 as usize
     }
 }
 

crates/text/src/anchor.rs 🔗

@@ -62,26 +62,23 @@ impl Anchor {
     }
 
     pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor {
-        if bias == Bias::Left {
-            self.bias_left(buffer)
-        } else {
-            self.bias_right(buffer)
+        match bias {
+            Bias::Left => self.bias_left(buffer),
+            Bias::Right => self.bias_right(buffer),
         }
     }
 
     pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor {
-        if self.bias == Bias::Left {
-            *self
-        } else {
-            buffer.anchor_before(self)
+        match self.bias {
+            Bias::Left => *self,
+            Bias::Right => buffer.anchor_before(self),
         }
     }
 
     pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor {
-        if self.bias == Bias::Right {
-            *self
-        } else {
-            buffer.anchor_after(self)
+        match self.bias {
+            Bias::Left => buffer.anchor_after(self),
+            Bias::Right => *self,
         }
     }
 
@@ -96,7 +93,7 @@ impl Anchor {
     pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
         if *self == Anchor::MIN || *self == Anchor::MAX {
             true
-        } else if self.buffer_id != Some(buffer.remote_id) {
+        } else if self.buffer_id.is_none_or(|id| id != buffer.remote_id) {
             false
         } else {
             let Some(fragment_id) = buffer.try_fragment_id_for_anchor(self) else {