Merge pull request #273 from zed-industries/flexible-blocks

Max Brunsfeld created

Render blocks as arbitrary elements

Change summary

crates/editor/src/display_map.rs           |  49 -
crates/editor/src/display_map/block_map.rs | 563 +++++++----------------
crates/editor/src/display_map/wrap_map.rs  |  22 
crates/editor/src/editor.rs                | 134 +----
crates/editor/src/element.rs               | 258 +++++-----
crates/gpui/src/elements.rs                |   4 
crates/theme/src/theme.rs                  |  10 
7 files changed, 375 insertions(+), 665 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,27 @@ 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 anchor_x: f32,
+}
+
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub enum BlockDisposition {
     Above,
@@ -83,7 +82,7 @@ struct Transform {
 }
 
 #[derive(Clone, Debug)]
-struct AlignedBlock {
+pub struct AlignedBlock {
     block: Arc<Block>,
     column: u32,
 }
@@ -92,35 +91,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 +317,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 +371,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 +413,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 +471,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 +506,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 +523,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 +530,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 +560,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 +569,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 +673,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 +701,25 @@ 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 mut block_end = self.transforms.end(&()).0 .0;
+            self.transforms.next(&());
+            if self.transforms.item().is_none() {
+                block_end -= 1;
+            }
+
             let start_in_block = self.output_row - block_start;
             let end_in_block = cmp::min(self.max_output_row, block_end) - block_start;
-            self.transforms.next(&());
-            self.block_chunks = Some(BlockChunks::new(
-                block,
-                start_in_block..end_in_block,
-                self.cx,
-            ));
-            return self.next();
+            let line_count = end_in_block - start_in_block;
+            self.output_row += line_count;
+
+            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 +756,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 +771,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 +791,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 +814,24 @@ 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)
+    }
+
+    pub fn position(&self) -> &Anchor {
+        &self.block.position
+    }
+}
+
 impl Deref for AlignedBlock {
     type Target = Block;
 
@@ -975,7 +845,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 +872,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 +892,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();
@@ -1110,49 +909,67 @@ mod tests {
         let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone());
 
         let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx);
-        let block_ids = writer.insert(
+        writer.insert(
             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");
+
+        let blocks = snapshot
+            .blocks_in_range(0..8)
+            .map(|(start_row, block)| {
+                (
+                    start_row..start_row + block.height(),
+                    block.column(),
+                    block
+                        .render(&BlockContext { cx, 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)
         );
         assert_eq!(
             snapshot.to_block_point(WrapPoint::new(1, 0)),
-            BlockPoint::new(3, 0)
+            BlockPoint::new(4, 0)
         );
         assert_eq!(
             snapshot.to_block_point(WrapPoint::new(3, 3)),
-            BlockPoint::new(5, 3)
+            BlockPoint::new(6, 3)
         );
 
         assert_eq!(
@@ -1168,7 +985,7 @@ mod tests {
             WrapPoint::new(1, 0)
         );
         assert_eq!(
-            snapshot.to_wrap_point(BlockPoint::new(6, 0)),
+            snapshot.to_wrap_point(BlockPoint::new(7, 0)),
             WrapPoint::new(3, 3)
         );
 
@@ -1178,7 +995,7 @@ mod tests {
         );
         assert_eq!(
             snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right),
-            BlockPoint::new(3, 0)
+            BlockPoint::new(4, 0)
         );
         assert_eq!(
             snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left),
@@ -1186,43 +1003,46 @@ mod tests {
         );
         assert_eq!(
             snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right),
-            BlockPoint::new(3, 0)
+            BlockPoint::new(4, 0)
         );
         assert_eq!(
-            snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left),
-            BlockPoint::new(3, 0)
+            snapshot.clip_point(BlockPoint::new(4, 0), Bias::Left),
+            BlockPoint::new(4, 0)
         );
         assert_eq!(
-            snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right),
-            BlockPoint::new(3, 0)
+            snapshot.clip_point(BlockPoint::new(4, 0), Bias::Right),
+            BlockPoint::new(4, 0)
         );
         assert_eq!(
-            snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left),
-            BlockPoint::new(5, 3)
+            snapshot.clip_point(BlockPoint::new(6, 3), Bias::Left),
+            BlockPoint::new(6, 3)
         );
         assert_eq!(
-            snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right),
-            BlockPoint::new(5, 3)
+            snapshot.clip_point(BlockPoint::new(6, 3), Bias::Right),
+            BlockPoint::new(6, 3)
         );
         assert_eq!(
-            snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left),
-            BlockPoint::new(5, 3)
+            snapshot.clip_point(BlockPoint::new(7, 0), Bias::Left),
+            BlockPoint::new(6, 3)
         );
         assert_eq!(
-            snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right),
-            BlockPoint::new(5, 3)
+            snapshot.clip_point(BlockPoint::new(7, 0), Bias::Right),
+            BlockPoint::new(6, 3)
         );
 
         assert_eq!(
-            snapshot.buffer_rows(0, None).collect::<Vec<_>>(),
+            snapshot.buffer_rows(0).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)
+                Some(0),
+                None,
+                None,
+                None,
+                Some(1),
+                Some(2),
+                Some(3),
+                None,
+                None,
+                None
             ]
         );
 
