sketch in split editor and start working on new logic in block map

Cole Miller and cameron created

Co-authored-by: cameron <cameron.studdstreet@gmail.com>

Change summary

crates/editor/src/display_map/block_map.rs | 115 ++++++++++++++++++--
crates/editor/src/display_map/wrap_map.rs  |   9 +
crates/editor/src/element.rs               |  10 +
crates/editor/src/split.rs                 | 130 ++++++++++++++++++++++++
crates/editor/src/test.rs                  |   5 
5 files changed, 250 insertions(+), 19 deletions(-)

Detailed changes

crates/editor/src/display_map/block_map.rs ๐Ÿ”—

@@ -257,6 +257,16 @@ pub enum BlockId {
     ExcerptBoundary(ExcerptId),
     FoldedBuffer(ExcerptId),
     Custom(CustomBlockId),
+    Spacer(SpacerId),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
+pub struct SpacerId(pub usize);
+
+impl From<SpacerId> for EntityId {
+    fn from(value: SpacerId) -> Self {
+        EntityId::from(value.0 as u64)
+    }
 }
 
 impl From<BlockId> for ElementId {
@@ -267,6 +277,7 @@ impl From<BlockId> for ElementId {
                 ("ExcerptBoundary", EntityId::from(excerpt_id)).into()
             }
             BlockId::FoldedBuffer(id) => ("FoldedBuffer", EntityId::from(id)).into(),
+            BlockId::Spacer(id) => ("Spacer", EntityId::from(id)).into(),
         }
     }
 }
@@ -277,6 +288,7 @@ impl std::fmt::Display for BlockId {
             Self::Custom(id) => write!(f, "Block({id:?})"),
             Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
             Self::FoldedBuffer(id) => write!(f, "FoldedBuffer({id:?})"),
+            Self::Spacer(id) => write!(f, "Spacer({id:?})"),
         }
     }
 }
@@ -302,6 +314,10 @@ pub enum Block {
         excerpt: ExcerptInfo,
         height: u32,
     },
+    Spacer {
+        height: u32,
+        id: SpacerId,
+    },
 }
 
 impl Block {
@@ -317,6 +333,7 @@ impl Block {
                 excerpt: next_excerpt,
                 ..
             } => BlockId::ExcerptBoundary(next_excerpt.id),
+            Block::Spacer { id, .. } => BlockId::Spacer(*id),
         }
     }
 
@@ -325,7 +342,8 @@ impl Block {
             Block::Custom(block) => block.height.is_some(),
             Block::ExcerptBoundary { .. }
             | Block::FoldedBuffer { .. }
-            | Block::BufferHeader { .. } => true,
+            | Block::BufferHeader { .. }
+            | Block::Spacer { .. } => true,
         }
     }
 
@@ -334,7 +352,8 @@ impl Block {
             Block::Custom(block) => block.height.unwrap_or(0),
             Block::ExcerptBoundary { height, .. }
             | Block::FoldedBuffer { height, .. }
-            | Block::BufferHeader { height, .. } => *height,
+            | Block::BufferHeader { height, .. }
+            | Block::Spacer { height, .. } => *height,
         }
     }
 
@@ -343,7 +362,8 @@ impl Block {
             Block::Custom(block) => block.style,
             Block::ExcerptBoundary { .. }
             | Block::FoldedBuffer { .. }
-            | Block::BufferHeader { .. } => BlockStyle::Sticky,
+            | Block::BufferHeader { .. }
+            | Block::Spacer { .. } => BlockStyle::Sticky,
         }
     }
 
@@ -353,6 +373,7 @@ impl Block {
             Block::FoldedBuffer { .. } => false,
             Block::ExcerptBoundary { .. } => true,
             Block::BufferHeader { .. } => true,
+            Block::Spacer { .. } => false,
         }
     }
 
@@ -362,6 +383,7 @@ impl Block {
             Block::FoldedBuffer { .. } => false,
             Block::ExcerptBoundary { .. } => false,
             Block::BufferHeader { .. } => false,
+            Block::Spacer { .. } => false,
         }
     }
 
