Start work on generalizing the BlockMap to allow arbitrary elements

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/display_map.rs           |   49 
crates/editor/src/display_map/block_map.rs | 1156 +++++++++--------------
crates/editor/src/display_map/wrap_map.rs  |   22 
crates/editor/src/editor.rs                |  126 -
crates/editor/src/element.rs               |  165 +--
crates/gpui/src/elements.rs                |    4 
crates/theme/src/theme.rs                  |   10 
7 files changed, 607 insertions(+), 925 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -3,13 +3,12 @@ mod fold_map;
 mod tab_map;
 mod wrap_map;
 
-pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks};
+pub use block_map::{
+    AlignedBlock, BlockContext, BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks,
+};
 use block_map::{BlockMap, BlockPoint};
 use fold_map::{FoldMap, ToFoldPoint as _};
-use gpui::{
-    fonts::{FontId, HighlightStyle},
-    AppContext, Entity, ModelContext, ModelHandle,
-};
+use gpui::{fonts::FontId, ElementBox, Entity, ModelContext, ModelHandle};
 use language::{Anchor, Buffer, Point, Subscription as BufferSubscription, ToOffset, ToPoint};
 use std::{
     collections::{HashMap, HashSet},
@@ -17,8 +16,7 @@ use std::{
 };
 use sum_tree::Bias;
 use tab_map::TabMap;
-use text::Rope;
-use theme::{BlockStyle, SyntaxTheme};
+use theme::SyntaxTheme;
 use wrap_map::WrapMap;
 
 pub trait ToDisplayPoint {
@@ -124,14 +122,13 @@ impl DisplayMap {
         self.block_map.read(snapshot, edits, cx);
     }
 
-    pub fn insert_blocks<P, T>(
+    pub fn insert_blocks<P>(
         &mut self,
-        blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
+        blocks: impl IntoIterator<Item = BlockProperties<P>>,
         cx: &mut ModelContext<Self>,
     ) -> Vec<BlockId>
     where
         P: ToOffset + Clone,
-        T: Into<Rope> + Clone,
     {
         let snapshot = self.buffer.read(cx).snapshot();
         let edits = self.buffer_subscription.consume().into_inner();
@@ -144,12 +141,11 @@ impl DisplayMap {
         block_map.insert(blocks, cx)
     }
 
-    pub fn restyle_blocks<F1, F2>(&mut self, styles: HashMap<BlockId, (Option<F1>, Option<F2>)>)
+    pub fn replace_blocks<F>(&mut self, styles: HashMap<BlockId, F>)
     where
-        F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>,
-        F2: 'static + Fn(&AppContext) -> BlockStyle,
+        F: 'static + Fn(&BlockContext) -> ElementBox,
     {
-        self.block_map.restyle(styles);
+        self.block_map.replace(styles);
     }
 
     pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
@@ -198,8 +194,8 @@ impl DisplayMapSnapshot {
         self.buffer_snapshot.len() == 0
     }
 
-    pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
-        self.blocks_snapshot.buffer_rows(start_row, cx)
+    pub fn buffer_rows<'a>(&'a self, start_row: u32) -> BufferRows<'a> {
+        self.blocks_snapshot.buffer_rows(start_row)
     }
 
     pub fn buffer_row_count(&self) -> u32 {
@@ -256,7 +252,7 @@ impl DisplayMapSnapshot {
 
     pub fn text_chunks(&self, display_row: u32) -> impl Iterator<Item = &str> {
         self.blocks_snapshot
-            .chunks(display_row..self.max_point().row() + 1, None, None)
+            .chunks(display_row..self.max_point().row() + 1, None)
             .map(|h| h.text)
     }
 
@@ -264,9 +260,8 @@ impl DisplayMapSnapshot {
         &'a self,
         display_rows: Range<u32>,
         theme: Option<&'a SyntaxTheme>,
-        cx: &'a AppContext,
     ) -> block_map::Chunks<'a> {
-        self.blocks_snapshot.chunks(display_rows, theme, Some(cx))
+        self.blocks_snapshot.chunks(display_rows, theme)
     }
 
     pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + 'a {
@@ -322,6 +317,13 @@ impl DisplayMapSnapshot {
         self.folds_snapshot.folds_in_range(range)
     }
 
+    pub fn blocks_in_range<'a>(
+        &'a self,
+        rows: Range<u32>,
+    ) -> impl Iterator<Item = (u32, &'a AlignedBlock)> {
+        self.blocks_snapshot.blocks_in_range(rows)
+    }
+
     pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
         self.folds_snapshot.intersects_fold(offset)
     }
@@ -448,13 +450,6 @@ impl ToDisplayPoint for Anchor {
     }
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum DisplayRow {
-    Buffer(u32),
-    Block(BlockId, Option<BlockStyle>),
-    Wrap,
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -1065,7 +1060,7 @@ mod tests {
     ) -> Vec<(String, Option<Color>)> {
         let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
         let mut chunks: Vec<(String, Option<Color>)> = Vec::new();
-        for chunk in snapshot.chunks(rows, Some(theme), cx) {
+        for chunk in snapshot.chunks(rows, Some(theme)) {
             let color = chunk.highlight_style.map(|s| s.color);
             if let Some((last_chunk, last_color)) = chunks.last_mut() {
                 if color == *last_color {

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

@@ -1,26 +1,23 @@
-use super::{
-    wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint},
-    BlockStyle, DisplayRow,
-};
-use gpui::{fonts::HighlightStyle, AppContext, ModelHandle};
+use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint};
+use gpui::{AppContext, ElementBox, ModelHandle};
 use language::{Buffer, Chunk};
 use parking_lot::Mutex;
 use std::{
     cmp::{self, Ordering},
     collections::{HashMap, HashSet},
     fmt::Debug,
-    iter,
     ops::{Deref, Range},
     sync::{
         atomic::{AtomicUsize, Ordering::SeqCst},
         Arc,
     },
-    vec,
 };
 use sum_tree::SumTree;
-use text::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _};
+use text::{Anchor, Bias, Edit, Point, ToOffset, ToPoint as _};
 use theme::SyntaxTheme;
 
+const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
+
 pub struct BlockMap {
     buffer: ModelHandle<Buffer>,
     next_block_id: AtomicUsize,
@@ -51,25 +48,28 @@ struct WrapRow(u32);
 pub struct Block {
     id: BlockId,
     position: Anchor,
-    text: Rope,
-    build_runs: Mutex<Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>>,
-    build_style: Mutex<Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>>,
+    height: u8,
+    render: Mutex<Arc<dyn Fn(&BlockContext) -> ElementBox>>,
     disposition: BlockDisposition,
 }
 
 #[derive(Clone)]
-pub struct BlockProperties<P, T>
+pub struct BlockProperties<P>
 where
     P: Clone,
-    T: Clone,
 {
     pub position: P,
-    pub text: T,
-    pub build_runs: Option<Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>>,
-    pub build_style: Option<Arc<dyn Fn(&AppContext) -> BlockStyle>>,
+    pub height: u8,
+    pub render: Arc<dyn Fn(&BlockContext) -> ElementBox>,
     pub disposition: BlockDisposition,
 }
 
+pub struct BlockContext<'a> {
+    pub cx: &'a AppContext,
+    pub gutter_width: f32,
+    pub anchor_x: f32,
+}
+
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum BlockDisposition {
     Above,
@@ -83,7 +83,7 @@ struct Transform {
 }
 
 #[derive(Clone, Debug)]
-struct AlignedBlock {
+pub struct AlignedBlock {
     block: Arc<Block>,
     column: u32,
 }
@@ -92,35 +92,20 @@ struct AlignedBlock {
 struct TransformSummary {
     input_rows: u32,
     output_rows: u32,
-    longest_row_in_block: u32,
-    longest_row_in_block_chars: u32,
 }
 
 pub struct Chunks<'a> {
     transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
     input_chunks: wrap_map::Chunks<'a>,
     input_chunk: Chunk<'a>,
-    block_chunks: Option<BlockChunks<'a>>,
     output_row: u32,
     max_output_row: u32,
-    cx: Option<&'a AppContext>,
-}
-
-struct BlockChunks<'a> {
-    chunks: rope::Chunks<'a>,
-    runs: iter::Peekable<vec::IntoIter<(usize, HighlightStyle)>>,
-    chunk: Option<&'a str>,
-    remaining_padding: u32,
-    padding_column: u32,
-    run_start: usize,
-    offset: usize,
 }
 
 pub struct BufferRows<'a> {
     transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>,
     input_buffer_rows: wrap_map::BufferRows<'a>,
     output_row: u32,
-    cx: Option<&'a AppContext>,
     started: bool,
 }
 
@@ -333,19 +318,13 @@ impl BlockMap {
         *transforms = new_transforms;
     }
 
-    pub fn restyle<F1, F2>(&mut self, mut styles: HashMap<BlockId, (Option<F1>, Option<F2>)>)
+    pub fn replace<F>(&mut self, mut element_builders: HashMap<BlockId, F>)
     where
-        F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>,
-        F2: 'static + Fn(&AppContext) -> BlockStyle,
+        F: 'static + Fn(&BlockContext) -> ElementBox,
     {
         for block in &self.blocks {
-            if let Some((build_runs, build_style)) = styles.remove(&block.id) {
-                *block.build_runs.lock() = build_runs.map(|build_runs| {
-                    Arc::new(build_runs) as Arc<dyn Fn(&AppContext) -> Vec<(usize, HighlightStyle)>>
-                });
-                *block.build_style.lock() = build_style.map(|build_style| {
-                    Arc::new(build_style) as Arc<dyn Fn(&AppContext) -> BlockStyle>
-                });
+            if let Some(build_element) = element_builders.remove(&block.id) {
+                *block.render.lock() = Arc::new(build_element);
             }
         }
     }
@@ -393,14 +372,13 @@ impl std::ops::DerefMut for BlockPoint {
 }
 
 impl<'a> BlockMapWriter<'a> {
-    pub fn insert<P, T>(
+    pub fn insert<P>(
         &mut self,
-        blocks: impl IntoIterator<Item = BlockProperties<P, T>>,
+        blocks: impl IntoIterator<Item = BlockProperties<P>>,
         cx: &AppContext,
     ) -> Vec<BlockId>
     where
         P: ToOffset + Clone,
-        T: Into<Rope> + Clone,
     {
         let buffer = self.0.buffer.read(cx);
         let mut ids = Vec::new();
@@ -436,9 +414,8 @@ impl<'a> BlockMapWriter<'a> {
                 Arc::new(Block {
                     id,
                     position,
-                    text: block.text.into(),
-                    build_runs: Mutex::new(block.build_runs),
-                    build_style: Mutex::new(block.build_style),
+                    height: block.height,
+                    render: Mutex::new(block.render),
                     disposition: block.disposition,
                 }),
             );
@@ -495,17 +472,12 @@ impl<'a> BlockMapWriter<'a> {
 impl BlockSnapshot {
     #[cfg(test)]
     fn text(&mut self) -> String {
-        self.chunks(0..self.transforms.summary().output_rows, None, None)
+        self.chunks(0..self.transforms.summary().output_rows, None)
             .map(|chunk| chunk.text)
             .collect()
     }
 
-    pub fn chunks<'a>(
-        &'a self,
-        rows: Range<u32>,
-        theme: Option<&'a SyntaxTheme>,
-        cx: Option<&'a AppContext>,
-    ) -> Chunks<'a> {
+    pub fn chunks<'a>(&'a self, rows: Range<u32>, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> {
         let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
         let input_end = {
@@ -535,15 +507,13 @@ impl BlockSnapshot {
         Chunks {
             input_chunks: self.wrap_snapshot.chunks(input_start..input_end, theme),
             input_chunk: Default::default(),
-            block_chunks: None,
             transforms: cursor,
             output_row: rows.start,
             max_output_row,
-            cx,
         }
     }
 
-    pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> {
+    pub fn buffer_rows<'a>(&'a self, start_row: u32) -> BufferRows<'a> {
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
         cursor.seek(&BlockRow(start_row), Bias::Right, &());
         let (output_start, input_start) = cursor.start();
@@ -554,7 +524,6 @@ impl BlockSnapshot {
         };
         let input_start_row = input_start.0 + overshoot;
         BufferRows {
-            cx,
             transforms: cursor,
             input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row),
             output_row: start_row,
@@ -562,6 +531,29 @@ impl BlockSnapshot {
         }
     }
 
+    pub fn blocks_in_range<'a>(
+        &'a self,
+        rows: Range<u32>,
+    ) -> impl Iterator<Item = (u32, &'a AlignedBlock)> {
+        let mut cursor = self.transforms.cursor::<BlockRow>();
+        cursor.seek(&BlockRow(rows.start), Bias::Right, &());
+        std::iter::from_fn(move || {
+            while let Some(transform) = cursor.item() {
+                let start_row = cursor.start().0;
+                if start_row >= rows.end {
+                    break;
+                }
+                if let Some(block) = &transform.block {
+                    cursor.next(&());
+                    return Some((start_row, block));
+                } else {
+                    cursor.next(&());
+                }
+            }
+            None
+        })
+    }
+
     pub fn max_point(&self) -> BlockPoint {
         let row = self.transforms.summary().output_rows - 1;
         BlockPoint::new(row, self.line_len(row))
@@ -569,18 +561,7 @@ impl BlockSnapshot {
 
     pub fn longest_row(&self) -> u32 {
         let input_row = self.wrap_snapshot.longest_row();
-        let input_row_chars = self.wrap_snapshot.line_char_count(input_row);
-        let TransformSummary {
-            longest_row_in_block: block_row,
-            longest_row_in_block_chars: block_row_chars,
-            ..
-        } = &self.transforms.summary();
-
-        if *block_row_chars > input_row_chars {
-            *block_row
-        } else {
-            self.to_block_point(WrapPoint::new(input_row, 0)).row
-        }
+        self.to_block_point(WrapPoint::new(input_row, 0)).row
     }
 
     pub fn line_len(&self, row: u32) -> u32 {
@@ -589,12 +570,8 @@ impl BlockSnapshot {
         if let Some(transform) = cursor.item() {
             let (output_start, input_start) = cursor.start();
             let overshoot = row - output_start.0;
-            if let Some(block) = &transform.block {
-                let mut len = block.text.line_len(overshoot);
-                if len > 0 {
-                    len += block.column;
-                }
-                len
+            if transform.block.is_some() {
+                0
             } else {
                 self.wrap_snapshot.line_len(input_start.0 + overshoot)
             }
@@ -697,21 +674,16 @@ impl Transform {
             summary: TransformSummary {
                 input_rows: rows,
                 output_rows: rows,
-                longest_row_in_block: 0,
-                longest_row_in_block_chars: 0,
             },
             block: None,
         }
     }
 
     fn block(block: Arc<Block>, column: u32) -> Self {
-        let text_summary = block.text.summary();
         Self {
             summary: TransformSummary {
                 input_rows: 0,
-                output_rows: text_summary.lines.row + 1,
-                longest_row_in_block: text_summary.longest_row,
-                longest_row_in_block_chars: column + text_summary.longest_row_chars,
+                output_rows: block.height as u32,
             },
             block: Some(AlignedBlock { block, column }),
         }
@@ -730,37 +702,20 @@ impl<'a> Iterator for Chunks<'a> {
             return None;
         }
 
-        if let Some(block_chunks) = self.block_chunks.as_mut() {
-            if let Some(block_chunk) = block_chunks.next() {
-                self.output_row += block_chunk.text.matches('\n').count() as u32;
-                return Some(block_chunk);
-            } else {
-                self.block_chunks.take();
-                self.output_row += 1;
-                if self.output_row < self.max_output_row {
-                    return Some(Chunk {
-                        text: "\n",
-                        ..Default::default()
-                    });
-                } else {
-                    return None;
-                }
-            }
-        }
-
         let transform = self.transforms.item()?;
-        if let Some(block) = transform.block.as_ref() {
+        if transform.block.is_some() {
             let block_start = self.transforms.start().0 .0;
             let block_end = self.transforms.end(&()).0 .0;
             let start_in_block = self.output_row - block_start;
             let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
+            let line_count = end_in_block - start_in_block;
+            self.output_row += line_count;
             self.transforms.next(&());
-            self.block_chunks = Some(BlockChunks::new(
-                block,
-                start_in_block..end_in_block,
-                self.cx,
-            ));
-            return self.next();
+            return Some(Chunk {
+                text: unsafe { std::str::from_utf8_unchecked(&NEWLINES[..line_count as usize]) },
+                highlight_style: None,
+                diagnostic: None,
+            });
         }
 
         if self.input_chunk.text.is_empty() {
@@ -797,107 +752,8 @@ impl<'a> Iterator for Chunks<'a> {
     }
 }
 
-impl<'a> BlockChunks<'a> {
-    fn new(block: &'a AlignedBlock, rows: Range<u32>, cx: Option<&'a AppContext>) -> Self {
-        let offset_range = block.text.point_to_offset(Point::new(rows.start, 0))
-            ..block.text.point_to_offset(Point::new(rows.end, 0));
-
-        let mut runs = block
-            .build_runs
-            .lock()
-            .as_ref()
-            .zip(cx)
-            .map(|(build_runs, cx)| build_runs(cx))
-            .unwrap_or_default()
-            .into_iter()
-            .peekable();
-        let mut run_start = 0;
-        while let Some((run_len, _)) = runs.peek() {
-            let run_end = run_start + run_len;
-            if run_end <= offset_range.start {
-                run_start = run_end;
-                runs.next();
-            } else {
-                break;
-            }
-        }
-
-        Self {
-            chunk: None,
-            run_start,
-            padding_column: block.column,
-            remaining_padding: block.column,
-            chunks: block.text.chunks_in_range(offset_range.clone()),
-            runs,
-            offset: offset_range.start,
-        }
-    }
-}
-
-impl<'a> Iterator for BlockChunks<'a> {
-    type Item = Chunk<'a>;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if self.chunk.is_none() {
-            self.chunk = self.chunks.next();
-        }
-
-        let chunk = self.chunk?;
-
-        if chunk.starts_with('\n') {
-            self.remaining_padding = 0;
-        }
-
-        if self.remaining_padding > 0 {
-            const PADDING: &'static str = "                ";
-            let padding_len = self.remaining_padding.min(PADDING.len() as u32);
-            self.remaining_padding -= padding_len;
-            return Some(Chunk {
-                text: &PADDING[..padding_len as usize],
-                ..Default::default()
-            });
-        }
-
-        let mut chunk_len = if let Some(ix) = chunk.find('\n') {
-            ix + 1
-        } else {
-            chunk.len()
-        };
-
-        let mut highlight_style = None;
-        if let Some((run_len, style)) = self.runs.peek() {
-            highlight_style = Some(style.clone());
-            let run_end_in_chunk = self.run_start + run_len - self.offset;
-            if run_end_in_chunk <= chunk_len {
-                chunk_len = run_end_in_chunk;
-                self.run_start += run_len;
-                self.runs.next();
-            }
-        }
-
-        self.offset += chunk_len;
-        let (chunk, suffix) = chunk.split_at(chunk_len);
-
-        if chunk.ends_with('\n') {
-            self.remaining_padding = self.padding_column;
-        }
-
-        self.chunk = if suffix.is_empty() {
-            None
-        } else {
-            Some(suffix)
-        };
-
-        Some(Chunk {
-            text: chunk,
-            highlight_style,
-            diagnostic: None,
-        })
-    }
-}
-
 impl<'a> Iterator for BufferRows<'a> {
-    type Item = DisplayRow;
+    type Item = Option<u32>;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.started {
@@ -911,11 +767,8 @@ impl<'a> Iterator for BufferRows<'a> {
         }
 
         let transform = self.transforms.item()?;
-        if let Some(block) = &transform.block {
-            let style = self
-                .cx
-                .and_then(|cx| block.build_style.lock().as_ref().map(|f| f(cx)));
-            Some(DisplayRow::Block(block.id, style))
+        if transform.block.is_some() {
+            Some(None)
         } else {
             Some(self.input_buffer_rows.next().unwrap())
         }
@@ -934,11 +787,6 @@ impl sum_tree::Summary for TransformSummary {
     type Context = ();
 
     fn add_summary(&mut self, summary: &Self, _: &()) {
-        if summary.longest_row_in_block_chars > self.longest_row_in_block_chars {
-            self.longest_row_in_block_chars = summary.longest_row_in_block_chars;
-            self.longest_row_in_block = self.output_rows + summary.longest_row_in_block;
-        }
-
         self.input_rows += summary.input_rows;
         self.output_rows += summary.output_rows;
     }
@@ -962,6 +810,20 @@ impl BlockDisposition {
     }
 }
 
+impl AlignedBlock {
+    pub fn height(&self) -> u32 {
+        self.height as u32
+    }
+
+    pub fn column(&self) -> u32 {
+        self.column
+    }
+
+    pub fn render(&self, cx: &BlockContext) -> ElementBox {
+        self.render.lock()(cx)
+    }
+}
+
 impl Deref for AlignedBlock {
     type Target = Block;
 
@@ -975,7 +837,6 @@ impl Debug for Block {
         f.debug_struct("Block")
             .field("id", &self.id)
             .field("position", &self.position)
-            .field("text", &self.text)
             .field("disposition", &self.disposition)
             .finish()
     }
@@ -1003,7 +864,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
 mod tests {
     use super::*;
     use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
-    use gpui::color::Color;
+    use gpui::{elements::Empty, Element};
     use language::Buffer;
     use rand::prelude::*;
     use std::env;
@@ -1023,76 +884,6 @@ mod tests {
         assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11));
     }
 
-    #[gpui::test]
-    fn test_block_chunks(cx: &mut gpui::MutableAppContext) {
-        let red = Color::red();
-        let blue = Color::blue();
-        let clear = Color::default();
-
-        let block = AlignedBlock {
-            column: 5,
-            block: Arc::new(Block {
-                id: BlockId(0),
-                position: Anchor::min(),
-                text: "one!\ntwo three\nfour".into(),
-                build_style: Mutex::new(None),
-                build_runs: Mutex::new(Some(Arc::new(move |_| {
-                    vec![(3, red.into()), (6, Default::default()), (5, blue.into())]
-                }))),
-                disposition: BlockDisposition::Above,
-            }),
-        };
-
-        assert_eq!(
-            colored_chunks(&block, 0..3, cx),
-            &[
-                ("     ", clear),
-                ("one", red),
-                ("!\n", clear),
-                ("     ", clear),
-                ("two ", clear),
-                ("three", blue),
-                ("\n", clear),
-                ("     ", clear),
-                ("four", clear)
-            ]
-        );
-        assert_eq!(
-            colored_chunks(&block, 0..1, cx),
-            &[
-                ("     ", clear), //
-                ("one", red),
-                ("!\n", clear),
-            ]
-        );
-        assert_eq!(
-            colored_chunks(&block, 1..3, cx),
-            &[
-                ("     ", clear),
-                ("two ", clear),
-                ("three", blue),
-                ("\n", clear),
-                ("     ", clear),
-                ("four", clear)
-            ]
-        );
-
-        fn colored_chunks<'a>(
-            block: &'a AlignedBlock,
-            row_range: Range<u32>,
-            cx: &'a AppContext,
-        ) -> Vec<(&'a str, Color)> {
-            BlockChunks::new(block, row_range, Some(cx))
-                .map(|c| {
-                    (
-                        c.text,
-                        c.highlight_style.map_or(Color::default(), |s| s.color),
-                    )
-                })
-                .collect()
-        }
-    }
-
     #[gpui::test]
     fn test_basic_blocks(cx: &mut gpui::MutableAppContext) {
         let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
@@ -1114,34 +905,56 @@ mod tests {
             vec![
                 BlockProperties {
                     position: Point::new(1, 0),
-                    text: "BLOCK 1",
+                    height: 1,
                     disposition: BlockDisposition::Above,
-                    build_runs: None,
-                    build_style: None,
+                    render: Arc::new(|_| Empty::new().named("block 1")),
                 },
                 BlockProperties {
                     position: Point::new(1, 2),
-                    text: "BLOCK 2",
+                    height: 2,
                     disposition: BlockDisposition::Above,
-                    build_runs: None,
-                    build_style: None,
+                    render: Arc::new(|_| Empty::new().named("block 2")),
                 },
                 BlockProperties {
-                    position: Point::new(3, 2),
-                    text: "BLOCK 3",
+                    position: Point::new(3, 3),
+                    height: 3,
                     disposition: BlockDisposition::Below,
-                    build_runs: None,
-                    build_style: None,
+                    render: Arc::new(|_| Empty::new().named("block 3")),
                 },
             ],
             cx,
         );
 
         let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
+        assert_eq!(snapshot.text(), "aaa\n\n\n\nbbb\nccc\nddd\n\n\n\n");
+
+        let blocks = snapshot
+            .blocks_in_range(0..8)
+            .map(|(start_row, block)| {
+                (
+                    start_row..start_row + block.height(),
+                    block.column(),
+                    block
+                        .render(&BlockContext {
+                            cx,
+                            gutter_width: 0.,
+                            anchor_x: 0.,
+                        })
+                        .name()
+                        .unwrap()
+                        .to_string(),
+                )
+            })
+            .collect::<Vec<_>>();
         assert_eq!(
-            snapshot.text(),
-            "aaa\nBLOCK 1\n  BLOCK 2\nbbb\nccc\nddd\n  BLOCK 3"
+            blocks,
+            &[
+                (1..2, 0, "block 1".to_string()),
+                (2..4, 2, "block 2".to_string()),
+                (7..10, 3, "block 3".to_string()),
+            ]
         );
+
         assert_eq!(
             snapshot.to_block_point(WrapPoint::new(0, 3)),
             BlockPoint::new(0, 3)
@@ -1214,16 +1027,8 @@ mod tests {
         );
 
         assert_eq!(
-            snapshot.buffer_rows(0, None).collect::<Vec<_>>(),
-            &[
-                DisplayRow::Buffer(0),
-                DisplayRow::Block(block_ids[0], None),
-                DisplayRow::Block(block_ids[1], None),
-                DisplayRow::Buffer(1),
-                DisplayRow::Buffer(2),
-                DisplayRow::Buffer(3),
-                DisplayRow::Block(block_ids[2], None)
-            ]
+            snapshot.buffer_rows(0).collect::<Vec<_>>(),
+            &[Some(0), None, None, Some(1), Some(2), Some(3), None]
         );
 
         // Insert a line break, separating two block decorations into separate
@@ -1240,371 +1045,368 @@ mod tests {
             wrap_map.sync(tabs_snapshot, tab_edits, cx)
         });
         let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx);
-        assert_eq!(
-            snapshot.text(),
-            "aaa\nBLOCK 1\nb!!!\n BLOCK 2\nbb\nccc\nddd\n  BLOCK 3"
-        );
+        assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
     }
 
-    #[gpui::test]
-    fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) {
-        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
-        let font_id = cx
-            .font_cache()
-            .select_font(family_id, &Default::default())
-            .unwrap();
-
-        let text = "one two three\nfour five six\nseven eight";
-
-        let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
-        let (_, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot());
-        let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
-        let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx);
-        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
-
-        let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
-        writer.insert(
-            vec![
-                BlockProperties {
-                    position: Point::new(1, 12),
-                    text: "<BLOCK 1",
-                    disposition: BlockDisposition::Above,
-                    build_runs: None,
-                    build_style: None,
-                },
-                BlockProperties {
-                    position: Point::new(1, 1),
-                    text: ">BLOCK 2",
-                    disposition: BlockDisposition::Below,
-                    build_runs: None,
-                    build_style: None,
-                },
-            ],
-            cx,
-        );
-
-        // Blocks with an 'above' disposition go above their corresponding buffer line.
-        // Blocks with a 'below' disposition go below their corresponding buffer line.
-        let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
-        assert_eq!(
-            snapshot.text(),
-            "one two \nthree\n  <BLOCK 1\nfour five \nsix\n >BLOCK 2\nseven \neight"
-        );
-    }
-
-    #[gpui::test(iterations = 100)]
-    fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
-        let operations = env::var("OPERATIONS")
-            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
-            .unwrap_or(10);
-
-        let wrap_width = if rng.gen_bool(0.2) {
-            None
-        } else {
-            Some(rng.gen_range(0.0..=100.0))
-        };
-        let tab_size = 1;
-        let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
-        let font_id = cx
-            .font_cache()
-            .select_font(family_id, &Default::default())
-            .unwrap();
-        let font_size = 14.0;
-
-        log::info!("Wrap width: {:?}", wrap_width);
-
-        let buffer = cx.add_model(|cx| {
-            let len = rng.gen_range(0..10);
-            let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
-            log::info!("initial buffer text: {:?}", text);
-            Buffer::new(0, text, cx)
-        });
-        let mut buffer_snapshot = buffer.read(cx).snapshot();
-        let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
-        let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
-        let (wrap_map, wraps_snapshot) =
-            WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx);
-        let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot);
-        let mut expected_blocks = Vec::new();
-
-        for _ in 0..operations {
-            let mut buffer_edits = Vec::new();
-            match rng.gen_range(0..=100) {
-                0..=19 => {
-                    let wrap_width = if rng.gen_bool(0.2) {
-                        None
-                    } else {
-                        Some(rng.gen_range(0.0..=100.0))
-                    };
-                    log::info!("Setting wrap width to {:?}", wrap_width);
-                    wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
-                }
-                20..=39 => {
-                    let block_count = rng.gen_range(1..=1);
-                    let block_properties = (0..block_count)
-                        .map(|_| {
-                            let buffer = buffer.read(cx);
-                            let position = buffer.anchor_after(
-                                buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
-                            );
-
-                            let len = rng.gen_range(0..10);
-                            let mut text = Rope::from(
-                                RandomCharIter::new(&mut rng)
-                                    .take(len)
-                                    .collect::<String>()
-                                    .to_uppercase()
-                                    .as_str(),
-                            );
-                            let disposition = if rng.gen() {
-                                text.push_front("<");
-                                BlockDisposition::Above
-                            } else {
-                                text.push_front(">");
-                                BlockDisposition::Below
-                            };
-                            log::info!(
-                                "inserting block {:?} {:?} with text {:?}",
-                                disposition,
-                                position.to_point(buffer),
-                                text.to_string()
-                            );
-                            BlockProperties {
-                                position,
-                                text,
-                                disposition,
-                                build_runs: None,
-                                build_style: None,
-                            }
-                        })
-                        .collect::<Vec<_>>();
-
-                    let (folds_snapshot, fold_edits) =
-                        fold_map.read(buffer_snapshot.clone(), vec![]);
-                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
-                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
-                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
-                    });
-                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
-                    let block_ids = block_map.insert(block_properties.clone(), cx);
-                    for (block_id, props) in block_ids.into_iter().zip(block_properties) {
-                        expected_blocks.push((block_id, props));
-                    }
-                }
-                40..=59 if !expected_blocks.is_empty() => {
-                    let block_count = rng.gen_range(1..=4.min(expected_blocks.len()));
-                    let block_ids_to_remove = (0..block_count)
-                        .map(|_| {
-                            expected_blocks
-                                .remove(rng.gen_range(0..expected_blocks.len()))
-                                .0
-                        })
-                        .collect();
-
-                    let (folds_snapshot, fold_edits) =
-                        fold_map.read(buffer_snapshot.clone(), vec![]);
-                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
-                    let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
-                        wrap_map.sync(tabs_snapshot, tab_edits, cx)
-                    });
-                    let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
-                    block_map.remove(block_ids_to_remove, cx);
-                }
-                _ => {
-                    buffer.update(cx, |buffer, cx| {
-                        let v0 = buffer.version();
-                        let edit_count = rng.gen_range(1..=5);
-                        buffer.randomly_edit(&mut rng, edit_count, cx);
-                        log::info!("buffer text: {:?}", buffer.text());
-                        buffer_edits.extend(buffer.edits_since(&v0));
-                        buffer_snapshot = buffer.snapshot();
-                    });
-                }
-            }
-
-            let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits);
-            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
-            let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
-                wrap_map.sync(tabs_snapshot, tab_edits, cx)
-            });
-            let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx);
-            assert_eq!(
-                blocks_snapshot.transforms.summary().input_rows,
-                wraps_snapshot.max_point().row() + 1
-            );
-            log::info!("blocks text: {:?}", blocks_snapshot.text());
-
-            let buffer = buffer.read(cx);
-            let mut sorted_blocks = expected_blocks
-                .iter()
-                .cloned()
-                .map(|(id, block)| {
-                    let mut position = block.position.to_point(buffer);
-                    let column = wraps_snapshot.from_point(position, Bias::Left).column();
-                    match block.disposition {
-                        BlockDisposition::Above => {
-                            position.column = 0;
-                        }
-                        BlockDisposition::Below => {
-                            position.column = buffer.line_len(position.row);
-                        }
-                    };
-                    let row = wraps_snapshot.from_point(position, Bias::Left).row();
-                    (
-                        id,
-                        BlockProperties {
-                            position: BlockPoint::new(row, column),
-                            text: block.text,
-                            build_runs: block.build_runs.clone(),
-                            build_style: None,
-                            disposition: block.disposition,
-                        },
-                    )
-                })
-                .collect::<Vec<_>>();
-            sorted_blocks
-                .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id));
-            let mut sorted_blocks = sorted_blocks.into_iter().peekable();
-
-            let mut expected_buffer_rows = Vec::new();
-            let mut expected_text = String::new();
-            let input_text = wraps_snapshot.text();
-            for (row, input_line) in input_text.split('\n').enumerate() {
-                let row = row as u32;
-                if row > 0 {
-                    expected_text.push('\n');
-                }
-
-                let buffer_row = wraps_snapshot
-                    .to_point(WrapPoint::new(row, 0), Bias::Left)
-                    .row;
-
-                while let Some((block_id, block)) = sorted_blocks.peek() {
-                    if block.position.row == row && block.disposition == BlockDisposition::Above {
-                        let text = block.text.to_string();
-                        let padding = " ".repeat(block.position.column as usize);
-                        for line in text.split('\n') {
-                            if !line.is_empty() {
-                                expected_text.push_str(&padding);
-                                expected_text.push_str(line);
-                            }
-                            expected_text.push('\n');
-                            expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
-                        }
-                        sorted_blocks.next();
-                    } else {
-                        break;
-                    }
-                }
-
-                let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
-                expected_buffer_rows.push(if soft_wrapped {
-                    DisplayRow::Wrap
-                } else {
-                    DisplayRow::Buffer(buffer_row)
-                });
-                expected_text.push_str(input_line);
-
-                while let Some((block_id, block)) = sorted_blocks.peek() {
-                    if block.position.row == row && block.disposition == BlockDisposition::Below {
-                        let text = block.text.to_string();
-                        let padding = " ".repeat(block.position.column as usize);
-                        for line in text.split('\n') {
-                            expected_text.push('\n');
-                            if !line.is_empty() {
-                                expected_text.push_str(&padding);
-                                expected_text.push_str(line);
-                            }
-                            expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
-                        }
-                        sorted_blocks.next();
-                    } else {
-                        break;
-                    }
-                }
-            }
-
-            let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
-            let expected_row_count = expected_lines.len();
-            for start_row in 0..expected_row_count {
-                let expected_text = expected_lines[start_row..].join("\n");
-                let actual_text = blocks_snapshot
-                    .chunks(start_row as u32..expected_row_count as u32, None, None)
-                    .map(|chunk| chunk.text)
-                    .collect::<String>();
-                assert_eq!(
-                    actual_text, expected_text,
-                    "incorrect text starting from row {}",
-                    start_row
-                );
-                assert_eq!(
-                    blocks_snapshot
-                        .buffer_rows(start_row as u32, None)
-                        .collect::<Vec<_>>(),
-                    &expected_buffer_rows[start_row..]
-                );
-            }
-
-            let mut expected_longest_rows = Vec::new();
-            let mut longest_line_len = -1_isize;
-            for (row, line) in expected_lines.iter().enumerate() {
-                let row = row as u32;
-
-                assert_eq!(
-                    blocks_snapshot.line_len(row),
-                    line.len() as u32,
-                    "invalid line len for row {}",
-                    row
-                );
-
-                let line_char_count = line.chars().count() as isize;
-                match line_char_count.cmp(&longest_line_len) {
-                    Ordering::Less => {}
-                    Ordering::Equal => expected_longest_rows.push(row),
-                    Ordering::Greater => {
-                        longest_line_len = line_char_count;
-                        expected_longest_rows.clear();
-                        expected_longest_rows.push(row);
-                    }
-                }
-            }
-
-            log::info!("getting longest row >>>>>>>>>>>>>>>>>>>>>>>>");
-            let longest_row = blocks_snapshot.longest_row();
-            assert!(
-                expected_longest_rows.contains(&longest_row),
-                "incorrect longest row {}. expected {:?} with length {}",
-                longest_row,
-                expected_longest_rows,
-                longest_line_len,
-            );
-
-            for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
-                let wrap_point = WrapPoint::new(row, 0);
-                let block_point = blocks_snapshot.to_block_point(wrap_point);
-                assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
-            }
-
-            let mut block_point = BlockPoint::new(0, 0);
-            for c in expected_text.chars() {
-                let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
-                let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
-
-                assert_eq!(
-                    blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
-                    left_point
-                );
-                assert_eq!(
-                    blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
-                    right_point
-                );
-
-                if c == '\n' {
-                    block_point.0 += Point::new(1, 0);
-                } else {
-                    block_point.column += c.len_utf8() as u32;
-                }
-            }
-        }
-    }
+    // #[gpui::test]
+    // fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) {
+    //     let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
+    //     let font_id = cx
+    //         .font_cache()
+    //         .select_font(family_id, &Default::default())
+    //         .unwrap();
+
+    //     let text = "one two three\nfour five six\nseven eight";
+
+    //     let buffer = cx.add_model(|cx| Buffer::new(0, text, cx));
+    //     let (_, folds_snapshot) = FoldMap::new(buffer.read(cx).snapshot());
+    //     let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
+    //     let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx);
+    //     let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
+
+    //     let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
+    //     writer.insert(
+    //         vec![
+    //             BlockProperties {
+    //                 position: Point::new(1, 12),
+    //                 text: "<BLOCK 1",
+    //                 disposition: BlockDisposition::Above,
+    //                 build_runs: None,
+    //                 build_style: None,
+    //             },
+    //             BlockProperties {
+    //                 position: Point::new(1, 1),
+    //                 text: ">BLOCK 2",
+    //                 disposition: BlockDisposition::Below,
+    //                 build_runs: None,
+    //                 build_style: None,
+    //             },
+    //         ],
+    //         cx,
+    //     );
+
+    //     // Blocks with an 'above' disposition go above their corresponding buffer line.
+    //     // Blocks with a 'below' disposition go below their corresponding buffer line.
+    //     let mut snapshot = block_map.read(wraps_snapshot, vec![], cx);
+    //     assert_eq!(
+    //         snapshot.text(),
+    //         "one two \nthree\n  <BLOCK 1\nfour five \nsix\n >BLOCK 2\nseven \neight"
+    //     );
+    // }
+
+    // #[gpui::test(iterations = 100)]
+    // fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
+    //     let operations = env::var("OPERATIONS")
+    //         .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+    //         .unwrap_or(10);
+
+    //     let wrap_width = if rng.gen_bool(0.2) {
+    //         None
+    //     } else {
+    //         Some(rng.gen_range(0.0..=100.0))
+    //     };
+    //     let tab_size = 1;
+    //     let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
+    //     let font_id = cx
+    //         .font_cache()
+    //         .select_font(family_id, &Default::default())
+    //         .unwrap();
+    //     let font_size = 14.0;
+
+    //     log::info!("Wrap width: {:?}", wrap_width);
+
+    //     let buffer = cx.add_model(|cx| {
+    //         let len = rng.gen_range(0..10);
+    //         let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
+    //         log::info!("initial buffer text: {:?}", text);
+    //         Buffer::new(0, text, cx)
+    //     });
+    //     let mut buffer_snapshot = buffer.read(cx).snapshot();
+    //     let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
+    //     let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
+    //     let (wrap_map, wraps_snapshot) =
+    //         WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx);
+    //     let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot);
+    //     let mut expected_blocks = Vec::new();
+
+    //     for _ in 0..operations {
+    //         let mut buffer_edits = Vec::new();
+    //         match rng.gen_range(0..=100) {
+    //             0..=19 => {
+    //                 let wrap_width = if rng.gen_bool(0.2) {
+    //                     None
+    //                 } else {
+    //                     Some(rng.gen_range(0.0..=100.0))
+    //                 };
+    //                 log::info!("Setting wrap width to {:?}", wrap_width);
+    //                 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
+    //             }
+    //             20..=39 => {
+    //                 let block_count = rng.gen_range(1..=1);
+    //                 let block_properties = (0..block_count)
+    //                     .map(|_| {
+    //                         let buffer = buffer.read(cx);
+    //                         let position = buffer.anchor_after(
+    //                             buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left),
+    //                         );
+
+    //                         let len = rng.gen_range(0..10);
+    //                         let mut text = Rope::from(
+    //                             RandomCharIter::new(&mut rng)
+    //                                 .take(len)
+    //                                 .collect::<String>()
+    //                                 .to_uppercase()
+    //                                 .as_str(),
+    //                         );
+    //                         let disposition = if rng.gen() {
+    //                             text.push_front("<");
+    //                             BlockDisposition::Above
+    //                         } else {
+    //                             text.push_front(">");
+    //                             BlockDisposition::Below
+    //                         };
+    //                         log::info!(
+    //                             "inserting block {:?} {:?} with text {:?}",
+    //                             disposition,
+    //                             position.to_point(buffer),
+    //                             text.to_string()
+    //                         );
+    //                         BlockProperties {
+    //                             position,
+    //                             text,
+    //                             disposition,
+    //                             build_runs: None,
+    //                             build_style: None,
+    //                         }
+    //                     })
+    //                     .collect::<Vec<_>>();
+
+    //                 let (folds_snapshot, fold_edits) =
+    //                     fold_map.read(buffer_snapshot.clone(), vec![]);
+    //                 let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+    //                 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
+    //                     wrap_map.sync(tabs_snapshot, tab_edits, cx)
+    //                 });
+    //                 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
+    //                 let block_ids = block_map.insert(block_properties.clone(), cx);
+    //                 for (block_id, props) in block_ids.into_iter().zip(block_properties) {
+    //                     expected_blocks.push((block_id, props));
+    //                 }
+    //             }
+    //             40..=59 if !expected_blocks.is_empty() => {
+    //                 let block_count = rng.gen_range(1..=4.min(expected_blocks.len()));
+    //                 let block_ids_to_remove = (0..block_count)
+    //                     .map(|_| {
+    //                         expected_blocks
+    //                             .remove(rng.gen_range(0..expected_blocks.len()))
+    //                             .0
+    //                     })
+    //                     .collect();
+
+    //                 let (folds_snapshot, fold_edits) =
+    //                     fold_map.read(buffer_snapshot.clone(), vec![]);
+    //                 let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+    //                 let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
+    //                     wrap_map.sync(tabs_snapshot, tab_edits, cx)
+    //                 });
+    //                 let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx);
+    //                 block_map.remove(block_ids_to_remove, cx);
+    //             }
+    //             _ => {
+    //                 buffer.update(cx, |buffer, cx| {
+    //                     let v0 = buffer.version();
+    //                     let edit_count = rng.gen_range(1..=5);
+    //                     buffer.randomly_edit(&mut rng, edit_count, cx);
+    //                     log::info!("buffer text: {:?}", buffer.text());
+    //                     buffer_edits.extend(buffer.edits_since(&v0));
+    //                     buffer_snapshot = buffer.snapshot();
+    //                 });
+    //             }
+    //         }
+
+    //         let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits);
+    //         let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+    //         let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
+    //             wrap_map.sync(tabs_snapshot, tab_edits, cx)
+    //         });
+    //         let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx);
+    //         assert_eq!(
+    //             blocks_snapshot.transforms.summary().input_rows,
+    //             wraps_snapshot.max_point().row() + 1
+    //         );
+    //         log::info!("blocks text: {:?}", blocks_snapshot.text());
+
+    //         let buffer = buffer.read(cx);
+    //         let mut sorted_blocks = expected_blocks
+    //             .iter()
+    //             .cloned()
+    //             .map(|(id, block)| {
+    //                 let mut position = block.position.to_point(buffer);
+    //                 let column = wraps_snapshot.from_point(position, Bias::Left).column();
+    //                 match block.disposition {
+    //                     BlockDisposition::Above => {
+    //                         position.column = 0;
+    //                     }
+    //                     BlockDisposition::Below => {
+    //                         position.column = buffer.line_len(position.row);
+    //                     }
+    //                 };
+    //                 let row = wraps_snapshot.from_point(position, Bias::Left).row();
+    //                 (
+    //                     id,
+    //                     BlockProperties {
+    //                         position: BlockPoint::new(row, column),
+    //                         text: block.text,
+    //                         build_runs: block.build_runs.clone(),
+    //                         build_style: None,
+    //                         disposition: block.disposition,
+    //                     },
+    //                 )
+    //             })
+    //             .collect::<Vec<_>>();
+    //         sorted_blocks
+    //             .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id));
+    //         let mut sorted_blocks = sorted_blocks.into_iter().peekable();
+
+    //         let mut expected_buffer_rows = Vec::new();
+    //         let mut expected_text = String::new();
+    //         let input_text = wraps_snapshot.text();
+    //         for (row, input_line) in input_text.split('\n').enumerate() {
+    //             let row = row as u32;
+    //             if row > 0 {
+    //                 expected_text.push('\n');
+    //             }
+
+    //             let buffer_row = wraps_snapshot
+    //                 .to_point(WrapPoint::new(row, 0), Bias::Left)
+    //                 .row;
+
+    //             while let Some((block_id, block)) = sorted_blocks.peek() {
+    //                 if block.position.row == row && block.disposition == BlockDisposition::Above {
+    //                     let text = block.text.to_string();
+    //                     let padding = " ".repeat(block.position.column as usize);
+    //                     for line in text.split('\n') {
+    //                         if !line.is_empty() {
+    //                             expected_text.push_str(&padding);
+    //                             expected_text.push_str(line);
+    //                         }
+    //                         expected_text.push('\n');
+    //                         expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
+    //                     }
+    //                     sorted_blocks.next();
+    //                 } else {
+    //                     break;
+    //                 }
+    //             }
+
+    //             let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
+    //             expected_buffer_rows.push(if soft_wrapped {
+    //                 DisplayRow::Wrap
+    //             } else {
+    //                 DisplayRow::Buffer(buffer_row)
+    //             });
+    //             expected_text.push_str(input_line);
+
+    //             while let Some((block_id, block)) = sorted_blocks.peek() {
+    //                 if block.position.row == row && block.disposition == BlockDisposition::Below {
+    //                     let text = block.text.to_string();
+    //                     let padding = " ".repeat(block.position.column as usize);
+    //                     for line in text.split('\n') {
+    //                         expected_text.push('\n');
+    //                         if !line.is_empty() {
+    //                             expected_text.push_str(&padding);
+    //                             expected_text.push_str(line);
+    //                         }
+    //                         expected_buffer_rows.push(DisplayRow::Block(*block_id, None));
+    //                     }
+    //                     sorted_blocks.next();
+    //                 } else {
+    //                     break;
+    //                 }
+    //             }
+    //         }
+
+    //         let expected_lines = expected_text.split('\n').collect::<Vec<_>>();
+    //         let expected_row_count = expected_lines.len();
+    //         for start_row in 0..expected_row_count {
+    //             let expected_text = expected_lines[start_row..].join("\n");
+    //             let actual_text = blocks_snapshot
+    //                 .chunks(start_row as u32..expected_row_count as u32, None, None)
+    //                 .map(|chunk| chunk.text)
+    //                 .collect::<String>();
+    //             assert_eq!(
+    //                 actual_text, expected_text,
+    //                 "incorrect text starting from row {}",
+    //                 start_row
+    //             );
+    //             assert_eq!(
+    //                 blocks_snapshot
+    //                     .buffer_rows(start_row as u32, None)
+    //                     .collect::<Vec<_>>(),
+    //                 &expected_buffer_rows[start_row..]
+    //             );
+    //         }
+
+    //         let mut expected_longest_rows = Vec::new();
+    //         let mut longest_line_len = -1_isize;
+    //         for (row, line) in expected_lines.iter().enumerate() {
+    //             let row = row as u32;
+
+    //             assert_eq!(
+    //                 blocks_snapshot.line_len(row),
+    //                 line.len() as u32,
+    //                 "invalid line len for row {}",
+    //                 row
+    //             );
+
+    //             let line_char_count = line.chars().count() as isize;
+    //             match line_char_count.cmp(&longest_line_len) {
+    //                 Ordering::Less => {}
+    //                 Ordering::Equal => expected_longest_rows.push(row),
+    //                 Ordering::Greater => {
+    //                     longest_line_len = line_char_count;
+    //                     expected_longest_rows.clear();
+    //                     expected_longest_rows.push(row);
+    //                 }
+    //             }
+    //         }
+
+    //         log::info!("getting longest row >>>>>>>>>>>>>>>>>>>>>>>>");
+    //         let longest_row = blocks_snapshot.longest_row();
+    //         assert!(
+    //             expected_longest_rows.contains(&longest_row),
+    //             "incorrect longest row {}. expected {:?} with length {}",
+    //             longest_row,
+    //             expected_longest_rows,
+    //             longest_line_len,
+    //         );
+
+    //         for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() {
+    //             let wrap_point = WrapPoint::new(row, 0);
+    //             let block_point = blocks_snapshot.to_block_point(wrap_point);
+    //             assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point);
+    //         }
+
+    //         let mut block_point = BlockPoint::new(0, 0);
+    //         for c in expected_text.chars() {
+    //             let left_point = blocks_snapshot.clip_point(block_point, Bias::Left);
+    //             let right_point = blocks_snapshot.clip_point(block_point, Bias::Right);
+
+    //             assert_eq!(
+    //                 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)),
+    //                 left_point
+    //             );
+    //             assert_eq!(
+    //                 blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)),
+    //                 right_point
+    //             );
+
+    //             if c == '\n' {
+    //                 block_point.0 += Point::new(1, 0);
+    //             } else {
+    //                 block_point.column += c.len_utf8() as u32;
+    //             }
+    //         }
+    //     }
+    // }
 }

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

