From 3e7b8efb98d248efcfd2dddf115e7f9820f7c6af Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Tue, 4 Nov 2025 09:48:38 +0100 Subject: [PATCH] editor: Newtype `WrapRow` (#41843) Makes a better distinction between `WrapRow` and `BlockRow` Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/editor/src/display_map.rs | 25 +- crates/editor/src/display_map/block_map.rs | 465 +++++++++++--------- crates/editor/src/display_map/dimensions.rs | 96 ++++ crates/editor/src/display_map/wrap_map.rs | 125 +++--- crates/text/src/patch.rs | 85 ++-- crates/text/src/text.rs | 20 +- 6 files changed, 484 insertions(+), 332 deletions(-) create mode 100644 crates/editor/src/display_map/dimensions.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 7a225d6019..17886d09bd 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -17,6 +17,9 @@ //! [Editor]: crate::Editor //! [EditorElement]: crate::element::EditorElement +#[macro_use] +mod dimensions; + mod block_map; mod crease_map; mod custom_highlights; @@ -167,11 +170,12 @@ impl DisplayMap { } pub fn snapshot(&mut self, cx: &mut Context) -> DisplaySnapshot { + let tab_size = Self::tab_size(&self.buffer, cx); + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot, edits); - let tab_size = Self::tab_size(&self.buffer, cx); let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot, edits, tab_size); let (wrap_snapshot, edits) = self .wrap_map @@ -915,7 +919,7 @@ impl DisplaySnapshot { pub fn text_chunks(&self, display_row: DisplayRow) -> impl Iterator { self.block_snapshot .chunks( - display_row.0..self.max_point().row().next_row().0, + BlockRow(display_row.0)..BlockRow(self.max_point().row().next_row().0), false, self.masked, Highlights::default(), @@ -927,7 +931,12 @@ impl DisplaySnapshot { pub fn reverse_text_chunks(&self, display_row: DisplayRow) -> impl Iterator { (0..=display_row.0).rev().flat_map(move |row| { self.block_snapshot - .chunks(row..row + 1, false, self.masked, Highlights::default()) + .chunks( + BlockRow(row)..BlockRow(row + 1), + false, + self.masked, + Highlights::default(), + ) .map(|h| h.text) .collect::>() .into_iter() @@ -942,7 +951,7 @@ impl DisplaySnapshot { highlight_styles: HighlightStyles, ) -> DisplayChunks<'_> { self.block_snapshot.chunks( - display_rows.start.0..display_rows.end.0, + BlockRow(display_rows.start.0)..BlockRow(display_rows.end.0), language_aware, self.masked, Highlights { @@ -1178,8 +1187,8 @@ impl DisplaySnapshot { rows: Range, ) -> impl Iterator { self.block_snapshot - .blocks_in_range(rows.start.0..rows.end.0) - .map(|(row, block)| (DisplayRow(row), block)) + .blocks_in_range(BlockRow(rows.start.0)..BlockRow(rows.end.0)) + .map(|(row, block)| (DisplayRow(row.0), block)) } pub fn sticky_header_excerpt(&self, row: f64) -> Option> { @@ -1211,7 +1220,7 @@ impl DisplaySnapshot { pub fn soft_wrap_indent(&self, display_row: DisplayRow) -> Option { let wrap_row = self .block_snapshot - .to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left) + .to_wrap_point(BlockPoint::new(BlockRow(display_row.0), 0), Bias::Left) .row(); self.wrap_snapshot().soft_wrap_indent(wrap_row) } @@ -1242,7 +1251,7 @@ impl DisplaySnapshot { } pub fn longest_row(&self) -> DisplayRow { - DisplayRow(self.block_snapshot.longest_row()) + DisplayRow(self.block_snapshot.longest_row().0) } pub fn longest_row_in_range(&self, range: Range) -> DisplayRow { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 99234899d3..7f2ab12308 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,9 +1,12 @@ use super::{ Highlights, fold_map::Chunk, - wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, + wrap_map::{self, WrapEdit, WrapPatch, WrapPoint, WrapSnapshot}, +}; +use crate::{ + EditorStyle, GutterDimensions, + display_map::{dimensions::RowDelta, wrap_map::WrapRow}, }; -use crate::{EditorStyle, GutterDimensions}; use collections::{Bound, HashMap, HashSet}; use gpui::{AnyElement, App, EntityId, Pixels, Window}; use language::{Patch, Point}; @@ -75,10 +78,17 @@ impl From for ElementId { pub struct BlockPoint(pub Point); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct BlockRow(pub(super) u32); +pub struct BlockRow(pub u32); -#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -struct WrapRow(u32); +impl_for_row_types! { + BlockRow => RowDelta +} + +impl BlockPoint { + pub fn row(&self) -> BlockRow { + BlockRow(self.0.row) + } +} pub type RenderBlock = Arc AnyElement>; @@ -159,19 +169,19 @@ impl BlockPlacement { BlockPlacement::Above(position) => { let mut position = position.to_point(buffer_snapshot); position.column = 0; - let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row()); + let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row(); Some(BlockPlacement::Above(wrap_row)) } BlockPlacement::Near(position) => { let mut position = position.to_point(buffer_snapshot); position.column = buffer_snapshot.line_len(MultiBufferRow(position.row)); - let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row()); + let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row(); Some(BlockPlacement::Near(wrap_row)) } BlockPlacement::Below(position) => { let mut position = position.to_point(buffer_snapshot); position.column = buffer_snapshot.line_len(MultiBufferRow(position.row)); - let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row()); + let wrap_row = wrap_snapshot.make_wrap_point(position, Bias::Left).row(); Some(BlockPlacement::Below(wrap_row)) } BlockPlacement::Replace(range) => { @@ -181,11 +191,9 @@ impl BlockPlacement { None } else { start.column = 0; - let start_wrap_row = - WrapRow(wrap_snapshot.make_wrap_point(start, Bias::Left).row()); + let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row(); end.column = buffer_snapshot.line_len(MultiBufferRow(end.row)); - let end_wrap_row = - WrapRow(wrap_snapshot.make_wrap_point(end, Bias::Left).row()); + let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row(); Some(BlockPlacement::Replace(start_wrap_row..=end_wrap_row)) } } @@ -433,9 +441,9 @@ impl Debug for Block { #[derive(Clone, Debug, Default)] struct TransformSummary { - input_rows: u32, - output_rows: u32, - longest_row: u32, + input_rows: WrapRow, + output_rows: BlockRow, + longest_row: BlockRow, longest_row_chars: u32, } @@ -443,8 +451,8 @@ pub struct BlockChunks<'a> { transforms: sum_tree::Cursor<'a, 'static, Transform, Dimensions>, input_chunks: wrap_map::WrapChunks<'a>, input_chunk: Chunk<'a>, - output_row: u32, - max_output_row: u32, + output_row: BlockRow, + max_output_row: BlockRow, masked: bool, } @@ -462,9 +470,9 @@ impl BlockMap { buffer_header_height: u32, excerpt_header_height: u32, ) -> Self { - let row_count = wrap_snapshot.max_point().row() + 1; + let row_count = wrap_snapshot.max_point().row() + WrapRow(1); let mut transforms = SumTree::default(); - push_isomorphic(&mut transforms, row_count, &wrap_snapshot); + push_isomorphic(&mut transforms, row_count - WrapRow(0), &wrap_snapshot); let map = Self { next_block_id: AtomicUsize::new(0), custom_blocks: Vec::new(), @@ -479,14 +487,14 @@ impl BlockMap { map.sync( &wrap_snapshot, Patch::new(vec![Edit { - old: 0..row_count, - new: 0..row_count, + old: WrapRow(0)..row_count, + new: WrapRow(0)..row_count, }]), ); map } - pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: Patch) -> BlockMapReader<'_> { + pub fn read(&self, wrap_snapshot: WrapSnapshot, edits: WrapPatch) -> BlockMapReader<'_> { self.sync(&wrap_snapshot, edits); *self.wrap_snapshot.borrow_mut() = wrap_snapshot.clone(); BlockMapReader { @@ -501,13 +509,13 @@ impl BlockMap { } } - pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Patch) -> BlockMapWriter<'_> { + pub fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: WrapPatch) -> BlockMapWriter<'_> { self.sync(&wrap_snapshot, edits); *self.wrap_snapshot.borrow_mut() = wrap_snapshot; BlockMapWriter(self) } - fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch) { + fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: WrapPatch) { let buffer = wrap_snapshot.buffer_snapshot(); // Handle changing the last excerpt if it is empty. @@ -520,7 +528,7 @@ impl BlockMap { { let max_point = wrap_snapshot.max_point(); let edit_start = wrap_snapshot.prev_row_boundary(max_point); - let edit_end = max_point.row() + 1; + let edit_end = max_point.row() + WrapRow(1); edits = edits.compose([WrapEdit { old: edit_start..edit_end, new: edit_start..edit_end, @@ -540,8 +548,8 @@ impl BlockMap { let mut edits = edits.into_iter().peekable(); while let Some(edit) = edits.next() { - let mut old_start = WrapRow(edit.old.start); - let mut new_start = WrapRow(edit.new.start); + let mut old_start = edit.old.start; + let mut new_start = edit.new.start; // Only preserve transforms that: // * Strictly precedes this edit @@ -550,7 +558,7 @@ impl BlockMap { // However, if we hit a replace block that ends at the start of the edit we want to reconstruct it. new_transforms.append(cursor.slice(&old_start, Bias::Left), ()); if let Some(transform) = cursor.item() - && transform.summary.input_rows > 0 + && transform.summary.input_rows > WrapRow(0) && cursor.end() == old_start && transform.block.as_ref().is_none_or(|b| !b.is_replacement()) { @@ -573,8 +581,8 @@ impl BlockMap { // If the edit starts within an isomorphic transform, preserve its prefix // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range let transform = cursor.item().unwrap(); - let transform_rows_before_edit = old_start.0 - cursor.start().0; - if transform_rows_before_edit > 0 { + let transform_rows_before_edit = old_start - *cursor.start(); + if transform_rows_before_edit > RowDelta(0) { if transform.block.is_none() { // Preserve any portion of the old isomorphic transform that precedes this edit. push_isomorphic( @@ -586,32 +594,32 @@ impl BlockMap { // We landed within a block that replaces some lines, so we // extend the edit to start at the beginning of the // replacement. - debug_assert!(transform.summary.input_rows > 0); - old_start.0 -= transform_rows_before_edit; - new_start.0 -= transform_rows_before_edit; + debug_assert!(transform.summary.input_rows > WrapRow(0)); + old_start -= transform_rows_before_edit; + new_start -= transform_rows_before_edit; } } // Decide where the edit ends // * It should end at a transform boundary // * Coalesce edits that intersect the same transform - let mut old_end = WrapRow(edit.old.end); - let mut new_end = WrapRow(edit.new.end); + let mut old_end = edit.old.end; + let mut new_end = edit.new.end; loop { // Seek to the transform starting at or after the end of the edit cursor.seek(&old_end, Bias::Left); cursor.next(); // Extend edit to the end of the discarded transform so it is reconstructed in full - let transform_rows_after_edit = cursor.start().0 - old_end.0; - old_end.0 += transform_rows_after_edit; - new_end.0 += transform_rows_after_edit; + let transform_rows_after_edit = *cursor.start() - old_end; + old_end += transform_rows_after_edit; + new_end += transform_rows_after_edit; // Combine this edit with any subsequent edits that intersect the same transform. while let Some(next_edit) = edits.peek() { - if next_edit.old.start <= cursor.start().0 { - old_end = WrapRow(next_edit.old.end); - new_end = WrapRow(next_edit.new.end); + if next_edit.old.start <= *cursor.start() { + old_end = next_edit.old.end; + new_end = next_edit.new.end; cursor.seek(&old_end, Bias::Left); cursor.next(); edits.next(); @@ -635,8 +643,7 @@ impl BlockMap { } // Find the blocks within this edited region. - let new_buffer_start = - wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left); + let new_buffer_start = wrap_snapshot.to_point(WrapPoint::new(new_start, 0), Bias::Left); let start_bound = Bound::Included(new_buffer_start); let start_block_ix = match self.custom_blocks[last_block_ix..].binary_search_by(|probe| { @@ -651,12 +658,11 @@ impl BlockMap { }; let end_bound; - let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() { + let end_block_ix = if new_end > wrap_snapshot.max_point().row() { end_bound = Bound::Unbounded; self.custom_blocks.len() } else { - let new_buffer_end = - wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left); + let new_buffer_end = wrap_snapshot.to_point(WrapPoint::new(new_end, 0), Bias::Left); end_bound = Bound::Excluded(new_buffer_end); match self.custom_blocks[start_block_ix..].binary_search_by(|probe| { probe @@ -699,30 +705,31 @@ impl BlockMap { let mut just_processed_folded_buffer = false; for (block_placement, block) in blocks_in_edit.drain(..) { let mut summary = TransformSummary { - input_rows: 0, - output_rows: block.height(), - longest_row: 0, + input_rows: WrapRow(0), + output_rows: BlockRow(block.height()), + longest_row: BlockRow(0), longest_row_chars: 0, }; let rows_before_block; match block_placement { BlockPlacement::Above(position) => { - rows_before_block = position.0 - new_transforms.summary().input_rows; + rows_before_block = position - new_transforms.summary().input_rows; just_processed_folded_buffer = false; } BlockPlacement::Near(position) | BlockPlacement::Below(position) => { if just_processed_folded_buffer { continue; } - if position.0 + 1 < new_transforms.summary().input_rows { + if position + RowDelta(1) < new_transforms.summary().input_rows { continue; } - rows_before_block = (position.0 + 1) - new_transforms.summary().input_rows; + rows_before_block = + (position + RowDelta(1)) - new_transforms.summary().input_rows; } BlockPlacement::Replace(range) => { - rows_before_block = range.start().0 - new_transforms.summary().input_rows; - summary.input_rows = range.end().0 - range.start().0 + 1; + rows_before_block = *range.start() - new_transforms.summary().input_rows; + summary.input_rows = WrapRow(1) + (*range.end() - *range.start()); just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. }); } } @@ -738,16 +745,15 @@ impl BlockMap { } // Insert an isomorphic transform after the final block. - let rows_after_last_block = new_end - .0 - .saturating_sub(new_transforms.summary().input_rows); + let rows_after_last_block = + RowDelta(new_end.0).saturating_sub(RowDelta(new_transforms.summary().input_rows.0)); push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot); } new_transforms.append(cursor.suffix(), ()); debug_assert_eq!( new_transforms.summary().input_rows, - wrap_snapshot.max_point().row() + 1 + wrap_snapshot.max_point().row() + WrapRow(1) ); drop(cursor); @@ -823,7 +829,7 @@ impl BlockMap { .row(); return Some(( - BlockPlacement::Replace(WrapRow(wrap_row)..=WrapRow(wrap_end_row)), + BlockPlacement::Replace(wrap_row..=wrap_end_row), Block::FoldedBuffer { height: height + self.buffer_header_height, first_excerpt, @@ -849,7 +855,7 @@ impl BlockMap { continue; }; - return Some((BlockPlacement::Above(WrapRow(wrap_row)), block)); + return Some((BlockPlacement::Above(wrap_row), block)); } }) } @@ -920,8 +926,8 @@ impl BlockMap { } } -fn push_isomorphic(tree: &mut SumTree, rows: u32, wrap_snapshot: &WrapSnapshot) { - if rows == 0 { +fn push_isomorphic(tree: &mut SumTree, rows: RowDelta, wrap_snapshot: &WrapSnapshot) { + if rows == RowDelta(0) { return; } @@ -929,9 +935,9 @@ fn push_isomorphic(tree: &mut SumTree, rows: u32, wrap_snapshot: &Wra let wrap_row_end = wrap_row_start + rows; let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end); let summary = TransformSummary { - input_rows: rows, - output_rows: rows, - longest_row: wrap_summary.longest_row, + input_rows: WrapRow(rows.0), + output_rows: BlockRow(rows.0), + longest_row: BlockRow(wrap_summary.longest_row), longest_row_chars: wrap_summary.longest_row_chars, }; let mut merged = false; @@ -956,8 +962,8 @@ fn push_isomorphic(tree: &mut SumTree, rows: u32, wrap_snapshot: &Wra } impl BlockPoint { - pub fn new(row: u32, column: u32) -> Self { - Self(Point::new(row, column)) + pub fn new(row: BlockRow, column: u32) -> Self { + Self(Point::new(row.0, column)) } } @@ -1000,15 +1006,13 @@ impl BlockMapReader<'_> { .wrap_snapshot .make_wrap_point(Point::new(buffer_row, 0), Bias::Left) .row(); - let start_wrap_row = WrapRow( - self.wrap_snapshot - .prev_row_boundary(WrapPoint::new(wrap_row, 0)), - ); - let end_wrap_row = WrapRow( - self.wrap_snapshot - .next_row_boundary(WrapPoint::new(wrap_row, 0)) - .unwrap_or(self.wrap_snapshot.max_point().row() + 1), - ); + let start_wrap_row = self + .wrap_snapshot + .prev_row_boundary(WrapPoint::new(wrap_row, 0)); + let end_wrap_row = self + .wrap_snapshot + .next_row_boundary(WrapPoint::new(wrap_row, 0)) + .unwrap_or(self.wrap_snapshot.max_point().row() + WrapRow(1)); let mut cursor = self.transforms.cursor::>(()); cursor.seek(&start_wrap_row, Bias::Left); @@ -1040,7 +1044,7 @@ impl BlockMapWriter<'_> { let wrap_snapshot = &*self.0.wrap_snapshot.borrow(); let buffer = wrap_snapshot.buffer_snapshot(); - let mut previous_wrap_row_range: Option> = None; + let mut previous_wrap_row_range: Option> = None; for block in blocks { if let BlockPlacement::Replace(_) = &block.placement { debug_assert!(block.height.unwrap() > 0); @@ -1063,7 +1067,7 @@ impl BlockMapWriter<'_> { wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0)); let end_row = wrap_snapshot .next_row_boundary(WrapPoint::new(end_wrap_row, 0)) - .unwrap_or(wrap_snapshot.max_point().row() + 1); + .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1)); start_row..end_row }); (range.start, range.end) @@ -1135,7 +1139,7 @@ impl BlockMapWriter<'_> { wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0)); let end = wrap_snapshot .next_row_boundary(WrapPoint::new(end_wrap_row, 0)) - .unwrap_or(wrap_snapshot.max_point().row() + 1); + .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1)); edits.push(Edit { old: start..end, new: start..end, @@ -1153,7 +1157,7 @@ impl BlockMapWriter<'_> { let buffer = wrap_snapshot.buffer_snapshot(); let mut edits = Patch::default(); let mut last_block_buffer_row = None; - let mut previous_wrap_row_range: Option> = None; + let mut previous_wrap_row_range: Option> = None; self.0.custom_blocks.retain(|block| { if block_ids.contains(&block.id) { let start = block.placement.start().to_point(buffer); @@ -1171,7 +1175,7 @@ impl BlockMapWriter<'_> { wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0)); let end_row = wrap_snapshot .next_row_boundary(WrapPoint::new(end_wrap_row, 0)) - .unwrap_or(wrap_snapshot.max_point().row() + 1); + .unwrap_or(wrap_snapshot.max_point().row() + WrapRow(1)); start_row..end_row }); (range.start, range.end) @@ -1255,9 +1259,9 @@ impl BlockMapWriter<'_> { let wrap_snapshot = self.0.wrap_snapshot.borrow().clone(); for range in ranges { let last_edit_row = cmp::min( - wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1, + wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + WrapRow(1), wrap_snapshot.max_point().row(), - ) + 1; + ) + WrapRow(1); let range = wrap_snapshot.make_wrap_point(range.start, Bias::Left).row()..last_edit_row; edits.push(Edit { old: range.clone(), @@ -1304,7 +1308,7 @@ impl BlockSnapshot { #[cfg(test)] pub fn text(&self) -> String { self.chunks( - 0..self.transforms.summary().output_rows, + BlockRow(0)..self.transforms.summary().output_rows, false, false, Highlights::default(), @@ -1315,7 +1319,7 @@ impl BlockSnapshot { pub(crate) fn chunks<'a>( &'a self, - rows: Range, + rows: Range, language_aware: bool, masked: bool, highlights: Highlights<'a>, @@ -1323,9 +1327,9 @@ impl BlockSnapshot { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::>(()); - cursor.seek(&BlockRow(rows.start), Bias::Right); - let transform_output_start = cursor.start().0.0; - let transform_input_start = cursor.start().1.0; + cursor.seek(&rows.start, Bias::Right); + let transform_output_start = cursor.start().0; + let transform_input_start = cursor.start().1; let mut input_start = transform_input_start; let mut input_end = transform_input_start; @@ -1335,7 +1339,7 @@ impl BlockSnapshot { input_start += rows.start - transform_output_start; input_end += cmp::min( rows.end - transform_output_start, - transform.summary.input_rows, + RowDelta(transform.summary.input_rows.0), ); } @@ -1361,11 +1365,11 @@ impl BlockSnapshot { .item() .is_some_and(|transform| transform.block.is_none()) { - start_row.0 - output_start.0 + start_row - *output_start } else { - 0 + RowDelta(0) }; - let input_start_row = input_start.0 + overshoot; + let input_start_row = *input_start + overshoot; BlockRows { transforms: cursor, input_rows: self.wrap_snapshot.row_infos(input_start_row), @@ -1374,16 +1378,19 @@ impl BlockSnapshot { } } - pub fn blocks_in_range(&self, rows: Range) -> impl Iterator { + pub fn blocks_in_range( + &self, + rows: Range, + ) -> impl Iterator { let mut cursor = self.transforms.cursor::(()); - cursor.seek(&BlockRow(rows.start), Bias::Left); - while cursor.start().0 < rows.start && cursor.end().0 <= rows.start { + cursor.seek(&rows.start, Bias::Left); + while *cursor.start() < rows.start && cursor.end() <= rows.start { cursor.next(); } std::iter::from_fn(move || { while let Some(transform) = cursor.item() { - let start_row = cursor.start().0; + let start_row = *cursor.start(); if start_row > rows.end || (start_row == rows.end && transform @@ -1443,7 +1450,7 @@ impl BlockSnapshot { .wrap_snapshot .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left), }; - let wrap_row = WrapRow(wrap_point.row()); + let wrap_row = wrap_point.row(); let mut cursor = self.transforms.cursor::(()); cursor.seek(&wrap_row, Bias::Left); @@ -1464,11 +1471,15 @@ impl BlockSnapshot { } pub fn max_point(&self) -> BlockPoint { - let row = self.transforms.summary().output_rows.saturating_sub(1); - BlockPoint::new(row, self.line_len(BlockRow(row))) + let row = self + .transforms + .summary() + .output_rows + .saturating_sub(RowDelta(1)); + BlockPoint::new(row, self.line_len(row)) } - pub fn longest_row(&self) -> u32 { + pub fn longest_row(&self) -> BlockRow { self.transforms.summary().longest_row } @@ -1480,12 +1491,12 @@ impl BlockSnapshot { let mut longest_row_chars = 0; if let Some(transform) = cursor.item() { if transform.block.is_none() { - let Dimensions(output_start, input_start, _) = cursor.start(); - let overshoot = range.start.0 - output_start.0; - let wrap_start_row = input_start.0 + overshoot; + let &Dimensions(output_start, input_start, _) = cursor.start(); + let overshoot = range.start - output_start; + let wrap_start_row = input_start + WrapRow(overshoot.0); let wrap_end_row = cmp::min( - input_start.0 + (range.end.0 - output_start.0), - cursor.end().1.0, + input_start + WrapRow((range.end - output_start).0), + cursor.end().1, ); let summary = self .wrap_snapshot @@ -1500,22 +1511,22 @@ impl BlockSnapshot { if range.end > cursor_start_row { let summary = cursor.summary::<_, TransformSummary>(&range.end, Bias::Right); if summary.longest_row_chars > longest_row_chars { - longest_row = BlockRow(cursor_start_row.0 + summary.longest_row); + longest_row = cursor_start_row + summary.longest_row; longest_row_chars = summary.longest_row_chars; } if let Some(transform) = cursor.item() && transform.block.is_none() { - let Dimensions(output_start, input_start, _) = cursor.start(); - let overshoot = range.end.0 - output_start.0; - let wrap_start_row = input_start.0; - let wrap_end_row = input_start.0 + overshoot; + let &Dimensions(output_start, input_start, _) = cursor.start(); + let overshoot = range.end - output_start; + let wrap_start_row = input_start; + let wrap_end_row = input_start + overshoot; let summary = self .wrap_snapshot .text_summary_for_range(wrap_start_row..wrap_end_row); if summary.longest_row_chars > longest_row_chars { - longest_row = BlockRow(output_start.0 + summary.longest_row); + longest_row = output_start + RowDelta(summary.longest_row); } } } @@ -1529,13 +1540,13 @@ impl BlockSnapshot { .find::, _>((), &row, Bias::Right); if let Some(transform) = item { let Dimensions(output_start, input_start, _) = start; - let overshoot = row.0 - output_start.0; + let overshoot = row - output_start; if transform.block.is_some() { 0 } else { - self.wrap_snapshot.line_len(input_start.0 + overshoot) + self.wrap_snapshot.line_len(input_start + overshoot) } - } else if row.0 == 0 { + } else if row == BlockRow(0) { 0 } else { panic!("row out of range"); @@ -1559,9 +1570,9 @@ impl BlockSnapshot { let wrap_point = self .wrap_snapshot .make_wrap_point(Point::new(row.0, 0), Bias::Left); - let (_, _, item) = - self.transforms - .find::((), &WrapRow(wrap_point.row()), Bias::Right); + let (_, _, item) = self + .transforms + .find::((), &wrap_point.row(), Bias::Right); item.is_some_and(|transform| { transform .block @@ -1574,9 +1585,9 @@ impl BlockSnapshot { let mut cursor = self.transforms.cursor::>(()); cursor.seek(&BlockRow(point.row), Bias::Right); - let max_input_row = WrapRow(self.transforms.summary().input_rows); - let mut search_left = - (bias == Bias::Left && cursor.start().1.0 > 0) || cursor.end().1 == max_input_row; + let max_input_row = self.transforms.summary().input_rows; + let mut search_left = (bias == Bias::Left && cursor.start().1 > WrapRow(0)) + || cursor.end().1 == max_input_row; let mut reversed = false; loop { @@ -1598,9 +1609,11 @@ impl BlockSnapshot { } None => { let input_point = if point.row >= output_end_row.0 { - let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1); - self.wrap_snapshot - .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias) + let line_len = self.wrap_snapshot.line_len(input_end_row - RowDelta(1)); + self.wrap_snapshot.clip_point( + WrapPoint::new(input_end_row - RowDelta(1), line_len), + bias, + ) } else { let output_overshoot = point.0.saturating_sub(output_start); self.wrap_snapshot @@ -1632,12 +1645,12 @@ impl BlockSnapshot { pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { let (start, _, item) = self.transforms.find::, _>( (), - &WrapRow(wrap_point.row()), + &wrap_point.row(), Bias::Right, ); if let Some(transform) = item { if transform.block.is_some() { - BlockPoint::new(start.1.0, 0) + BlockPoint::new(start.1, 0) } else { let Dimensions(input_start_row, output_start_row, _) = start; let input_start = Point::new(input_start_row.0, 0); @@ -1660,20 +1673,20 @@ impl BlockSnapshot { match transform.block.as_ref() { Some(block) => { if block.place_below() { - let wrap_row = start.1.0 - 1; + let wrap_row = start.1 - RowDelta(1); WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) } else if block.place_above() { - WrapPoint::new(start.1.0, 0) + WrapPoint::new(start.1, 0) } else if bias == Bias::Left { - WrapPoint::new(start.1.0, 0) + WrapPoint::new(start.1, 0) } else { - let wrap_row = end.1.0 - 1; + let wrap_row = end.1 - RowDelta(1); WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) } } None => { - let overshoot = block_point.row - start.0.0; - let wrap_row = start.1.0 + overshoot; + let overshoot = block_point.row() - start.0; + let wrap_row = start.1 + RowDelta(overshoot.0); WrapPoint::new(wrap_row, block_point.column) } } @@ -1705,11 +1718,11 @@ impl BlockChunks<'_> { .item() .is_some_and(|transform| transform.block.is_none()) { - let start_input_row = self.transforms.start().1.0; - let start_output_row = self.transforms.start().0.0; + let start_input_row = self.transforms.start().1; + let start_output_row = self.transforms.start().0; if start_output_row < self.max_output_row { let end_input_row = cmp::min( - self.transforms.end().1.0, + self.transforms.end().1, start_input_row + (self.max_output_row - start_output_row), ); self.input_chunks.seek(start_input_row..end_input_row); @@ -1732,22 +1745,22 @@ impl<'a> Iterator for BlockChunks<'a> { let transform = self.transforms.item()?; if transform.block.is_some() { - let block_start = self.transforms.start().0.0; - let mut block_end = self.transforms.end().0.0; + let block_start = self.transforms.start().0; + let mut block_end = self.transforms.end().0; self.advance(); if self.transforms.item().is_none() { - block_end -= 1; + block_end -= RowDelta(1); } let start_in_block = self.output_row - block_start; let end_in_block = cmp::min(self.max_output_row, block_end) - block_start; - // todo: We need to split the chunk here? - let line_count = cmp::min(end_in_block - start_in_block, u128::BITS); + // todo: We need to split the chunk here instead of taking min + let line_count = cmp::min(end_in_block - start_in_block, RowDelta(u128::BITS)); self.output_row += line_count; return Some(Chunk { - text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) }, - chars: 1u128.unbounded_shl(line_count) - 1, + text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count.0 as usize]) }, + chars: 1u128.unbounded_shl(line_count.0) - 1, ..Default::default() }); } @@ -1757,7 +1770,7 @@ impl<'a> Iterator for BlockChunks<'a> { self.input_chunk = input_chunk; } else { if self.output_row < self.max_output_row { - self.output_row += 1; + self.output_row.0 += 1; self.advance(); if self.transforms.item().is_some() { return Some(Chunk { @@ -1771,7 +1784,7 @@ impl<'a> Iterator for BlockChunks<'a> { } } - let transform_end = self.transforms.end().0.0; + let transform_end = self.transforms.end().0; let (prefix_rows, prefix_bytes) = offset_for_row(self.input_chunk.text, transform_end - self.output_row); self.output_row += prefix_rows; @@ -1819,7 +1832,7 @@ impl Iterator for BlockRows<'_> { self.started = true; } - if self.output_row.0 >= self.transforms.end().0.0 { + if self.output_row >= self.transforms.end().0 { self.transforms.next(); while let Some(transform) = self.transforms.item() { if transform @@ -1839,7 +1852,7 @@ impl Iterator for BlockRows<'_> { .as_ref() .is_none_or(|block| block.is_replacement()) { - self.input_rows.seek(self.transforms.start().1.0); + self.input_rows.seek(self.transforms.start().1); } } @@ -1889,7 +1902,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow { } fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { - self.0 += summary.input_rows; + *self += summary.input_rows; } } @@ -1899,7 +1912,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow { } fn add_summary(&mut self, summary: &'a TransformSummary, _: ()) { - self.0 += summary.output_rows; + *self += summary.output_rows; } } @@ -1949,7 +1962,7 @@ impl Debug for CustomBlock { // Count the number of bytes prior to a target point. If the string doesn't contain the target // point, return its total extent. Otherwise return the target point itself. -fn offset_for_row(s: &str, target: u32) -> (u32, usize) { +fn offset_for_row(s: &str, target: RowDelta) -> (RowDelta, usize) { let mut row = 0; let mut offset = 0; for (ix, line) in s.split('\n').enumerate() { @@ -1957,12 +1970,12 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { row += 1; offset += 1; } - if row >= target { + if row >= target.0 { break; } offset += line.len(); } - (row, offset) + (RowDelta(row), offset) } #[cfg(test)] @@ -1983,16 +1996,28 @@ mod tests { #[gpui::test] fn test_offset_for_row() { - assert_eq!(offset_for_row("", 0), (0, 0)); - assert_eq!(offset_for_row("", 1), (0, 0)); - assert_eq!(offset_for_row("abcd", 0), (0, 0)); - assert_eq!(offset_for_row("abcd", 1), (0, 4)); - assert_eq!(offset_for_row("\n", 0), (0, 0)); - assert_eq!(offset_for_row("\n", 1), (1, 1)); - assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0)); - assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4)); - assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8)); - assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11)); + assert_eq!(offset_for_row("", RowDelta(0)), (RowDelta(0), 0)); + assert_eq!(offset_for_row("", RowDelta(1)), (RowDelta(0), 0)); + assert_eq!(offset_for_row("abcd", RowDelta(0)), (RowDelta(0), 0)); + assert_eq!(offset_for_row("abcd", RowDelta(1)), (RowDelta(0), 4)); + assert_eq!(offset_for_row("\n", RowDelta(0)), (RowDelta(0), 0)); + assert_eq!(offset_for_row("\n", RowDelta(1)), (RowDelta(1), 1)); + assert_eq!( + offset_for_row("abc\ndef\nghi", RowDelta(0)), + (RowDelta(0), 0) + ); + assert_eq!( + offset_for_row("abc\ndef\nghi", RowDelta(1)), + (RowDelta(1), 4) + ); + assert_eq!( + offset_for_row("abc\ndef\nghi", RowDelta(2)), + (RowDelta(2), 8) + ); + assert_eq!( + offset_for_row("abc\ndef\nghi", RowDelta(3)), + (RowDelta(2), 11) + ); } #[gpui::test] @@ -2040,10 +2065,10 @@ mod tests { assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n"); let blocks = snapshot - .blocks_in_range(0..8) + .blocks_in_range(BlockRow(0)..BlockRow(8)) .map(|(start_row, block)| { let block = block.as_custom().unwrap(); - (start_row..start_row + block.height.unwrap(), block.id) + (start_row.0..start_row.0 + block.height.unwrap(), block.id) }) .collect::>(); @@ -2058,74 +2083,74 @@ mod tests { ); assert_eq!( - snapshot.to_block_point(WrapPoint::new(0, 3)), - BlockPoint::new(0, 3) + snapshot.to_block_point(WrapPoint::new(WrapRow(0), 3)), + BlockPoint::new(BlockRow(0), 3) ); assert_eq!( - snapshot.to_block_point(WrapPoint::new(1, 0)), - BlockPoint::new(4, 0) + snapshot.to_block_point(WrapPoint::new(WrapRow(1), 0)), + BlockPoint::new(BlockRow(4), 0) ); assert_eq!( - snapshot.to_block_point(WrapPoint::new(3, 3)), - BlockPoint::new(6, 3) + snapshot.to_block_point(WrapPoint::new(WrapRow(3), 3)), + BlockPoint::new(BlockRow(6), 3) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(0, 3), Bias::Left), - WrapPoint::new(0, 3) + snapshot.to_wrap_point(BlockPoint::new(BlockRow(0), 3), Bias::Left), + WrapPoint::new(WrapRow(0), 3) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(1, 0), Bias::Left), - WrapPoint::new(1, 0) + snapshot.to_wrap_point(BlockPoint::new(BlockRow(1), 0), Bias::Left), + WrapPoint::new(WrapRow(1), 0) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(3, 0), Bias::Left), - WrapPoint::new(1, 0) + snapshot.to_wrap_point(BlockPoint::new(BlockRow(3), 0), Bias::Left), + WrapPoint::new(WrapRow(1), 0) ); assert_eq!( - snapshot.to_wrap_point(BlockPoint::new(7, 0), Bias::Left), - WrapPoint::new(3, 3) + snapshot.to_wrap_point(BlockPoint::new(BlockRow(7), 0), Bias::Left), + WrapPoint::new(WrapRow(3), 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left), - BlockPoint::new(0, 3) + snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Left), + BlockPoint::new(BlockRow(0), 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), - BlockPoint::new(4, 0) + snapshot.clip_point(BlockPoint::new(BlockRow(1), 0), Bias::Right), + BlockPoint::new(BlockRow(4), 0) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), - BlockPoint::new(0, 3) + snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Left), + BlockPoint::new(BlockRow(0), 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), - BlockPoint::new(4, 0) + snapshot.clip_point(BlockPoint::new(BlockRow(1), 1), Bias::Right), + BlockPoint::new(BlockRow(4), 0) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left), - BlockPoint::new(4, 0) + snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Left), + BlockPoint::new(BlockRow(4), 0) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right), - BlockPoint::new(4, 0) + snapshot.clip_point(BlockPoint::new(BlockRow(4), 0), Bias::Right), + BlockPoint::new(BlockRow(4), 0) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left), - BlockPoint::new(6, 3) + snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Left), + BlockPoint::new(BlockRow(6), 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right), - BlockPoint::new(6, 3) + snapshot.clip_point(BlockPoint::new(BlockRow(6), 3), Bias::Right), + BlockPoint::new(BlockRow(6), 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left), - BlockPoint::new(6, 3) + snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Left), + BlockPoint::new(BlockRow(6), 3) ); assert_eq!( - snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right), - BlockPoint::new(6, 3) + snapshot.clip_point(BlockPoint::new(BlockRow(7), 0), Bias::Right), + BlockPoint::new(BlockRow(6), 3) ); assert_eq!( @@ -2220,8 +2245,8 @@ mod tests { assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3"); let blocks: Vec<_> = snapshot - .blocks_in_range(0..u32::MAX) - .map(|(row, block)| (row..row + block.height(), block.id())) + .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX)) + .map(|(row, block)| (row.0..row.0 + block.height(), block.id())) .collect(); assert_eq!( blocks, @@ -2675,7 +2700,7 @@ mod tests { }]); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks = blocks_snapshot - .blocks_in_range(0..u32::MAX) + .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX)) .collect::>(); for (_, block) in &blocks { if let BlockId::Custom(custom_block_id) = block.id() { @@ -2736,7 +2761,7 @@ mod tests { }); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks = blocks_snapshot - .blocks_in_range(0..u32::MAX) + .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX)) .collect::>(); for (_, block) in &blocks { if let BlockId::Custom(custom_block_id) = block.id() { @@ -2789,7 +2814,7 @@ mod tests { }); let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default()); let blocks = blocks_snapshot - .blocks_in_range(0..u32::MAX) + .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX)) .collect::>(); for (_, block) in &blocks { if let BlockId::Custom(custom_block_id) = block.id() { @@ -2845,7 +2870,7 @@ mod tests { }); let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default()); let blocks = blocks_snapshot - .blocks_in_range(0..u32::MAX) + .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX)) .collect::>(); for (_, block) in &blocks { if let BlockId::Custom(custom_block_id) = block.id() { @@ -2912,7 +2937,7 @@ mod tests { }); let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default()); let blocks = blocks_snapshot - .blocks_in_range(0..u32::MAX) + .blocks_in_range(BlockRow(0)..BlockRow(u32::MAX)) .collect::>(); assert_eq!( 1, @@ -3185,7 +3210,7 @@ mod tests { let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); assert_eq!( blocks_snapshot.transforms.summary().input_rows, - wraps_snapshot.max_point().row() + 1 + wraps_snapshot.max_point().row() + RowDelta(1) ); log::info!("wrapped text: {:?}", wraps_snapshot.text()); log::info!("blocks text: {:?}", blocks_snapshot.text()); @@ -3238,14 +3263,14 @@ mod tests { let input_text_lines = input_text.split('\n').enumerate().peekable(); let mut block_row = 0; for (wrap_row, input_line) in input_text_lines { - let wrap_row = wrap_row as u32; + let wrap_row = WrapRow(wrap_row as u32); let multibuffer_row = wraps_snapshot .to_point(WrapPoint::new(wrap_row, 0), Bias::Left) .row; // Create empty lines for the above block while let Some((placement, block)) = sorted_blocks_iter.peek() { - if placement.start().0 == wrap_row && block.place_above() { + if *placement.start() == wrap_row && block.place_above() { let (_, block) = sorted_blocks_iter.next().unwrap(); expected_block_positions.push((block_row, block.id())); if block.height() > 0 { @@ -3268,11 +3293,11 @@ mod tests { let mut is_in_replace_block = false; if let Some((BlockPlacement::Replace(replace_range), block)) = sorted_blocks_iter.peek() - && wrap_row >= replace_range.start().0 + && wrap_row >= *replace_range.start() { is_in_replace_block = true; - if wrap_row == replace_range.start().0 { + if wrap_row == *replace_range.start() { if matches!(block, Block::FoldedBuffer { .. }) { expected_buffer_rows.push(None); } else { @@ -3280,7 +3305,7 @@ mod tests { } } - if wrap_row == replace_range.end().0 { + if wrap_row == *replace_range.end() { expected_block_positions.push((block_row, block.id())); let text = "\n".repeat((block.height() - 1) as usize); if block_row > 0 { @@ -3314,7 +3339,7 @@ mod tests { } while let Some((placement, block)) = sorted_blocks_iter.peek() { - if placement.end().0 == wrap_row && block.place_below() { + if *placement.end() == wrap_row && block.place_below() { let (_, block) = sorted_blocks_iter.next().unwrap(); expected_block_positions.push((block_row, block.id())); if block.height() > 0 { @@ -3358,7 +3383,7 @@ mod tests { let actual_text = blocks_snapshot .chunks( - start_row as u32..end_row as u32, + BlockRow(start_row as u32)..BlockRow(end_row as u32), false, false, Highlights::default(), @@ -3384,8 +3409,8 @@ mod tests { assert_eq!( blocks_snapshot - .blocks_in_range(0..(expected_row_count as u32)) - .map(|(row, block)| (row, block.id())) + .blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32)) + .map(|(row, block)| (row.0, block.id())) .collect::>(), expected_block_positions, "invalid blocks_in_range({:?})", @@ -3393,7 +3418,7 @@ mod tests { ); for (_, expected_block) in - blocks_snapshot.blocks_in_range(0..(expected_row_count as u32)) + blocks_snapshot.blocks_in_range(BlockRow(0)..BlockRow(expected_row_count as u32)) { let actual_block = blocks_snapshot.block_for_id(expected_block.id()); assert_eq!( @@ -3437,9 +3462,9 @@ mod tests { let longest_row = blocks_snapshot.longest_row(); assert!( - expected_longest_rows.contains(&longest_row), + expected_longest_rows.contains(&longest_row.0), "incorrect longest row {}. expected {:?} with length {}", - longest_row, + longest_row.0, expected_longest_rows, longest_line_len, ); @@ -3471,7 +3496,7 @@ mod tests { assert!( expected_longest_rows_in_range.contains(&longest_row_in_range.0), "incorrect longest row {} in range {:?}. expected {:?} with length {}", - longest_row, + longest_row.0, start_row..end_row, expected_longest_rows_in_range, longest_line_len_in_range, @@ -3479,8 +3504,8 @@ mod tests { } // Ensure that conversion between block points and wrap points is stable. - for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { - let wrap_point = WrapPoint::new(row, 0); + for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row().0 { + let wrap_point = WrapPoint::new(WrapRow(row), 0); let block_point = blocks_snapshot.to_block_point(wrap_point); let left_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Left); let right_wrap_point = blocks_snapshot.to_wrap_point(block_point, Bias::Right); @@ -3491,7 +3516,7 @@ mod tests { ); } - let mut block_point = BlockPoint::new(0, 0); + let mut block_point = BlockPoint::new(BlockRow(0), 0); for c in expected_text.chars() { let left_point = blocks_snapshot.clip_point(block_point, Bias::Left); let left_buffer_point = blocks_snapshot.to_point(left_point, Bias::Left); diff --git a/crates/editor/src/display_map/dimensions.rs b/crates/editor/src/display_map/dimensions.rs new file mode 100644 index 0000000000..fd8efa6ca5 --- /dev/null +++ b/crates/editor/src/display_map/dimensions.rs @@ -0,0 +1,96 @@ +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct RowDelta(pub u32); + +impl RowDelta { + pub fn saturating_sub(self, other: RowDelta) -> RowDelta { + RowDelta(self.0.saturating_sub(other.0)) + } +} + +impl ::std::ops::Add for RowDelta { + type Output = RowDelta; + + fn add(self, rhs: RowDelta) -> Self::Output { + RowDelta(self.0 + rhs.0) + } +} + +impl ::std::ops::Sub for RowDelta { + type Output = RowDelta; + + fn sub(self, rhs: RowDelta) -> Self::Output { + RowDelta(self.0 - rhs.0) + } +} + +impl ::std::ops::AddAssign for RowDelta { + fn add_assign(&mut self, rhs: RowDelta) { + self.0 += rhs.0; + } +} + +impl ::std::ops::SubAssign for RowDelta { + fn sub_assign(&mut self, rhs: RowDelta) { + self.0 -= rhs.0; + } +} + +macro_rules! impl_for_row_types { + ($row:ident => $row_delta:ident) => { + impl $row { + pub fn saturating_sub(self, other: $row_delta) -> $row { + $row(self.0.saturating_sub(other.0)) + } + } + + impl ::std::ops::Add for $row { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } + } + + impl ::std::ops::Add<$row_delta> for $row { + type Output = Self; + + fn add(self, rhs: $row_delta) -> Self::Output { + Self(self.0 + rhs.0) + } + } + + impl ::std::ops::Sub for $row { + type Output = $row_delta; + + fn sub(self, rhs: Self) -> Self::Output { + $row_delta(self.0 - rhs.0) + } + } + + impl ::std::ops::Sub<$row_delta> for $row { + type Output = $row; + + fn sub(self, rhs: $row_delta) -> Self::Output { + $row(self.0 - rhs.0) + } + } + + impl ::std::ops::AddAssign for $row { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } + } + + impl ::std::ops::AddAssign<$row_delta> for $row { + fn add_assign(&mut self, rhs: $row_delta) { + self.0 += rhs.0; + } + } + + impl ::std::ops::SubAssign<$row_delta> for $row { + fn sub_assign(&mut self, rhs: $row_delta) { + self.0 -= rhs.0; + } + } + }; +} diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 7371eb6785..5c71ade683 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,6 @@ use super::{ Highlights, + dimensions::RowDelta, fold_map::{Chunk, FoldRows}, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, }; @@ -7,13 +8,20 @@ use gpui::{App, AppContext as _, Context, Entity, Font, LineWrapper, Pixels, Tas use language::Point; use multi_buffer::{MultiBufferSnapshot, RowInfo}; use smol::future::yield_now; -use std::sync::LazyLock; -use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration}; +use std::{cmp, collections::VecDeque, mem, ops::Range, sync::LazyLock, time::Duration}; use sum_tree::{Bias, Cursor, Dimensions, SumTree}; use text::Patch; pub use super::tab_map::TextSummary; -pub type WrapEdit = text::Edit; +pub type WrapEdit = text::Edit; +pub type WrapPatch = text::Patch; + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct WrapRow(pub u32); + +impl_for_row_types! { + WrapRow => RowDelta +} /// Handles soft wrapping of text. /// @@ -21,8 +29,8 @@ pub type WrapEdit = text::Edit; pub struct WrapMap { snapshot: WrapSnapshot, pending_edits: VecDeque<(TabSnapshot, Vec)>, - interpolated_edits: Patch, - edits_since_sync: Patch, + interpolated_edits: WrapPatch, + edits_since_sync: WrapPatch, wrap_width: Option, background_task: Option>, font_with_size: (Font, Pixels), @@ -54,7 +62,7 @@ pub struct WrapChunks<'a> { input_chunks: tab_map::TabChunks<'a>, input_chunk: Chunk<'a>, output_position: WrapPoint, - max_output_row: u32, + max_output_row: WrapRow, transforms: Cursor<'a, 'static, Transform, Dimensions>, snapshot: &'a WrapSnapshot, } @@ -63,19 +71,19 @@ pub struct WrapChunks<'a> { pub struct WrapRows<'a> { input_buffer_rows: FoldRows<'a>, input_buffer_row: RowInfo, - output_row: u32, + output_row: WrapRow, soft_wrapped: bool, - max_output_row: u32, + max_output_row: WrapRow, transforms: Cursor<'a, 'static, Transform, Dimensions>, } impl WrapRows<'_> { - pub(crate) fn seek(&mut self, start_row: u32) { + pub(crate) fn seek(&mut self, start_row: WrapRow) { self.transforms .seek(&WrapPoint::new(start_row, 0), Bias::Left); let mut input_row = self.transforms.start().1.row(); if self.transforms.item().is_some_and(|t| t.is_isomorphic()) { - input_row += start_row - self.transforms.start().0.row(); + input_row += (start_row - self.transforms.start().0.row()).0; } self.soft_wrapped = self.transforms.item().is_some_and(|t| !t.is_isomorphic()); self.input_buffer_rows.seek(input_row); @@ -120,7 +128,7 @@ impl WrapMap { tab_snapshot: TabSnapshot, edits: Vec, cx: &mut Context, - ) -> (WrapSnapshot, Patch) { + ) -> (WrapSnapshot, WrapPatch) { if self.wrap_width.is_some() { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); @@ -226,8 +234,8 @@ impl WrapMap { let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.interpolated = false; self.edits_since_sync = self.edits_since_sync.compose(Patch::new(vec![WrapEdit { - old: 0..old_rows, - new: 0..new_rows, + old: WrapRow(0)..WrapRow(old_rows), + new: WrapRow(0)..WrapRow(new_rows), }])); } } @@ -331,7 +339,7 @@ impl WrapSnapshot { self.tab_snapshot.buffer_snapshot() } - fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch { + fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> WrapPatch { let mut new_transforms; if tab_edits.is_empty() { new_transforms = self.transforms.clone(); @@ -401,7 +409,7 @@ impl WrapSnapshot { tab_edits: &[TabEdit], wrap_width: Pixels, line_wrapper: &mut LineWrapper, - ) -> Patch { + ) -> WrapPatch { #[derive(Debug)] struct RowEdit { old_rows: Range, @@ -554,7 +562,7 @@ impl WrapSnapshot { old_snapshot.compute_edits(tab_edits, self) } - fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch { + fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> WrapPatch { let mut wrap_edits = Vec::with_capacity(tab_edits.len()); let mut old_cursor = self.transforms.cursor::(()); let mut new_cursor = new_snapshot.transforms.cursor::(()); @@ -581,8 +589,8 @@ impl WrapSnapshot { new_end += tab_edit.new.end.0 - new_cursor.start().input.lines; wrap_edits.push(WrapEdit { - old: old_start.row..old_end.row, - new: new_start.row..new_end.row, + old: WrapRow(old_start.row)..WrapRow(old_end.row), + new: WrapRow(new_start.row)..WrapRow(new_end.row), }); } @@ -592,7 +600,7 @@ impl WrapSnapshot { pub(crate) fn chunks<'a>( &'a self, - rows: Range, + rows: Range, language_aware: bool, highlights: Highlights<'a>, ) -> WrapChunks<'a> { @@ -627,17 +635,17 @@ impl WrapSnapshot { WrapPoint(self.transforms.summary().output.lines) } - pub fn line_len(&self, row: u32) -> u32 { + pub fn line_len(&self, row: WrapRow) -> u32 { let (start, _, item) = self.transforms.find::, _>( (), - &WrapPoint::new(row + 1, 0), + &WrapPoint::new(row + WrapRow(1), 0), Bias::Left, ); if item.is_some_and(|transform| transform.is_isomorphic()) { let overshoot = row - start.0.row(); - let tab_row = start.1.row() + overshoot; + let tab_row = start.1.row() + overshoot.0; let tab_line_len = self.tab_snapshot.line_len(tab_row); - if overshoot == 0 { + if overshoot.0 == 0 { start.0.column() + (tab_line_len - start.1.column()) } else { tab_line_len @@ -647,7 +655,7 @@ impl WrapSnapshot { } } - pub fn text_summary_for_range(&self, rows: Range) -> TextSummary { + pub fn text_summary_for_range(&self, rows: Range) -> TextSummary { let mut summary = TextSummary::default(); let start = WrapPoint::new(rows.start, 0); @@ -708,10 +716,12 @@ impl WrapSnapshot { summary } - pub fn soft_wrap_indent(&self, row: u32) -> Option { - let (.., item) = - self.transforms - .find::((), &WrapPoint::new(row + 1, 0), Bias::Right); + pub fn soft_wrap_indent(&self, row: WrapRow) -> Option { + let (.., item) = self.transforms.find::( + (), + &WrapPoint::new(row + WrapRow(1), 0), + Bias::Right, + ); item.and_then(|transform| { if transform.is_isomorphic() { None @@ -725,14 +735,14 @@ impl WrapSnapshot { self.transforms.summary().output.longest_row } - pub fn row_infos(&self, start_row: u32) -> WrapRows<'_> { + pub fn row_infos(&self, start_row: WrapRow) -> WrapRows<'_> { let mut transforms = self .transforms .cursor::>(()); transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left); let mut input_row = transforms.start().1.row(); if transforms.item().is_some_and(|t| t.is_isomorphic()) { - input_row += start_row - transforms.start().0.row(); + input_row += (start_row - transforms.start().0.row()).0; } let soft_wrapped = transforms.item().is_some_and(|t| !t.is_isomorphic()); let mut input_buffer_rows = self.tab_snapshot.rows(input_row); @@ -787,9 +797,9 @@ impl WrapSnapshot { self.tab_point_to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias)) } - pub fn prev_row_boundary(&self, mut point: WrapPoint) -> u32 { + pub fn prev_row_boundary(&self, mut point: WrapPoint) -> WrapRow { if self.transforms.is_empty() { - return 0; + return WrapRow(0); } *point.column_mut() = 0; @@ -813,7 +823,7 @@ impl WrapSnapshot { unreachable!() } - pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option { + pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option { point.0 += Point::new(1, 0); let mut cursor = self @@ -833,13 +843,13 @@ impl WrapSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.text_chunks(0).collect() + self.text_chunks(WrapRow(0)).collect() } #[cfg(test)] - pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { + pub fn text_chunks(&self, wrap_row: WrapRow) -> impl Iterator { self.chunks( - wrap_row..self.max_point().row() + 1, + wrap_row..self.max_point().row() + WrapRow(1), false, Highlights::default(), ) @@ -867,21 +877,22 @@ impl WrapSnapshot { let mut input_buffer_rows = self.tab_snapshot.rows(0); let mut expected_buffer_rows = Vec::new(); let mut prev_tab_row = 0; - for display_row in 0..=self.max_point().row() { + for display_row in 0..=self.max_point().row().0 { + let display_row = WrapRow(display_row); let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - if tab_point.row() == prev_tab_row && display_row != 0 { + if tab_point.row() == prev_tab_row && display_row != WrapRow(0) { expected_buffer_rows.push(None); } else { expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row); } prev_tab_row = tab_point.row(); - assert_eq!(self.line_len(display_row), text.line_len(display_row)); + assert_eq!(self.line_len(display_row), text.line_len(display_row.0)); } for start_display_row in 0..expected_buffer_rows.len() { assert_eq!( - self.row_infos(start_display_row as u32) + self.row_infos(WrapRow(start_display_row as u32)) .map(|row_info| row_info.buffer_row) .collect::>(), &expected_buffer_rows[start_display_row..], @@ -894,7 +905,7 @@ impl WrapSnapshot { } impl WrapChunks<'_> { - pub(crate) fn seek(&mut self, rows: Range) { + pub(crate) fn seek(&mut self, rows: Range) { let output_start = WrapPoint::new(rows.start, 0); let output_end = WrapPoint::new(rows.end, 0); self.transforms.seek(&output_start, Bias::Right); @@ -931,7 +942,7 @@ impl<'a> Iterator for WrapChunks<'a> { // Exclude newline starting prior to the desired row. start_ix = 1; summary.row = 0; - } else if self.output_position.row() + 1 >= self.max_output_row { + } else if self.output_position.row() + WrapRow(1) >= self.max_output_row { // Exclude soft indentation ending after the desired row. end_ix = 1; summary.column = 0; @@ -997,7 +1008,7 @@ impl Iterator for WrapRows<'_> { let soft_wrapped = self.soft_wrapped; let diff_status = self.input_buffer_row.diff_status; - self.output_row += 1; + self.output_row += WrapRow(1); self.transforms .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left); if self.transforms.item().is_some_and(|t| t.is_isomorphic()) { @@ -1107,12 +1118,12 @@ impl SumTreeExt for SumTree { } impl WrapPoint { - pub fn new(row: u32, column: u32) -> Self { - Self(Point::new(row, column)) + pub fn new(row: WrapRow, column: u32) -> Self { + Self(Point::new(row.0, column)) } - pub fn row(self) -> u32 { - self.0.row + pub fn row(self) -> WrapRow { + WrapRow(self.0.row) } pub fn row_mut(&mut self) -> &mut u32 { @@ -1417,14 +1428,14 @@ mod tests { for (snapshot, patch) in edits { let snapshot_text = Rope::from(snapshot.text().as_str()); for edit in &patch { - let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0)); + let old_start = initial_text.point_to_offset(Point::new(edit.new.start.0, 0)); let old_end = initial_text.point_to_offset(cmp::min( - Point::new(edit.new.start + edit.old.len() as u32, 0), + Point::new(edit.new.start.0 + (edit.old.end - edit.old.start).0, 0), initial_text.max_point(), )); - let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0)); + let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start.0, 0)); let new_end = snapshot_text.point_to_offset(cmp::min( - Point::new(edit.new.end, 0), + Point::new(edit.new.end.0, 0), snapshot_text.max_point(), )); let new_text = snapshot_text @@ -1484,11 +1495,11 @@ mod tests { impl WrapSnapshot { fn verify_chunks(&mut self, rng: &mut impl Rng) { for _ in 0..5 { - let mut end_row = rng.random_range(0..=self.max_point().row()); + let mut end_row = rng.random_range(0..=self.max_point().row().0); let start_row = rng.random_range(0..=end_row); end_row += 1; - let mut expected_text = self.text_chunks(start_row).collect::(); + let mut expected_text = self.text_chunks(WrapRow(start_row)).collect::(); if expected_text.ends_with('\n') { expected_text.push('\n'); } @@ -1497,12 +1508,16 @@ mod tests { .take((end_row - start_row) as usize) .collect::>() .join("\n"); - if end_row <= self.max_point().row() { + if end_row <= self.max_point().row().0 { expected_text.push('\n'); } let actual_text = self - .chunks(start_row..end_row, true, Highlights::default()) + .chunks( + WrapRow(start_row)..WrapRow(end_row), + true, + Highlights::default(), + ) .map(|c| c.text) .collect::(); assert_eq!( diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index b8bb904052..ec495f60fd 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -9,15 +9,7 @@ pub struct Patch(Vec>); impl Patch where - T: 'static - + Clone - + Copy - + Ord - + Sub - + Add - + AddAssign - + Default - + PartialEq, + T: 'static + Clone + Copy + Ord + Default, { pub fn new(edits: Vec>) -> Self { #[cfg(debug_assertions)] @@ -41,7 +33,50 @@ where pub fn into_inner(self) -> Vec> { self.0 } + pub fn invert(&mut self) -> &mut Self { + for edit in &mut self.0 { + mem::swap(&mut edit.old, &mut edit.new); + } + self + } + pub fn clear(&mut self) { + self.0.clear(); + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn push(&mut self, edit: Edit) { + if edit.is_empty() { + return; + } + + if let Some(last) = self.0.last_mut() { + if last.old.end >= edit.old.start { + last.old.end = edit.old.end; + last.new.end = edit.new.end; + } else { + self.0.push(edit); + } + } else { + self.0.push(edit); + } + } +} + +impl Patch +where + T: 'static + + Copy + + Ord + + Sub + + Add + + AddAssign + + Default, + TDelta: Ord + Copy, +{ #[must_use] pub fn compose(&self, new_edits_iter: impl IntoIterator>) -> Self { let mut old_edits_iter = self.0.iter().cloned().peekable(); @@ -169,38 +204,6 @@ where composed } - pub fn invert(&mut self) -> &mut Self { - for edit in &mut self.0 { - mem::swap(&mut edit.old, &mut edit.new); - } - self - } - - pub fn clear(&mut self) { - self.0.clear(); - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn push(&mut self, edit: Edit) { - if edit.is_empty() { - return; - } - - if let Some(last) = self.0.last_mut() { - if last.old.end >= edit.old.start { - last.old.end = edit.old.end; - last.new.end = edit.new.end; - } else { - self.0.push(edit); - } - } else { - self.0.push(edit); - } - } - pub fn old_to_new(&self, old: T) -> T { let ix = match self.0.binary_search_by(|probe| probe.old.start.cmp(&old)) { Ok(ix) => ix, diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 8e98ac5f19..851d4fecc4 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -496,22 +496,26 @@ pub struct Edit { pub old: Range, pub new: Range, } - impl Edit where - D: Sub + PartialEq + Copy, + D: PartialEq, { - pub fn old_len(&self) -> D { + pub fn is_empty(&self) -> bool { + self.old.start == self.old.end && self.new.start == self.new.end + } +} + +impl Edit +where + D: Sub + Copy, +{ + pub fn old_len(&self) -> DDelta { self.old.end - self.old.start } - pub fn new_len(&self) -> D { + pub fn new_len(&self) -> DDelta { self.new.end - self.new.start } - - pub fn is_empty(&self) -> bool { - self.old.start == self.old.end && self.new.start == self.new.end - } } impl Edit<(D1, D2)> {