@@ -374,6 +396,7 @@ impl Block {
             Block::FoldedBuffer { .. } => false,
             Block::ExcerptBoundary { .. } => false,
             Block::BufferHeader { .. } => false,
+            Block::Spacer { .. } => false,
         }
     }
 
@@ -383,6 +406,7 @@ impl Block {
             Block::FoldedBuffer { .. } => true,
             Block::ExcerptBoundary { .. } => false,
             Block::BufferHeader { .. } => false,
+            Block::Spacer { .. } => true,
         }
     }
 
@@ -392,6 +416,7 @@ impl Block {
             Block::FoldedBuffer { .. } => true,
             Block::ExcerptBoundary { .. } => true,
             Block::BufferHeader { .. } => true,
+            Block::Spacer { .. } => false,
         }
     }
 
@@ -401,6 +426,7 @@ impl Block {
             Block::FoldedBuffer { .. } => true,
             Block::ExcerptBoundary { .. } => false,
             Block::BufferHeader { .. } => true,
+            Block::Spacer { .. } => false,
         }
     }
 }
@@ -427,6 +453,11 @@ impl Debug for Block {
                 .field("excerpt", excerpt)
                 .field("height", height)
                 .finish(),
+            Self::Spacer { height, id } => f
+                .debug_struct("Spacer")
+                .field("height", height)
+                .field("id", id)
+                .finish(),
         }
     }
 }
