editor: Correctly handle blocks spanning more than 128 rows (#42172)

Lukas Wirth created

Release Notes:

- Fixed block rendering for blocks spanning more than 128 rows

Change summary

crates/editor/src/display_map.rs           | 31 ++++++++++++-----------
crates/editor/src/display_map/block_map.rs | 26 +++++++++++++++----
crates/editor/src/hover_popover.rs         |  2 
3 files changed, 37 insertions(+), 22 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -25,29 +25,25 @@ mod crease_map;
 mod custom_highlights;
 mod fold_map;
 mod inlay_map;
-pub(crate) mod invisibles;
+mod invisibles;
 mod tab_map;
 mod wrap_map;
 
-use crate::{
-    EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,
-};
+pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap};
 pub use block_map::{
     Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement,
     BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, EditorMargins, RenderBlock,
     StickyHeaderExcerpt,
 };
-use block_map::{BlockRow, BlockSnapshot};
-use collections::{HashMap, HashSet};
 pub use crease_map::*;
-use fold_map::FoldSnapshot;
 pub use fold_map::{
     ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint,
 };
-use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
-use inlay_map::InlaySnapshot;
 pub use inlay_map::{InlayOffset, InlayPoint};
 pub use invisibles::{is_invisible, replacement};
+
+use collections::{HashMap, HashSet};
+use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle};
 use language::{
     OffsetUtf16, Point, Subscription as BufferSubscription, language_settings::language_settings,
 };
@@ -58,6 +54,10 @@ use multi_buffer::{
 use project::InlayId;
 use project::project_settings::DiagnosticSeverity;
 use serde::Deserialize;
+use sum_tree::{Bias, TreeMap};
+use text::{BufferId, LineIndent};
+use ui::{SharedString, px};
+use unicode_segmentation::UnicodeSegmentation;
 
 use std::{
     any::TypeId,
@@ -68,15 +68,16 @@ use std::{
     ops::{Add, Range, Sub},
     sync::Arc,
 };
-use sum_tree::{Bias, TreeMap};
+
+use crate::{
+    EditorStyle, RowExt, hover_links::InlayHighlight, inlays::Inlay, movement::TextLayoutDetails,
+};
+use block_map::{BlockRow, BlockSnapshot};
+use fold_map::FoldSnapshot;
+use inlay_map::InlaySnapshot;
 use tab_map::TabSnapshot;
-use text::{BufferId, LineIndent};
-use ui::{SharedString, px};
-use unicode_segmentation::UnicodeSegmentation;
 use wrap_map::{WrapMap, WrapSnapshot};
 
-pub use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap};
-
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum FoldStatus {
     Folded,

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

@@ -453,6 +453,7 @@ pub struct BlockChunks<'a> {
     input_chunk: Chunk<'a>,
     output_row: BlockRow,
     max_output_row: BlockRow,
+    line_count_overflow: RowDelta,
     masked: bool,
 }
 
@@ -1352,6 +1353,7 @@ impl BlockSnapshot {
             input_chunk: Default::default(),
             transforms: cursor,
             output_row: rows.start,
+            line_count_overflow: RowDelta(0),
             max_output_row,
             masked,
         }
@@ -1743,6 +1745,17 @@ impl<'a> Iterator for BlockChunks<'a> {
             return None;
         }
 
+        if self.line_count_overflow > RowDelta(0) {
+            let lines = self.line_count_overflow.0.min(u128::BITS);
+            self.line_count_overflow.0 -= lines;
+            self.output_row += RowDelta(lines);
+            return Some(Chunk {
+                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines as usize]) },
+                chars: 1u128.unbounded_shl(lines).wrapping_sub(1),
+                ..Default::default()
+            });
+        }
+
         let transform = self.transforms.item()?;
         if transform.block.is_some() {
             let block_start = self.transforms.start().0;
@@ -1754,13 +1767,14 @@ impl<'a> Iterator for BlockChunks<'a> {
 
             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 instead of taking min
-            let line_count = cmp::min(end_in_block - start_in_block, RowDelta(u128::BITS));
-            self.output_row += line_count;
+            let line_count = end_in_block - start_in_block;
+            let lines = RowDelta(line_count.0.min(u128::BITS));
+            self.line_count_overflow = line_count - lines;
+            self.output_row += lines;
 
             return Some(Chunk {
-                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count.0 as usize]) },
-                chars: 1u128.unbounded_shl(line_count.0) - 1,
+                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..lines.0 as usize]) },
+                chars: 1u128.unbounded_shl(lines.0).wrapping_sub(1),
                 ..Default::default()
             });
         }
@@ -3042,7 +3056,7 @@ mod tests {
                                 _ => BlockPlacement::Below(buffer.anchor_after(offset)),
                             };
 
-                            let height = rng.random_range(min_height..5);
+                            let height = rng.random_range(min_height..512);
                             BlockProperties {
                                 style: BlockStyle::Fixed,
                                 placement,

crates/editor/src/hover_popover.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     ActiveDiagnostic, Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings,
     EditorSnapshot, GlobalDiagnosticRenderer, Hover,
-    display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible},
+    display_map::{InlayOffset, ToDisplayPoint, is_invisible},
     hover_links::{InlayHighlight, RangeInEditor},
     movement::TextLayoutDetails,
     scroll::ScrollAmount,