@@ -1,7 +1,6 @@
 use super::{
     fold_map,
     tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint},
-    DisplayRow,
 };
 use gpui::{
     fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext,
@@ -607,13 +606,6 @@ impl Snapshot {
         len as u32
     }
 
-    pub fn line_char_count(&self, row: u32) -> u32 {
-        self.text_chunks(row)
-            .flat_map(|c| c.chars())
-            .take_while(|c| *c != '\n')
-            .count() as u32
-    }
-
     pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
         let mut cursor = self.transforms.cursor::<WrapPoint>();
         cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
@@ -719,11 +711,7 @@ impl Snapshot {
                     prev_tab_row = tab_point.row();
                     soft_wrapped = false;
                 }
-                expected_buffer_rows.push(if soft_wrapped {
-                    DisplayRow::Wrap
-                } else {
-                    DisplayRow::Buffer(buffer_row)
-                });
+                expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
             }
 
             for start_display_row in 0..expected_buffer_rows.len() {
@@ -803,7 +791,7 @@ impl<'a> Iterator for Chunks<'a> {
 }
 
 impl<'a> Iterator for BufferRows<'a> {
-    type Item = DisplayRow;
+    type Item = Option<u32>;
 
     fn next(&mut self) -> Option<Self::Item> {
         if self.output_row > self.max_output_row {
@@ -823,11 +811,7 @@ impl<'a> Iterator for BufferRows<'a> {
             self.soft_wrapped = true;
         }
 
-        Some(if soft_wrapped {
-            DisplayRow::Wrap
-        } else {
-            DisplayRow::Buffer(buffer_row)
-        })
+        Some(if soft_wrapped { None } else { Some(buffer_row) })
     }
 }
 

crates/editor/src/editor.rs 🔗

@@ -8,11 +8,12 @@ mod test;
 
 use aho_corasick::AhoCorasick;
 use clock::ReplicaId;
+pub use display_map::DisplayPoint;
 use display_map::*;
-pub use display_map::{DisplayPoint, DisplayRow};
 pub use element::*;
 use gpui::{
     action,
+    elements::Text,
     geometry::vector::{vec2f, Vector2F},
     keymap::Binding,
     text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
@@ -28,14 +29,14 @@ use std::{
     cmp,
     collections::HashMap,
     iter, mem,
-    ops::{Range, RangeInclusive},
+    ops::{Deref, Range, RangeInclusive},
     rc::Rc,
     sync::Arc,
     time::Duration,
 };
 use sum_tree::Bias;
 use text::rope::TextDimension;
-use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme};
+use theme::{DiagnosticStyle, EditorStyle};
 use util::post_inc;
 use workspace::{EntryOpener, Workspace};
 
@@ -2877,35 +2878,16 @@ impl Editor {
                 active_diagnostics.is_valid = is_valid;
                 let mut new_styles = HashMap::new();
                 for (block_id, diagnostic) in &active_diagnostics.blocks {
-                    let severity = diagnostic.severity;
-                    let message_len = diagnostic.message.len();
-                    new_styles.insert(
-                        *block_id,
-                        (
-                            Some({
-                                let build_settings = self.build_settings.clone();
-                                move |cx: &AppContext| {
-                                    let settings = build_settings.borrow()(cx);
-                                    vec![(
-                                        message_len,
-                                        diagnostic_style(severity, is_valid, &settings.style)
-                                            .text
-                                            .into(),
-                                    )]
-                                }
-                            }),
-                            Some({
-                                let build_settings = self.build_settings.clone();
-                                move |cx: &AppContext| {
-                                    let settings = build_settings.borrow()(cx);
-                                    diagnostic_style(severity, is_valid, &settings.style).block
-                                }
-                            }),
-                        ),
-                    );
+                    let build_settings = self.build_settings.clone();
+                    let diagnostic = diagnostic.clone();
+                    new_styles.insert(*block_id, move |cx: &BlockContext| {
+                        let diagnostic = diagnostic.clone();
+                        let settings = build_settings.borrow()(cx.cx);
+                        render_diagnostic(diagnostic, &settings.style)
+                    });
                 }
                 self.display_map
-                    .update(cx, |display_map, _| display_map.restyle_blocks(new_styles));
+                    .update(cx, |display_map, _| display_map.replace_blocks(new_styles));
             }
         }
     }