@@ -540,8 +571,29 @@ impl BlockMap {
         let mut edits = edits.into_iter().peekable();
 
         while let Some(edit) = edits.next() {
-            let mut old_start = WrapRow(edit.old.start);
+            // FIXME biases?
+
+            let mut old_buffer_start = self
+                .wrap_snapshot
+                .borrow()
+                .to_point(WrapPoint::new(edit.old.start, 0), Bias::Left);
+            old_buffer_start.column = 0;
+            let mut old_start = WrapRow(
+                self.wrap_snapshot
+                    .borrow()
+                    .make_wrap_point(old_buffer_start, Bias::Left)
+                    .row(),
+            );
+
+            let mut new_buffer_start =
+                wrap_snapshot.to_point(WrapPoint::new(edit.new.start, 0), Bias::Left);
+            new_buffer_start.column = 0;
             let mut new_start = WrapRow(edit.new.start);
+            let mut new_start = WrapRow(
+                wrap_snapshot
+                    .make_wrap_point(new_buffer_start, Bias::Left)
+                    .row(),
+            );
 
             // Only preserve transforms that:
             // * Strictly precedes this edit
@@ -694,6 +746,10 @@ impl BlockMap {
 
             BlockMap::sort_blocks(&mut blocks_in_edit);
 
+            let hunks = buffer
+                .diff_hunks_in_range(new_buffer_start..new_buffer_end)
+                .collect::<Vec<_>>();
+
             // For each of these blocks, insert a new isomorphic transform preceding the block,
             // and then insert the block itself.
             let mut just_processed_folded_buffer = false;
@@ -918,6 +974,19 @@ impl BlockMap {
             _ => false,
         });
     }
+
+    // fn spacer_blocks<'a, R, T>(
+    //     &'a self,
+    //     buffer: &'a multi_buffer::MultiBufferSnapshot,
+    //     range: R,
+    //     wrap_snapshot: &'a WrapSnapshot,
+    // ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'a
+    // where
+    //     R: RangeBounds<T>,
+    //     T: multi_buffer::ToOffset,
+    // {
+    //     let start_row = range.start_bound().map(|x| x.to_offset(snapshot))
+    // }
 }
 
 fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32, wrap_snapshot: &WrapSnapshot) {
@@ -1429,31 +1498,42 @@ impl BlockSnapshot {
 
     pub fn block_for_id(&self, block_id: BlockId) -> Option<Block> {
         let buffer = self.wrap_snapshot.buffer_snapshot();
-        let wrap_point = match block_id {
+        let wrap_row = match block_id {
             BlockId::Custom(custom_block_id) => {
                 let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
                 return Some(Block::Custom(custom_block.clone()));
             }
             BlockId::ExcerptBoundary(next_excerpt_id) => {
                 let excerpt_range = buffer.range_for_excerpt(next_excerpt_id)?;
-                self.wrap_snapshot
-                    .make_wrap_point(excerpt_range.start, Bias::Left)
+                Some(
+                    self.wrap_snapshot
+                        .make_wrap_point(excerpt_range.start, Bias::Left)
+                        .row(),
+                )
             }
-            BlockId::FoldedBuffer(excerpt_id) => self
-                .wrap_snapshot
-                .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left),
+            BlockId::FoldedBuffer(excerpt_id) => Some(
+                self.wrap_snapshot
+                    .make_wrap_point(buffer.range_for_excerpt(excerpt_id)?.start, Bias::Left)
+                    .row(),
+            ),
+            BlockId::Spacer(_) => None,
         };
-        let wrap_row = WrapRow(wrap_point.row());
 
         let mut cursor = self.transforms.cursor::<WrapRow>(());
-        cursor.seek(&wrap_row, Bias::Left);
+        if let Some(wrap_row) = wrap_row {
+            cursor.seek(&WrapRow(wrap_row), Bias::Left);
+        } else {
+            cursor.next();
+        }
 
         while let Some(transform) = cursor.item() {
             if let Some(block) = transform.block.as_ref() {
                 if block.id() == block_id {
                     return Some(block.clone());
                 }
-            } else if *cursor.start() > wrap_row {
+            } else if let Some(wrap_row) = wrap_row
+                && *cursor.start() > WrapRow(wrap_row)
+            {
                 break;
             }
 
@@ -2211,10 +2291,10 @@ mod tests {
         let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot);
         let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
         let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
-        let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
+        let (_, wrap_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
 
-        let block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1);
-        let snapshot = block_map.read(wraps_snapshot, Default::default());
+        let block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
+        let snapshot = block_map.read(wrap_snapshot.clone(), Default::default());
 
         // Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
         assert_eq!(snapshot.text(), "\nBuff\ner 1\n\nBuff\ner 2\n\nBuff\ner 3");
@@ -2231,6 +2311,9 @@ mod tests {
                 (6..7, BlockId::ExcerptBoundary(excerpt_ids[2])), // path, header
             ]
         );
+
+        dbg!(wrap_snapshot.row_infos(0).collect::<Vec<_>>());
+        // dbg!(block_map.transforms.borrow().iter().collect::<Vec<_>>());
     }
 
     #[gpui::test]

crates/editor/src/display_map/wrap_map.rs ๐Ÿ”—

@@ -31,18 +31,21 @@ pub struct WrapMap {
 #[derive(Clone)]
 pub struct WrapSnapshot {
     pub(super) tab_snapshot: TabSnapshot,
-    transforms: SumTree<Transform>,
+    // FIXME
+    pub(crate) transforms: SumTree<Transform>,
     interpolated: bool,
 }
 
+// FIXME
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
-struct Transform {
+pub(crate) struct Transform {
     summary: TransformSummary,
     display_text: Option<&'static str>,
 }
 
+// FIXME
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
-struct TransformSummary {
+pub(crate) struct TransformSummary {
     input: TextSummary,
     output: TextSummary,
 }

crates/editor/src/element.rs ๐Ÿ”—

@@ -3757,6 +3757,16 @@ impl EditorElement {
 
                 result.into_any()
             }
+
+            Block::Spacer { height, .. } => v_flex()
+                .id(block_id)
+                .w_full()
+                .child(
+                    div()
+                        .h(*height as f32 * window.line_height())
+                        .debug_bg_magenta(),
+                )
+                .into_any(),
         };
 
         // Discover the element's content height, then round up to the nearest multiple of line height.

crates/editor/src/split.rs ๐Ÿ”—

@@ -0,0 +1,130 @@
+use crate::EditorEvent;
+
+use super::{Editor, EditorElement, EditorStyle};
+use gpui::{App, Context, Entity, Render, Window};
+use ui::{Element, IntoElement};
+use workspace::ItemHandle;
+
+// stage 1: render two side-by-side editors with the correct scrolling behavior
+// stage 2: add alignment map to insert blank lines
+
+/// An editor that can be rendered with a split diff layout.
+///
+/// When [secondary] is `None`, it is rendered with an inline diff style.
+pub struct SplittableEditor {
+    primary: Entity<Editor>,
+    secondary: Option<Entity<Editor>>,
+}
+
+impl SplittableEditor {
+    fn subscribe(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+        cx.subscribe_in(
+            &self.primary,
+            window,
+            |this, editor, event: &EditorEvent, window, cx| {},
+        )
+        .detach();
+    }
+
+    fn sync_state(&mut self, cx: &mut App) {}
+}
+
+impl SplittableEditor {}
+
+struct SplitEditorElement {
+    primary: Entity<Editor>,
+    secondary: Entity<Editor>,
+    style: EditorStyle,
+}
+
+struct SplitEditorElementLayout {}
+
+impl Element for SplitEditorElement {
+    type RequestLayoutState = ();
+
+    type PrepaintState = SplitEditorElementLayout;
+
+    fn id(&self) -> Option<ui::ElementId> {
+        todo!()
+    }
+
+    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
+        todo!()
+    }
+
+    fn request_layout(
+        &mut self,
+        id: Option<&gpui::GlobalElementId>,
+        inspector_id: Option<&gpui::InspectorElementId>,
+        window: &mut ui::Window,
+        cx: &mut ui::App,
+    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
+    }
+
+    fn prepaint(
+        &mut self,
+        id: Option<&gpui::GlobalElementId>,
+        inspector_id: Option<&gpui::InspectorElementId>,
+        bounds: gpui::Bounds<ui::Pixels>,
+        request_layout: &mut Self::RequestLayoutState,
+        window: &mut ui::Window,
+        cx: &mut ui::App,
+    ) -> Self::PrepaintState {
+        todo!()
+    }
+
+    fn paint(
+        &mut self,
+        id: Option<&gpui::GlobalElementId>,
+        inspector_id: Option<&gpui::InspectorElementId>,
+        bounds: gpui::Bounds<ui::Pixels>,
+        request_layout: &mut Self::RequestLayoutState,
+        prepaint: &mut Self::PrepaintState,
+        window: &mut ui::Window,
+        cx: &mut ui::App,
+    ) {
+        todo!()
+    }
+}
+
+impl Render for SplittableEditor {
+    fn render(
+        &mut self,
+        window: &mut ui::Window,
+        cx: &mut ui::Context<Self>,
+    ) -> impl ui::IntoElement {
+        enum SplittableEditorElement {
+            Single(EditorElement),
+            Split(SplitEditorElement),
+        }
+
+        impl Element for SplittableEditorElement {}
+        impl IntoElement for SplittableEditorElement {
+            type Element = Self;
+
+            fn into_element(self) -> Self::Element {
+                self
+            }
+        }
+
+        let style;
+
+        if let Some(secondary) = self.secondary.clone() {
+            SplittableEditorElement::Split(SplitEditorElement {
+                primary: self.primary.clone(),
+                secondary,
+                style,
+            })
+        } else {
+            SplittableEditorElement::Single(EditorElement::new(&self.primary.clone(), style))
+        }
+    }
+}
+
+impl IntoElement for SplitEditorElement {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

crates/editor/src/test.rs ๐Ÿ”—

@@ -238,6 +238,11 @@ pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestCo
                     lines[row as usize].push_str("ยง -----");
                 }
             }
+            Block::Spacer { height, .. } => {
+                for row in row.0..row.0 + height {
+                    lines[row as usize].push_str("@@@@@@@");
+                }
+            }
         }
     }
     lines.join("\n")