@@ -1240,10 +1060,7 @@ 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]
@@ -1267,17 +1084,15 @@ mod tests {
             vec![
                 BlockProperties {
                     position: Point::new(1, 12),
-                    text: "<BLOCK 1",
                     disposition: BlockDisposition::Above,
-                    build_runs: None,
-                    build_style: None,
+                    render: Arc::new(|_| Empty::new().named("block 1")),
+                    height: 1,
                 },
                 BlockProperties {
                     position: Point::new(1, 1),
-                    text: ">BLOCK 2",
                     disposition: BlockDisposition::Below,
-                    build_runs: None,
-                    build_style: None,
+                    render: Arc::new(|_| Empty::new().named("block 2")),
+                    height: 1,
                 },
             ],
             cx,
@@ -1288,7 +1103,7 @@ mod tests {
         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"
+            "one two \nthree\n\nfour five \nsix\n\nseven \neight"
         );
     }
 
@@ -1348,33 +1163,23 @@ mod tests {
                                 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
                             };
+                            let height = rng.gen_range(1..5);
                             log::info!(
-                                "inserting block {:?} {:?} with text {:?}",
+                                "inserting block {:?} {:?} with height {}",
                                 disposition,
                                 position.to_point(buffer),
-                                text.to_string()
+                                height
                             );
                             BlockProperties {
                                 position,
-                                text,
+                                height,
                                 disposition,
-                                build_runs: None,
-                                build_style: None,
+                                render: Arc::new(|_| Empty::new().boxed()),
                             }
                         })
                         .collect::<Vec<_>>();
@@ -1454,10 +1259,9 @@ mod tests {
                         id,
                         BlockProperties {
                             position: BlockPoint::new(row, column),
-                            text: block.text,
-                            build_runs: block.build_runs.clone(),
-                            build_style: None,
+                            height: block.height,
                             disposition: block.disposition,
+                            render: block.render.clone(),
                         },
                     )
                 })
@@ -1479,17 +1283,12 @@ mod tests {
                     .to_point(WrapPoint::new(row, 0), Bias::Left)
                     .row;
 
-                while let Some((block_id, block)) = sorted_blocks.peek() {
+                while let Some((_, 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));
+                        let text = "\n".repeat(block.height as usize);
+                        expected_text.push_str(&text);
+                        for _ in 0..block.height {
+                            expected_buffer_rows.push(None);
                         }
                         sorted_blocks.next();
                     } else {
@@ -1498,24 +1297,15 @@ mod tests {
                 }
 
                 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_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) });
                 expected_text.push_str(input_line);
 