@@ -2940,30 +2922,17 @@ impl Editor {
                 .insert_blocks(
                     diagnostic_group.iter().map(|(range, diagnostic)| {
                         let build_settings = self.build_settings.clone();
-                        let message_len = diagnostic.message.len();
-                        let severity = diagnostic.severity;
+                        let diagnostic = diagnostic.clone();
+                        let message_height = diagnostic.message.lines().count() as u8;
+
                         BlockProperties {
                             position: range.start,
-                            text: diagnostic.message.as_str(),
-                            build_runs: Some(Arc::new({
-                                let build_settings = build_settings.clone();
-                                move |cx| {
-                                    let settings = build_settings.borrow()(cx);
-                                    vec![(
-                                        message_len,
-                                        diagnostic_style(severity, true, &settings.style)
-                                            .text
-                                            .into(),
-                                    )]
-                                }
-                            })),
-                            build_style: Some(Arc::new({
-                                let build_settings = build_settings.clone();
-                                move |cx| {
-                                    let settings = build_settings.borrow()(cx);
-                                    diagnostic_style(severity, true, &settings.style).block
-                                }
-                            })),
+                            height: message_height,
+                            render: Arc::new(move |cx| {
+                                let settings = build_settings.borrow()(cx.cx);
+                                let diagnostic = diagnostic.clone();
+                                render_diagnostic(diagnostic, &settings.style)
+                            }),
                             disposition: BlockDisposition::Below,
                         }
                     }),
@@ -3482,10 +3451,6 @@ impl Editor {
 }
 
 impl Snapshot {
-    pub fn is_empty(&self) -> bool {
-        self.display_snapshot.is_empty()
-    }
-
     pub fn is_focused(&self) -> bool {
         self.is_focused
     }
@@ -3494,23 +3459,6 @@ impl Snapshot {
         self.placeholder_text.as_ref()
     }
 
-    pub fn buffer_row_count(&self) -> u32 {
-        self.display_snapshot.buffer_row_count()
-    }
-
-    pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> {
-        self.display_snapshot.buffer_rows(start_row, Some(cx))
-    }
-
-    pub fn chunks<'a>(
-        &'a self,
-        display_rows: Range<u32>,
-        theme: Option<&'a SyntaxTheme>,
-        cx: &'a AppContext,
-    ) -> display_map::Chunks<'a> {
-        self.display_snapshot.chunks(display_rows, theme, cx)
-    }
-
     pub fn scroll_position(&self) -> Vector2F {
         compute_scroll_position(
             &self.display_snapshot,
@@ -3518,29 +3466,13 @@ impl Snapshot {
             &self.scroll_top_anchor,
         )
     }
+}
 
-    pub fn max_point(&self) -> DisplayPoint {
-        self.display_snapshot.max_point()
-    }
-
-    pub fn longest_row(&self) -> u32 {
-        self.display_snapshot.longest_row()
-    }
-
-    pub fn line_len(&self, display_row: u32) -> u32 {
-        self.display_snapshot.line_len(display_row)
-    }
-
-    pub fn line(&self, display_row: u32) -> String {
-        self.display_snapshot.line(display_row)
-    }
+impl Deref for Snapshot {
+    type Target = DisplayMapSnapshot;
 
-    pub fn prev_row_boundary(&self, point: DisplayPoint) -> (DisplayPoint, Point) {
-        self.display_snapshot.prev_row_boundary(point)
-    }
-
-    pub fn next_row_boundary(&self, point: DisplayPoint) -> (DisplayPoint, Point) {
-        self.display_snapshot.next_row_boundary(point)
+    fn deref(&self) -> &Self::Target {
+        &self.display_snapshot
     }
 }
 
@@ -3709,6 +3641,12 @@ impl SelectionExt for Selection<Point> {
     }
 }
 
+fn render_diagnostic(diagnostic: Diagnostic, style: &EditorStyle) -> ElementBox {
+    let mut text_style = style.text.clone();
+    text_style.color = diagnostic_style(diagnostic.severity, true, &style).text;
+    Text::new(diagnostic.message, text_style).boxed()
+}
+
 pub fn diagnostic_style(
     severity: DiagnosticSeverity,
     valid: bool,

crates/editor/src/element.rs 🔗

@@ -1,6 +1,8 @@
+use crate::display_map::BlockContext;
+
 use super::{
-    DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
-    Select, SelectPhase, Snapshot, SoftWrap, MAX_LINE_LEN,
+    DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select,
+    SelectPhase, Snapshot, SoftWrap, MAX_LINE_LEN,
 };
 use clock::ReplicaId;
 use gpui::{
@@ -13,7 +15,7 @@ use gpui::{
     json::{self, ToJson},
     keymap::Keystroke,
     text_layout::{self, RunStyle, TextLayoutCache},
-    AppContext, Axis, Border, Element, Event, EventContext, FontCache, LayoutContext,
+    AppContext, Axis, Border, Element, ElementBox, Event, EventContext, FontCache, LayoutContext,
     MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
 };
 use json::json;
@@ -25,7 +27,6 @@ use std::{
     fmt::Write,
     ops::Range,
 };
-use theme::BlockStyle;
 
 pub struct EditorElement {
     view: WeakViewHandle<Editor>,
@@ -278,51 +279,6 @@ impl EditorElement {
                 });
             }
         }
-
-        // Draw block backgrounds
-        for (ixs, block_style) in &layout.block_layouts {
-            let row = start_row + ixs.start;
-            let offset = vec2f(0., row as f32 * layout.line_height - scroll_top);
-            let height = ixs.len() as f32 * layout.line_height;
-            cx.scene.push_quad(Quad {
-                bounds: RectF::new(
-                    text_bounds.origin() + offset,
-                    vec2f(text_bounds.width(), height),
-                ),
-                background: block_style.background,
-                border: block_style
-                    .border
-                    .map_or(Default::default(), |color| Border {
-                        width: 1.,
-                        color,
-                        overlay: true,
-                        top: true,
-                        right: false,
-                        bottom: true,
-                        left: false,
-                    }),
-                corner_radius: 0.,
-            });
-            cx.scene.push_quad(Quad {
-                bounds: RectF::new(
-                    gutter_bounds.origin() + offset,
-                    vec2f(gutter_bounds.width(), height),
-                ),
-                background: block_style.gutter_background,
-                border: block_style
-                    .gutter_border
-                    .map_or(Default::default(), |color| Border {
-                        width: 1.,
-                        color,
-                        overlay: true,
-                        top: true,
-                        right: false,
-                        bottom: true,
-                        left: false,
-                    }),
-                corner_radius: 0.,
-            });
-        }
     }
 
     fn paint_gutter(
@@ -461,6 +417,18 @@ impl EditorElement {
         cx.scene.pop_layer();
     }
 
+    fn paint_blocks(
+        &mut self,
+        bounds: RectF,
+        visible_bounds: RectF,
+        layout: &LayoutState,
+        cx: &mut PaintContext,
+    ) {
+        for (row_range, block) in &layout.blocks {
+            //
+        }
+    }
+
     fn max_line_number_width(&self, snapshot: &Snapshot, cx: &LayoutContext) -> f32 {
         let digit_count = (snapshot.buffer_row_count() as f32).log10().floor() as usize + 1;
         let style = &self.settings.style;
@@ -487,18 +455,13 @@ impl EditorElement {
         active_rows: &BTreeMap<u32, bool>,
         snapshot: &Snapshot,
         cx: &LayoutContext,
-    ) -> (
-        Vec<Option<text_layout::Line>>,
-        Vec<(Range<u32>, BlockStyle)>,
-    ) {
+    ) -> Vec<Option<text_layout::Line>> {
         let style = &self.settings.style;
         let include_line_numbers = snapshot.mode == EditorMode::Full;
-        let mut last_block_id = None;
-        let mut blocks = Vec::<(Range<u32>, BlockStyle)>::new();
         let mut line_number_layouts = Vec::with_capacity(rows.len());
         let mut line_number = String::new();
         for (ix, row) in snapshot
-            .buffer_rows(rows.start, cx)
+            .buffer_rows(rows.start)
             .take((rows.end - rows.start) as usize)
             .enumerate()
         {
@@ -508,46 +471,29 @@ impl EditorElement {
             } else {
                 style.line_number
             };
-            match row {
-                DisplayRow::Buffer(buffer_row) => {
-                    if include_line_numbers {
-                        line_number.clear();
-                        write!(&mut line_number, "{}", buffer_row + 1).unwrap();
-                        line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
-                            &line_number,
-                            style.text.font_size,
-                            &[(
-                                line_number.len(),
-                                RunStyle {
-                                    font_id: style.text.font_id,
-                                    color,
-                                    underline: None,
-                                },
-                            )],
-                        )));
-                    }
-                    last_block_id = None;
-                }
-                DisplayRow::Block(block_id, style) => {
-                    let ix = ix as u32;
-                    if last_block_id == Some(block_id) {
-                        if let Some((row_range, _)) = blocks.last_mut() {
-                            row_range.end += 1;
-                        }
-                    } else if let Some(style) = style {
-                        blocks.push((ix..ix + 1, style));
-                    }
-                    line_number_layouts.push(None);
-                    last_block_id = Some(block_id);
-                }
-                DisplayRow::Wrap => {
-                    line_number_layouts.push(None);
-                    last_block_id = None;
+            if let Some(buffer_row) = row {
+                if include_line_numbers {
+                    line_number.clear();
+                    write!(&mut line_number, "{}", buffer_row + 1).unwrap();
+                    line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
+                        &line_number,
+                        style.text.font_size,
+                        &[(
+                            line_number.len(),
+                            RunStyle {
+                                font_id: style.text.font_id,
+                                color,
+                                underline: None,
+                            },
+                        )],
+                    )));
                 }
+            } else {
+                line_number_layouts.push(None);
             }
         }
 
