diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index dec9b0beb23d9d7d49116a30404f244b5bb1f8e8..0d7e6d02b1a2541cecc9013aa5cb9ce4dfbbe3ba 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -76,7 +76,7 @@ pub enum MessageEditorEvent { impl EventEmitter for MessageEditor {} -const COMMAND_HINT_INLAY_ID: usize = 0; +const COMMAND_HINT_INLAY_ID: u32 = 0; impl MessageEditor { pub fn new( diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 56484a48f4a5521c1f962295daea4a9f70e8cfae..f4c460c5d88610acb5ebf7f68420b9a046fed1b2 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/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; diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 2b2734009dc4e3c70e1a9b568357438f2eb9ae88..3fcbb530be3af64c842c17ef49151995ad14c218 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/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, + 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) -> Self { + pub fn mock_hint(id: u32, position: Anchor, text: impl Into) -> 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>(id: usize, position: Anchor, text: T) -> Self { + pub fn edit_prediction>(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>(id: usize, position: Anchor, text: T) -> Self { + pub fn debugger>(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 = 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 { - 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) { 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::(prefix_end); + summary += inlay.text().cursor(0).summary::(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::>(); 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]); diff --git a/crates/editor/src/display_map/invisibles.rs b/crates/editor/src/display_map/invisibles.rs index 0712ddf9e2e53c22081c6fa63ebb4baeced37f78..5622a659b7acf850d24f6a476b23b53d214d855d 100644 --- a/crates/editor/src/display_map/invisibles.rs +++ b/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; } } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 50e7363de7a09bfef6d380040f746891c7c9cbfd..b37d81c66614030ab574244f3de0277d3fd8bee9 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/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<'_> { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0deb15e6b700abd44a065334c361ebbe3d05a4c9..037844239c39fb6aa6754e897b1c859ec965e102 100644 --- a/crates/editor/src/editor.rs +++ b/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, pixel_position_of_newest_cursor: Option>, 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, folding_newlines: Task<()>, pub lookup_key: Option>, @@ -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); } }); diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index c2a0108915ac5c4f4baef814b2fe7753ce1e2e62..057e8e3bdfbc25d8c33705974f965fc8c1477f15 100644 --- a/crates/editor/src/hover_links.rs +++ b/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, diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 59c52e4341a74e85236d99da4bf6ff1195266f68..f2a715420e34050bf61ae862a8dc15b88848fa19 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/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() } } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 344957cd97744e221adf4358bb9680b76fc083da..4132666c628925aceb57ee5033600f64ffa5f6a8 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/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 for usize { fn from(val: ExcerptId) -> Self { - val.0 + val.0 as usize } } diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index becc5d9c0a113acf64c4b2d331432bea6d00a5c9..a05da1243faa05f33708fe6858fc9dada3c0a1e0 100644 --- a/crates/text/src/anchor.rs +++ b/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 {