-                while let Some((block_id, block)) = sorted_blocks.peek() {
+                while let Some((_, 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));
+                        let text = "\n".repeat(block.height as usize);
+                        expected_text.push_str(&text);
+                        for _ in 0..block.height {
+                            expected_buffer_rows.push(None);
                         }
                         sorted_blocks.next();
                     } else {
@@ -1529,7 +1319,7 @@ mod tests {
             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)
+                    .chunks(start_row as u32..expected_row_count as u32, None)
                     .map(|chunk| chunk.text)
                     .collect::<String>();
                 assert_eq!(
@@ -1539,7 +1329,7 @@ mod tests {
                 );
                 assert_eq!(
                     blocks_snapshot
-                        .buffer_rows(start_row as u32, None)
+                        .buffer_rows(start_row as u32)
                         .collect::<Vec<_>>(),
                     &expected_buffer_rows[start_row..]
                 );
@@ -1569,7 +1359,6 @@ mod tests {
                 }
             }
 
-            log::info!("getting longest row >>>>>>>>>>>>>>>>>>>>>>>>");
             let longest_row = blocks_snapshot.longest_row();
             assert!(
                 expected_longest_rows.contains(&longest_row),

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, is_valid, cx.anchor_x)
+                    });
                 }
                 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, true, cx.anchor_x)
+                            }),
                             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)
-    }
-
-    pub fn prev_row_boundary(&self, point: DisplayPoint) -> (DisplayPoint, Point) {
-        self.display_snapshot.prev_row_boundary(point)
-    }
+impl Deref for Snapshot {
+    type Target = DisplayMapSnapshot;
 
-    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,20 @@ impl SelectionExt for Selection<Point> {
     }
 }
 