-        (line_number_layouts, blocks)
+        line_number_layouts
     }
 
     fn layout_lines(
@@ -598,7 +544,7 @@ impl EditorElement {
         let mut styles = Vec::new();
         let mut row = rows.start;
         let mut line_exceeded_max_len = false;
-        let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax), cx);
+        let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax));
 
         let newline_chunk = Chunk {
             text: "\n",
@@ -668,6 +614,27 @@ impl EditorElement {
 
         layouts
     }
+
+    fn layout_blocks(
+        &mut self,
+        rows: Range<u32>,
+        snapshot: &Snapshot,
+        cx: &LayoutContext,
+    ) -> Vec<(Range<u32>, ElementBox)> {
+        snapshot
+            .blocks_in_range(rows)
+            .map(|(start_row, block)| {
+                (
+                    start_row..start_row + block.height(),
+                    block.render(&BlockContext {
+                        cx,
+                        gutter_width: 0.0,
+                        anchor_x: 0.0,
+                    }),
+                )
+            })
+            .collect()
+    }
 }
 
 impl Element for EditorElement {
@@ -773,8 +740,7 @@ impl Element for EditorElement {
             }
         });
 
-        let (line_number_layouts, block_layouts) =
-            self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
+        let line_number_layouts = self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx);
 
         let mut max_visible_line_width = 0.0;
         let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx);