+fn render_diagnostic(
+    diagnostic: Diagnostic,
+    style: &EditorStyle,
+    valid: bool,
+    anchor_x: f32,
+) -> ElementBox {
+    let mut text_style = style.text.clone();
+    text_style.color = diagnostic_style(diagnostic.severity, valid, &style).text;
+    Text::new(diagnostic.message, text_style)
+        .contained()
+        .with_margin_left(anchor_x)
+        .boxed()
+}
+
 pub fn diagnostic_style(
     severity: DiagnosticSeverity,
     valid: bool,

crates/editor/src/element.rs 🔗

@@ -1,6 +1,8 @@
+use crate::display_map::{BlockContext, ToDisplayPoint};
+
 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,11 +15,11 @@ 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;
-use language::Chunk;
+use language::{Chunk, ToPoint};
 use smallvec::SmallVec;
 use std::{
     cmp::{self, Ordering},
@@ -25,7 +27,6 @@ use std::{
     fmt::Write,
     ops::Range,
 };
-use theme::BlockStyle;
 
 pub struct EditorElement {
     view: WeakViewHandle<Editor>,
@@ -219,7 +220,6 @@ impl EditorElement {
     ) {
         let bounds = gutter_bounds.union_rect(text_bounds);
         let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
-        let start_row = layout.snapshot.scroll_position().y() as u32;
         let editor = self.view(cx.app);
         let style = &self.settings.style;
         cx.scene.push_quad(Quad {
@@ -278,51 +278,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 +416,24 @@ impl EditorElement {
         cx.scene.pop_layer();
     }
 
+    fn paint_blocks(
+        &mut self,
+        text_bounds: RectF,
+        visible_bounds: RectF,
+        layout: &mut LayoutState,
+        cx: &mut PaintContext,
+    ) {
+        let scroll_position = layout.snapshot.scroll_position();
+        let scroll_left = scroll_position.x() * layout.em_width;
+        let scroll_top = scroll_position.y() * layout.line_height;
+
+        for (row, element) in &mut layout.blocks {
+            let origin = text_bounds.origin()
+                + vec2f(-scroll_left, *row as f32 * layout.line_height - scroll_top);
+            element.paint(origin, visible_bounds, cx);
+        }
+    }
+
     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 +460,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 +476,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 +549,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 +619,46 @@ impl EditorElement {
 
         layouts
     }
+
+    fn layout_blocks(
+        &mut self,
+        rows: Range<u32>,
+        snapshot: &Snapshot,
+        text_width: f32,
+        line_height: f32,
+        style: &EditorStyle,
+        line_layouts: &[text_layout::Line],
+        cx: &mut LayoutContext,
+    ) -> Vec<(u32, ElementBox)> {
+        snapshot
+            .blocks_in_range(rows.clone())
+            .map(|(start_row, block)| {
+                let anchor_row = block
+                    .position()
+                    .to_point(&snapshot.buffer_snapshot)
+                    .to_display_point(snapshot)
+                    .row();
+
+                let anchor_x = if rows.contains(&anchor_row) {
+                    line_layouts[(anchor_row - rows.start) as usize]
+                        .x_for_index(block.column() as usize)
+                } else {
+                    layout_line(anchor_row, snapshot, style, cx.text_layout_cache)
+                        .x_for_index(block.column() as usize)
+                };
+
+                let mut element = block.render(&BlockContext { cx, anchor_x });
+                element.layout(
+                    SizeConstraint {
+                        min: Vector2F::zero(),
+                        max: vec2f(text_width, block.height() as f32 * line_height),
+                    },
+                    cx,
+                );
+                (start_row, element)
+            })
+            .collect()
+    }
 }
 
 impl Element for EditorElement {
@@ -773,8 +764,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 +774,16 @@ impl Element for EditorElement {
             }
         }
 
+        let blocks = self.layout_blocks(
+            start_row..end_row,
+            &snapshot,
+            text_size.x(),
+            line_height,
+            &style,
+            &line_layouts,
+            cx,
+        );
+
         let mut layout = LayoutState {
             size,
             gutter_size,
@@ -797,7 +797,7 @@ impl Element for EditorElement {
             highlighted_row,
             line_layouts,
             line_number_layouts,
-            block_layouts,
+            blocks,
             line_height,
             em_width,
             em_advance,
@@ -853,6 +853,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 +928,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<(u32, ElementBox)>,
     line_height: f32,
     em_width: f32,
     em_advance: f32,
@@ -940,7 +941,8 @@ pub struct LayoutState {
 impl LayoutState {
     fn scroll_width(&self, layout_cache: &TextLayoutCache) -> f32 {
         let row = self.snapshot.longest_row();
-        let longest_line_width = self.layout_line(row, &self.snapshot, layout_cache).width();
+        let longest_line_width =
+            layout_line(row, &self.snapshot, &self.style, layout_cache).width();
         longest_line_width.max(self.max_visible_line_width) + self.overscroll.x()
     }
 
@@ -955,36 +957,36 @@ impl LayoutState {
             max_row.saturating_sub(1) as f32,
         )
     }
+}
 
-    pub fn layout_line(
-        &self,
-        row: u32,
-        snapshot: &Snapshot,
-        layout_cache: &TextLayoutCache,
-    ) -> text_layout::Line {
-        let mut line = snapshot.line(row);
-
-        if line.len() > MAX_LINE_LEN {
-            let mut len = MAX_LINE_LEN;
-            while !line.is_char_boundary(len) {
-                len -= 1;
-            }
-            line.truncate(len);
+fn layout_line(
+    row: u32,
+    snapshot: &Snapshot,
+    style: &EditorStyle,
+    layout_cache: &TextLayoutCache,
+) -> text_layout::Line {
+    let mut line = snapshot.line(row);
+
+    if line.len() > MAX_LINE_LEN {
+        let mut len = MAX_LINE_LEN;
+        while !line.is_char_boundary(len) {
+            len -= 1;
         }
-
-        layout_cache.layout_str(
-            &line,
-            self.style.text.font_size,
-            &[(
-                snapshot.line_len(row) as usize,
-                RunStyle {
-                    font_id: self.style.text.font_id,
-                    color: Color::black(),
-                    underline: None,
-                },
-            )],
-        )
+        line.truncate(len);
     }
+
+    layout_cache.layout_str(
+        &line,
+        style.text.font_size,
+        &[(
+            snapshot.line_len(row) as usize,
+            RunStyle {
+                font_id: style.text.font_id,
+                color: Color::black(),
+                underline: None,
+            },
+        )],
+    )
 }
 
 pub struct PaintState {
@@ -1185,7 +1187,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)