@@ -784,6 +750,8 @@ impl Element for EditorElement {
             }
         }
 
+        let blocks = self.layout_blocks(start_row..end_row, &snapshot, cx);
+
         let mut layout = LayoutState {
             size,
             gutter_size,
@@ -797,7 +765,7 @@ impl Element for EditorElement {
             highlighted_row,
             line_layouts,
             line_number_layouts,
-            block_layouts,
+            blocks,
             line_height,
             em_width,
             em_advance,
@@ -853,6 +821,7 @@ impl Element for EditorElement {
                 self.paint_gutter(gutter_bounds, visible_bounds, layout, cx);
             }
             self.paint_text(text_bounds, visible_bounds, layout, cx);
+            self.paint_blocks(text_bounds, visible_bounds, layout, cx);
 
             cx.scene.pop_layer();
 
@@ -927,7 +896,7 @@ pub struct LayoutState {
     highlighted_row: Option<u32>,
     line_layouts: Vec<text_layout::Line>,
     line_number_layouts: Vec<Option<text_layout::Line>>,
-    block_layouts: Vec<(Range<u32>, BlockStyle)>,
+    blocks: Vec<(Range<u32>, ElementBox)>,
     line_height: f32,
     em_width: f32,
     em_advance: f32,
@@ -1185,7 +1154,7 @@ mod tests {
         });
         let element = EditorElement::new(editor.downgrade(), settings);
 
-        let (layouts, _) = editor.update(cx, |editor, cx| {
+        let layouts = editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
             let mut presenter = cx.build_presenter(window_id, 30.);
             let mut layout_cx = presenter.build_layout_context(false, cx);

crates/gpui/src/elements.rs 🔗

@@ -301,6 +301,10 @@ impl<T: Element> Default for Lifecycle<T> {
 }
 
 impl ElementBox {
+    pub fn name(&self) -> Option<&str> {
+        self.0.name.as_deref()
+    }
+
     pub fn metadata<T: 'static>(&self) -> Option<&T> {
         let element = unsafe { &*self.0.element.as_ptr() };
         element.metadata().and_then(|m| m.downcast_ref())

crates/theme/src/theme.rs 🔗

@@ -253,8 +253,6 @@ pub struct EditorStyle {
 #[derive(Copy, Clone, Deserialize, Default)]
 pub struct DiagnosticStyle {
     pub text: Color,
-    #[serde(flatten)]
-    pub block: BlockStyle,
 }
 
 #[derive(Clone, Copy, Default, Deserialize)]
@@ -273,14 +271,6 @@ pub struct InputEditorStyle {
     pub selection: SelectionStyle,
 }
 
-#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
-pub struct BlockStyle {
-    pub background: Option<Color>,
-    pub border: Option<Color>,
-    pub gutter_background: Option<Color>,
-    pub gutter_border: Option<Color>,
-}
-
 impl EditorStyle {
     pub fn placeholder_text(&self) -> &TextStyle {
         self.placeholder_text.as_ref().unwrap_or(&self.text)