WIP: Make `editor` crate compile again

Antonio Scandurra created

Tests are still failing though.

Change summary

crates/editor/src/display_map.rs           |  45 +++-
crates/editor/src/display_map/block_map.rs | 220 +++++++++++++----------
crates/editor/src/editor.rs                |  14 +
crates/editor/src/element.rs               |  75 ++++----
crates/editor/src/movement.rs              |  49 ----
crates/editor/src/multi_buffer.rs          |  98 ++++++---
crates/language/src/buffer.rs              |  24 ++
7 files changed, 299 insertions(+), 226 deletions(-)

Detailed changes

crates/editor/src/display_map.rs πŸ”—

@@ -15,8 +15,8 @@ use tab_map::TabMap;
 use wrap_map::WrapMap;
 
 pub use block_map::{
-    AlignedBlock, BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
-    BlockDisposition, BlockId, BlockProperties, RenderBlock,
+    BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
+    BlockDisposition, BlockId, BlockProperties, RenderBlock, TransformBlock,
 };
 
 pub trait ToDisplayPoint {
@@ -43,13 +43,14 @@ impl DisplayMap {
         font_id: FontId,
         font_size: f32,
         wrap_width: Option<f32>,
+        excerpt_header_height: u8,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
         let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
         let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
         let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
-        let block_map = BlockMap::new(snapshot);
+        let block_map = BlockMap::new(snapshot, excerpt_header_height);
         cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
         DisplayMap {
             buffer,
@@ -318,7 +319,7 @@ impl DisplaySnapshot {
     pub fn blocks_in_range<'a>(
         &'a self,
         rows: Range<u32>,
-    ) -> impl Iterator<Item = (u32, &'a AlignedBlock)> {
+    ) -> impl Iterator<Item = (u32, &'a TransformBlock)> {
         self.blocks_snapshot.blocks_in_range(rows)
     }
 
@@ -471,6 +472,7 @@ mod tests {
 
         let font_cache = cx.font_cache().clone();
         let tab_size = rng.gen_range(1..=4);
+        let excerpt_header_height = rng.gen_range(1..=5);
         let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
         let font_id = font_cache
             .select_font(family_id, &Default::default())
@@ -497,7 +499,15 @@ mod tests {
         });
 
         let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
+            DisplayMap::new(
+                buffer.clone(),
+                tab_size,
+                font_id,
+                font_size,
+                wrap_width,
+                excerpt_header_height,
+                cx,
+            )
         });
         let mut notifications = observe(&map, &mut cx);
         let mut fold_count = 0;
@@ -711,7 +721,15 @@ mod tests {
         let text = "one two three four five\nsix seven eight";
         let buffer = MultiBuffer::build_simple(text, cx);
         let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, wrap_width, cx)
+            DisplayMap::new(
+                buffer.clone(),
+                tab_size,
+                font_id,
+                font_size,
+                wrap_width,
+                1,
+                cx,
+            )
         });
 
         let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
@@ -791,7 +809,7 @@ mod tests {
             .unwrap();
         let font_size = 14.0;
         let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
+            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, cx)
         });
         buffer.update(cx, |buffer, cx| {
             buffer.edit(
@@ -871,7 +889,7 @@ mod tests {
         let font_size = 14.0;
 
         let map =
-            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
+            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, cx));
         assert_eq!(
             cx.update(|cx| chunks(0..5, &map, &theme, cx)),
             vec![
@@ -958,8 +976,9 @@ mod tests {
             .unwrap();
         let font_size = 16.0;
 
-        let map = cx
-            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx));
+        let map = cx.add_model(|cx| {
+            DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), 1, cx)
+        });
         assert_eq!(
             cx.update(|cx| chunks(0..5, &map, &theme, cx)),
             [
@@ -1003,7 +1022,7 @@ mod tests {
             .unwrap();
         let font_size = 14.0;
         let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
+            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, cx)
         });
         let map = map.update(cx, |map, cx| map.snapshot(cx));
 
@@ -1047,7 +1066,7 @@ mod tests {
         let font_size = 14.0;
 
         let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
+            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, cx)
         });
         let map = map.update(cx, |map, cx| map.snapshot(cx));
         assert_eq!(map.text(), "βœ…       Ξ±\nΞ²   \nπŸ€Ξ²      Ξ³");
@@ -1105,7 +1124,7 @@ mod tests {
             .unwrap();
         let font_size = 14.0;
         let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, cx)
+            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, cx)
         });
         assert_eq!(
             map.update(cx, |map, cx| map.snapshot(cx)).max_point(),

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

@@ -5,10 +5,9 @@ use gpui::{AppContext, ElementBox};
 use language::{BufferSnapshot, Chunk};
 use parking_lot::Mutex;
 use std::{
-    cmp::{self, Ordering, Reverse},
+    cmp::{self, Ordering},
     fmt::Debug,
     ops::{Deref, Range},
-    path::Path,
     sync::{
         atomic::{AtomicUsize, Ordering::SeqCst},
         Arc,
@@ -24,6 +23,7 @@ pub struct BlockMap {
     wrap_snapshot: Mutex<WrapSnapshot>,
     blocks: Vec<Arc<Block>>,
     transforms: Mutex<SumTree<Transform>>,
+    excerpt_header_height: u8,
 }
 
 pub struct BlockMapWriter<'a>(&'a mut BlockMap);
@@ -89,7 +89,7 @@ struct Transform {
 }
 
 #[derive(Clone)]
-enum TransformBlock {
+pub enum TransformBlock {
     Custom {
         block: Arc<Block>,
         column: u32,
@@ -97,17 +97,24 @@ enum TransformBlock {
     ExcerptHeader {
         buffer: BufferSnapshot,
         range: Range<text::Anchor>,
-        path: Option<Arc<Path>>,
+        height: u8,
     },
 }
 
 impl TransformBlock {
     fn disposition(&self) -> BlockDisposition {
         match self {
-            TransformBlock::Custom { block, column } => block.disposition,
+            TransformBlock::Custom { block, .. } => block.disposition,
             TransformBlock::ExcerptHeader { .. } => BlockDisposition::Above,
         }
     }
+
+    fn height(&self) -> u8 {
+        match self {
+            TransformBlock::Custom { block, .. } => block.height,
+            TransformBlock::ExcerptHeader { height, .. } => *height,
+        }
+    }
 }
 
 impl Debug for TransformBlock {
@@ -118,9 +125,10 @@ impl Debug for TransformBlock {
                 .field("block", block)
                 .field("column", column)
                 .finish(),
-            Self::ExcerptHeader { buffer, path, .. } => {
-                f.debug_struct("ExcerptHeader").field("path", path).finish()
-            }
+            Self::ExcerptHeader { buffer, .. } => f
+                .debug_struct("ExcerptHeader")
+                .field("path", &buffer.path())
+                .finish(),
         }
     }
 }
@@ -147,7 +155,7 @@ pub struct BlockBufferRows<'a> {
 }
 
 impl BlockMap {
-    pub fn new(wrap_snapshot: WrapSnapshot) -> Self {
+    pub fn new(wrap_snapshot: WrapSnapshot, excerpt_header_height: u8) -> Self {
         Self {
             next_block_id: AtomicUsize::new(0),
             blocks: Vec::new(),
@@ -156,6 +164,7 @@ impl BlockMap {
                 &(),
             )),
             wrap_snapshot: Mutex::new(wrap_snapshot),
+            excerpt_header_height,
         }
     }
 
@@ -202,7 +211,7 @@ impl BlockMap {
                         if transform
                             .block
                             .as_ref()
-                            .map_or(false, |b| b.disposition.is_below())
+                            .map_or(false, |b| b.disposition().is_below())
                         {
                             new_transforms.push(transform.clone(), &());
                             cursor.next(&());
@@ -227,7 +236,7 @@ impl BlockMap {
                     if transform
                         .block
                         .as_ref()
-                        .map_or(false, |b| b.disposition.is_below())
+                        .map_or(false, |b| b.disposition().is_below())
                     {
                         cursor.next(&());
                     } else {
@@ -248,7 +257,7 @@ impl BlockMap {
                             if transform
                                 .block
                                 .as_ref()
-                                .map_or(false, |b| b.disposition.is_below())
+                                .map_or(false, |b| b.disposition().is_below())
                             {
                                 cursor.next(&());
                             } else {
@@ -328,7 +337,7 @@ impl BlockMap {
                             TransformBlock::ExcerptHeader {
                                 buffer: excerpt_boundary.buffer,
                                 range: excerpt_boundary.range,
-                                path: excerpt_boundary.path,
+                                height: self.excerpt_header_height,
                             },
                         )
                     }),
@@ -336,7 +345,23 @@ impl BlockMap {
 
             // When multiple blocks are on the same row, newer blocks appear above older
             // blocks. This is arbitrary, but we currently rely on it in ProjectDiagnosticsEditor.
-            blocks_in_edit.sort();
+            blocks_in_edit.sort_unstable_by(|(row_a, block_a), (row_b, block_b)| {
+                row_a.cmp(&row_b).then_with(|| match (block_a, block_b) {
+                    (
+                        TransformBlock::ExcerptHeader { .. },
+                        TransformBlock::ExcerptHeader { .. },
+                    ) => Ordering::Equal,
+                    (TransformBlock::ExcerptHeader { .. }, _) => Ordering::Less,
+                    (_, TransformBlock::ExcerptHeader { .. }) => Ordering::Greater,
+                    (
+                        TransformBlock::Custom { block: block_a, .. },
+                        TransformBlock::Custom { block: block_b, .. },
+                    ) => block_a
+                        .disposition
+                        .cmp(&block_b.disposition)
+                        .then_with(|| block_a.id.cmp(&block_b.id).reverse()),
+                })
+            });
 
             // For each of these blocks, insert a new isomorphic transform preceding the block,
             // and then insert the block itself.
@@ -577,7 +602,7 @@ impl BlockSnapshot {
     pub fn blocks_in_range<'a>(
         &'a self,
         rows: Range<u32>,
-    ) -> impl Iterator<Item = (u32, &'a AlignedBlock)> {
+    ) -> impl Iterator<Item = (u32, &'a TransformBlock)> {
         let mut cursor = self.transforms.cursor::<BlockRow>();
         cursor.seek(&BlockRow(rows.start), Bias::Right, &());
         std::iter::from_fn(move || {
@@ -697,7 +722,7 @@ impl BlockSnapshot {
         let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>();
         cursor.seek(&BlockRow(block_point.row), Bias::Right, &());
         if let Some(transform) = cursor.item() {
-            match transform.block.as_ref().map(|b| b.disposition) {
+            match transform.block.as_ref().map(|b| b.disposition()) {
                 Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0),
                 Some(BlockDisposition::Below) => {
                     let wrap_row = cursor.start().1 .0 - 1;
@@ -726,13 +751,13 @@ impl Transform {
         }
     }
 
-    fn block(block: Arc<Block>, column: u32) -> Self {
+    fn block(block: TransformBlock) -> Self {
         Self {
             summary: TransformSummary {
                 input_rows: 0,
-                output_rows: block.height as u32,
+                output_rows: block.height() as u32,
             },
-            block: Some(AlignedBlock { block, column }),
+            block: Some(block),
         }
     }
 
@@ -862,32 +887,6 @@ 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;
-
-    fn deref(&self) -> &Self::Target {
-        self.block.as_ref()
-    }
-}
-
 impl<'a> Deref for BlockContext<'a> {
     type Target = AppContext;
 
@@ -896,6 +895,12 @@ impl<'a> Deref for BlockContext<'a> {
     }
 }
 
+impl Block {
+    pub fn render(&self, cx: &BlockContext) -> ElementBox {
+        self.render.lock()(cx)
+    }
+}
+
 impl Debug for Block {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("Block")
@@ -931,6 +936,7 @@ mod tests {
     use crate::multi_buffer::MultiBuffer;
     use gpui::{elements::Empty, Element};
     use rand::prelude::*;
+    use std::cmp::Reverse;
     use std::env;
     use text::RandomCharIter;
 
@@ -964,7 +970,7 @@ mod tests {
         let (fold_map, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
         let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1);
         let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx);
-        let mut block_map = BlockMap::new(wraps_snapshot.clone());
+        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1);
 
         let mut writer = block_map.write(wraps_snapshot.clone(), vec![]);
         writer.insert(vec![
@@ -994,9 +1000,10 @@ mod tests {
         let blocks = snapshot
             .blocks_in_range(0..8)
             .map(|(start_row, block)| {
+                let (block, column) = block.as_custom().unwrap();
                 (
-                    start_row..start_row + block.height(),
-                    block.column(),
+                    start_row..start_row + block.height as u32,
+                    column,
                     block
                         .render(&BlockContext {
                             cx,
@@ -1142,7 +1149,7 @@ mod tests {
         let (_, folds_snapshot) = FoldMap::new(buffer_snapshot.clone());
         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(wraps_snapshot.clone());
+        let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1);
 
         let mut writer = block_map.write(wraps_snapshot.clone(), vec![]);
         writer.insert(vec![
@@ -1187,8 +1194,10 @@ mod tests {
             .select_font(family_id, &Default::default())
             .unwrap();
         let font_size = 14.0;
+        let excerpt_header_height = rng.gen_range(1..=5);
 
         log::info!("Wrap width: {:?}", wrap_width);
+        log::info!("Excerpt Header Height: {:?}", excerpt_header_height);
 
         let buffer = if rng.gen() {
             let len = rng.gen_range(0..10);
@@ -1204,8 +1213,8 @@ mod tests {
         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(wraps_snapshot);
-        let mut expected_blocks = Vec::new();
+        let mut block_map = BlockMap::new(wraps_snapshot, excerpt_header_height);
+        let mut custom_blocks = Vec::new();
 
         for _ in 0..operations {
             let mut buffer_edits = Vec::new();
@@ -1258,15 +1267,15 @@ mod tests {
                     let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
                     let block_ids = block_map.insert(block_properties.clone());
                     for (block_id, props) in block_ids.into_iter().zip(block_properties) {
-                        expected_blocks.push((block_id, props));
+                        custom_blocks.push((block_id, props));
                     }
                 }
-                40..=59 if !expected_blocks.is_empty() => {
-                    let block_count = rng.gen_range(1..=4.min(expected_blocks.len()));
+                40..=59 if !custom_blocks.is_empty() => {
+                    let block_count = rng.gen_range(1..=4.min(custom_blocks.len()));
                     let block_ids_to_remove = (0..block_count)
                         .map(|_| {
-                            expected_blocks
-                                .remove(rng.gen_range(0..expected_blocks.len()))
+                            custom_blocks
+                                .remove(rng.gen_range(0..custom_blocks.len()))
                                 .0
                         })
                         .collect();
@@ -1304,36 +1313,39 @@ mod tests {
             );
             log::info!("blocks text: {:?}", blocks_snapshot.text());
 
-            let mut sorted_blocks = expected_blocks
-                .iter()
-                .cloned()
-                .map(|(id, block)| {
-                    let mut position = block.position.to_point(&buffer_snapshot);
-                    let column = wraps_snapshot.from_point(position, Bias::Left).column();
-                    match block.disposition {
-                        BlockDisposition::Above => {
-                            position.column = 0;
-                        }
-                        BlockDisposition::Below => {
-                            position.column = buffer_snapshot.line_len(position.row);
-                        }
-                    };
-                    let row = wraps_snapshot.from_point(position, Bias::Left).row();
-                    (
-                        id,
-                        BlockProperties {
-                            position: BlockPoint::new(row, column),
-                            height: block.height,
-                            disposition: block.disposition,
-                            render: block.render.clone(),
-                        },
-                    )
-                })
-                .collect::<Vec<_>>();
-            sorted_blocks.sort_unstable_by_key(|(id, block)| {
-                (block.position.row, block.disposition, Reverse(*id))
+            let mut expected_blocks = Vec::new();
+            expected_blocks.extend(custom_blocks.iter().map(|(id, block)| {
+                let mut position = block.position.to_point(&buffer_snapshot);
+                let column = wraps_snapshot.from_point(position, Bias::Left).column();
+                match block.disposition {
+                    BlockDisposition::Above => {
+                        position.column = 0;
+                    }
+                    BlockDisposition::Below => {
+                        position.column = buffer_snapshot.line_len(position.row);
+                    }
+                };
+                let row = wraps_snapshot.from_point(position, Bias::Left).row();
+                (row, block.disposition, *id, block.height)
+            }));
+            expected_blocks.extend(
+                buffer_snapshot
+                    .excerpt_boundaries_in_range(0..buffer_snapshot.len())
+                    .map(|boundary| {
+                        let position =
+                            wraps_snapshot.from_point(Point::new(boundary.row, 0), Bias::Left);
+                        (
+                            position.row(),
+                            BlockDisposition::Above,
+                            BlockId(usize::MAX),
+                            excerpt_header_height,
+                        )
+                    }),
+            );
+            expected_blocks.sort_unstable_by_key(|(row, disposition, id, _)| {
+                (*row, *disposition, Reverse(*id))
             });
-            let mut sorted_blocks_iter = sorted_blocks.iter().peekable();
+            let mut sorted_blocks_iter = expected_blocks.iter().peekable();
 
             let input_buffer_rows = buffer_snapshot.buffer_rows(0).collect::<Vec<_>>();
             let mut expected_buffer_rows = Vec::new();
@@ -1350,13 +1362,13 @@ mod tests {
                     .to_point(WrapPoint::new(row, 0), Bias::Left)
                     .row as usize];
 
-                while let Some((block_id, block)) = sorted_blocks_iter.peek() {
-                    if block.position.row == row && block.disposition == BlockDisposition::Above {
+                while let Some((block_row, disposition, id, height)) = sorted_blocks_iter.peek() {
+                    if *block_row == row && *disposition == BlockDisposition::Above {
                         expected_block_positions
-                            .push((expected_text.matches('\n').count() as u32, *block_id));
-                        let text = "\n".repeat(block.height as usize);
+                            .push((expected_text.matches('\n').count() as u32, *id));
+                        let text = "\n".repeat(*height as usize);
                         expected_text.push_str(&text);
-                        for _ in 0..block.height {
+                        for _ in 0..*height {
                             expected_buffer_rows.push(None);
                         }
                         sorted_blocks_iter.next();
@@ -1369,13 +1381,13 @@ mod tests {
                 expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
                 expected_text.push_str(input_line);
 
-                while let Some((block_id, block)) = sorted_blocks_iter.peek() {
-                    if block.position.row == row && block.disposition == BlockDisposition::Below {
+                while let Some((block_row, disposition, id, height)) = sorted_blocks_iter.peek() {
+                    if *block_row == row && *disposition == BlockDisposition::Below {
                         expected_block_positions
-                            .push((expected_text.matches('\n').count() as u32 + 1, *block_id));
-                        let text = "\n".repeat(block.height as usize);
+                            .push((expected_text.matches('\n').count() as u32 + 1, *id));
+                        let text = "\n".repeat(*height as usize);
                         expected_text.push_str(&text);
-                        for _ in 0..block.height {
+                        for _ in 0..*height {
                             expected_buffer_rows.push(None);
                         }
                         sorted_blocks_iter.next();
@@ -1409,7 +1421,14 @@ mod tests {
             assert_eq!(
                 blocks_snapshot
                     .blocks_in_range(0..(expected_row_count as u32))
-                    .map(|(row, block)| (row, block.id))
+                    .map(|(row, block)| (
+                        row,
+                        if let Some((block, _)) = block.as_custom() {
+                            block.id
+                        } else {
+                            BlockId(usize::MAX)
+                        }
+                    ))
                     .collect::<Vec<_>>(),
                 expected_block_positions
             );
@@ -1490,6 +1509,15 @@ mod tests {
         }
     }
 
+    impl TransformBlock {
+        fn as_custom(&self) -> Option<(&Block, u32)> {
+            match self {
+                TransformBlock::Custom { block, column } => Some((block, *column)),
+                TransformBlock::ExcerptHeader { .. } => None,
+            }
+        }
+    }
+
     impl BlockSnapshot {
         fn to_point(&self, point: BlockPoint, bias: Bias) -> Point {
             self.wrap_snapshot.to_point(self.to_wrap_point(point), bias)

crates/editor/src/editor.rs πŸ”—

@@ -3,7 +3,6 @@ mod element;
 pub mod items;
 pub mod movement;
 mod multi_buffer;
-mod multi_editor;
 
 #[cfg(test)]
 mod test;
@@ -810,6 +809,7 @@ impl Editor {
                 settings.style.text.font_id,
                 settings.style.text.font_size,
                 None,
+                2,
                 cx,
             )
         });
@@ -2604,7 +2604,11 @@ impl Editor {
                     .0;
 
                 // Don't move lines across excerpts
-                if !buffer.range_contains_excerpt_boundary(insertion_point..range_to_move.end) {
+                if buffer
+                    .excerpt_boundaries_in_range(insertion_point..range_to_move.end)
+                    .next()
+                    .is_none()
+                {
                     let text = buffer
                         .text_for_range(range_to_move.clone())
                         .flat_map(|s| s.chars())
@@ -2704,7 +2708,11 @@ impl Editor {
                 let insertion_point = display_map.next_line_boundary(Point::new(end_row, 0)).0;
 
                 // Don't move lines across excerpt boundaries
-                if !buffer.range_contains_excerpt_boundary(range_to_move.start..insertion_point) {
+                if buffer
+                    .excerpt_boundaries_in_range(range_to_move.start..insertion_point)
+                    .next()
+                    .is_none()
+                {
                     let mut text = String::from("\n");
                     text.extend(buffer.text_for_range(range_to_move.clone()));
                     text.pop(); // Drop trailing newline

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

@@ -649,43 +649,44 @@ impl EditorElement {
         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 = text_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,
-                    gutter_padding,
-                    line_height,
-                    scroll_x: snapshot.scroll_position.x(),
-                    gutter_width,
-                    em_width,
-                });
-                element.layout(
-                    SizeConstraint {
-                        min: Vector2F::zero(),
-                        max: vec2f(width, block.height() as f32 * line_height),
-                    },
-                    cx,
-                );
-                (start_row, element)
-            })
-            .collect()
+        Default::default()
+        // 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 = text_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,
+        //             gutter_padding,
+        //             line_height,
+        //             scroll_x: snapshot.scroll_position.x(),
+        //             gutter_width,
+        //             em_width,
+        //         });
+        //         element.layout(
+        //             SizeConstraint {
+        //                 min: Vector2F::zero(),
+        //                 max: vec2f(width, block.height() as f32 * line_height),
+        //             },
+        //             cx,
+        //         );
+        //         (start_row, element)
+        //     })
+        //     .collect()
     }
 }
 

crates/editor/src/movement.rs πŸ”—

@@ -225,13 +225,8 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range<
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{
-        display_map::{BlockDisposition, BlockProperties},
-        Buffer, DisplayMap, ExcerptProperties, MultiBuffer,
-    };
-    use gpui::{elements::Empty, Element};
+    use crate::{Buffer, DisplayMap, ExcerptProperties, MultiBuffer};
     use language::Point;
-    use std::sync::Arc;
 
     #[gpui::test]
     fn test_move_up_and_down_with_excerpts(cx: &mut gpui::MutableAppContext) {
@@ -242,59 +237,27 @@ mod tests {
             .unwrap();
 
         let buffer = cx.add_model(|cx| Buffer::new(0, "abc\ndefg\nhijkl\nmn", cx));
-        let mut excerpt1_header_position = None;
-        let mut excerpt2_header_position = None;
         let multibuffer = cx.add_model(|cx| {
             let mut multibuffer = MultiBuffer::new(0);
-            let excerpt1_id = multibuffer.push_excerpt(
+            multibuffer.push_excerpt(
                 ExcerptProperties {
                     buffer: &buffer,
                     range: Point::new(0, 0)..Point::new(1, 4),
                 },
                 cx,
             );
-            let excerpt2_id = multibuffer.push_excerpt(
+            multibuffer.push_excerpt(
                 ExcerptProperties {
                     buffer: &buffer,
                     range: Point::new(2, 0)..Point::new(3, 2),
                 },
                 cx,
             );
-
-            excerpt1_header_position = Some(
-                multibuffer
-                    .read(cx)
-                    .anchor_in_excerpt(excerpt1_id, language::Anchor::min()),
-            );
-            excerpt2_header_position = Some(
-                multibuffer
-                    .read(cx)
-                    .anchor_in_excerpt(excerpt2_id, language::Anchor::min()),
-            );
             multibuffer
         });
 
         let display_map =
-            cx.add_model(|cx| DisplayMap::new(multibuffer, 2, font_id, 14.0, None, cx));
-        display_map.update(cx, |display_map, cx| {
-            display_map.insert_blocks(
-                [
-                    BlockProperties {
-                        position: excerpt1_header_position.unwrap(),
-                        height: 2,
-                        render: Arc::new(|_| Empty::new().boxed()),
-                        disposition: BlockDisposition::Above,
-                    },
-                    BlockProperties {
-                        position: excerpt2_header_position.unwrap(),
-                        height: 3,
-                        render: Arc::new(|_| Empty::new().boxed()),
-                        disposition: BlockDisposition::Above,
-                    },
-                ],
-                cx,
-            )
-        });
+            cx.add_model(|cx| DisplayMap::new(multibuffer, 2, font_id, 14.0, None, 2, cx));
 
         let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
         assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\n\nhijkl\nmn");
@@ -352,7 +315,7 @@ mod tests {
 
         let buffer = MultiBuffer::build_simple("a bcΞ” defΞ³ hiβ€”jk", cx);
         let display_map =
-            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
+            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, cx));
         let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
         assert_eq!(
             prev_word_boundary(&snapshot, DisplayPoint::new(0, 12)),
@@ -408,7 +371,7 @@ mod tests {
         let font_size = 14.0;
         let buffer = MultiBuffer::build_simple("lorem ipsum   dolor\n    sit", cx);
         let display_map =
-            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx));
+            cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, cx));
         let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 
         assert_eq!(

crates/editor/src/multi_buffer.rs πŸ”—

@@ -15,7 +15,6 @@ use std::{
     cmp, fmt, io,
     iter::{self, FromIterator},
     ops::{Range, Sub},
-    path::Path,
     str,
     sync::Arc,
     time::{Duration, Instant},
@@ -83,6 +82,7 @@ struct BufferState {
     last_parse_count: usize,
     last_selections_update_count: usize,
     last_diagnostics_update_count: usize,
+    last_file_update_count: usize,
     excerpts: Vec<ExcerptId>,
     _subscriptions: [gpui::Subscription; 2],
 }
@@ -105,7 +105,6 @@ pub struct ExcerptProperties<'a, T> {
 pub struct ExcerptBoundary {
     pub row: u32,
     pub buffer: BufferSnapshot,
-    pub path: Option<Arc<Path>>,
     pub range: Range<text::Anchor>,
     pub starts_new_buffer: bool,
 }
@@ -679,6 +678,7 @@ impl MultiBuffer {
                 last_parse_count: buffer_snapshot.parse_count(),
                 last_selections_update_count: buffer_snapshot.selections_update_count(),
                 last_diagnostics_update_count: buffer_snapshot.diagnostics_update_count(),
+                last_file_update_count: buffer_snapshot.file_update_count(),
                 excerpts: Default::default(),
                 _subscriptions: [
                     cx.observe(&props.buffer, |_, _, cx| cx.notify()),
@@ -929,6 +929,7 @@ impl MultiBuffer {
             let parse_count = buffer.parse_count();
             let selections_update_count = buffer.selections_update_count();
             let diagnostics_update_count = buffer.diagnostics_update_count();
+            let file_update_count = buffer.file_update_count();
 
             let buffer_edited = version.changed_since(&buffer_state.last_version);
             let buffer_reparsed = parse_count > buffer_state.last_parse_count;
@@ -936,15 +937,18 @@ impl MultiBuffer {
                 selections_update_count > buffer_state.last_selections_update_count;
             let buffer_diagnostics_updated =
                 diagnostics_update_count > buffer_state.last_diagnostics_update_count;
+            let buffer_file_updated = file_update_count > buffer_state.last_file_update_count;
             if buffer_edited
                 || buffer_reparsed
                 || buffer_selections_updated
                 || buffer_diagnostics_updated
+                || buffer_file_updated
             {
                 buffer_state.last_version = version;
                 buffer_state.last_parse_count = parse_count;
                 buffer_state.last_selections_update_count = selections_update_count;
                 buffer_state.last_diagnostics_update_count = diagnostics_update_count;
+                buffer_state.last_file_update_count = file_update_count;
                 excerpts_to_edit.extend(
                     buffer_state
                         .excerpts
@@ -1761,38 +1765,35 @@ impl MultiBufferSnapshot {
         }
     }
 
-    pub fn range_contains_excerpt_boundary<T: ToOffset>(&self, range: Range<T>) -> bool {
-        let start = range.start.to_offset(self);
-        let end = range.end.to_offset(self);
-        let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>();
-        cursor.seek(&start, Bias::Right, &());
-        let start_id = cursor
-            .item()
-            .or_else(|| cursor.prev_item())
-            .map(|excerpt| &excerpt.id);
-        cursor.seek_forward(&end, Bias::Right, &());
-        let end_id = cursor
-            .item()
-            .or_else(|| cursor.prev_item())
-            .map(|excerpt| &excerpt.id);
-        start_id != end_id
-    }
-
     pub fn excerpt_boundaries_in_range<'a, T: ToOffset>(
         &'a self,
         range: Range<T>,
     ) -> impl Iterator<Item = ExcerptBoundary> + 'a {
         let start = range.start.to_offset(self);
         let end = range.end.to_offset(self);
-        let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>();
+        let mut cursor = self
+            .excerpts
+            .cursor::<(usize, (Option<&ExcerptId>, Point))>();
         cursor.seek(&start, Bias::Right, &());
 
-        let prev_buffer_id = cursor.prev_item().map(|excerpt| excerpt.buffer_id);
-
+        let mut prev_buffer_id = cursor.prev_item().map(|excerpt| excerpt.buffer_id);
         std::iter::from_fn(move || {
-            let excerpt = cursor.item()?;
-            let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id;
-            todo!()
+            if start <= cursor.start().0 && end > cursor.start().0 {
+                let excerpt = cursor.item()?;
+                let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id;
+                let boundary = ExcerptBoundary {
+                    row: cursor.start().1 .1.row,
+                    buffer: excerpt.buffer.clone(),
+                    range: excerpt.range.clone(),
+                    starts_new_buffer,
+                };
+
+                prev_buffer_id = Some(excerpt.buffer_id);
+                cursor.next(&());
+                Some(boundary)
+            } else {
+                None
+            }
         })
     }
 
@@ -2648,21 +2649,50 @@ mod tests {
         );
         assert_eq!(snapshot.buffer_rows(4).collect::<Vec<_>>(), [Some(3)]);
         assert_eq!(snapshot.buffer_rows(5).collect::<Vec<_>>(), []);
-        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(1, 5)));
-        assert!(snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(2, 0)));
-        assert!(snapshot.range_contains_excerpt_boundary(Point::new(1, 0)..Point::new(4, 0)));
-        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(2, 0)..Point::new(3, 0)));
-        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(4, 0)..Point::new(4, 2)));
-        assert!(!snapshot.range_contains_excerpt_boundary(Point::new(4, 2)..Point::new(4, 2)));
+        assert!(snapshot
+            .excerpt_boundaries_in_range(Point::new(1, 0)..Point::new(1, 5))
+            .next()
+            .is_none());
+        assert!(snapshot
+            .excerpt_boundaries_in_range(Point::new(1, 0)..Point::new(2, 0))
+            .next()
+            .is_some());
+        assert!(snapshot
+            .excerpt_boundaries_in_range(Point::new(1, 0)..Point::new(4, 0))
+            .next()
+            .is_some());
+        assert!(snapshot
+            .excerpt_boundaries_in_range(Point::new(2, 0)..Point::new(3, 0))
+            .next()
+            .is_none());
+        assert!(snapshot
+            .excerpt_boundaries_in_range(Point::new(4, 0)..Point::new(4, 2))
+            .next()
+            .is_none());
+        assert!(snapshot
+            .excerpt_boundaries_in_range(Point::new(4, 2)..Point::new(4, 2))
+            .next()
+            .is_none());
 
         assert_eq!(
             snapshot
                 .excerpt_boundaries_in_range(Point::new(0, 0)..Point::new(4, 2))
+                .map(|boundary| (
+                    boundary.row,
+                    boundary
+                        .buffer
+                        .text_for_range(boundary.range)
+                        .collect::<String>(),
+                    boundary.starts_new_buffer
+                ))
                 .collect::<Vec<_>>(),
             &[
-                (Some(buffer_1.clone()), true),
-                (Some(buffer_1.clone()), false),
-                (Some(buffer_2.clone()), false),
+                (0, "".to_string(), true),
+                (0, "".to_string(), true),
+                (0, "".to_string(), true),
+                (0, "".to_string(), true),
+                (0, "".to_string(), true),
+                (0, "".to_string(), true),
             ]
         );
 

crates/language/src/buffer.rs πŸ”—

@@ -68,6 +68,7 @@ pub struct Buffer {
     remote_selections: TreeMap<ReplicaId, SelectionSet>,
     selections_update_count: usize,
     diagnostics_update_count: usize,
+    file_update_count: usize,
     language_server: Option<LanguageServerState>,
     completion_triggers: Vec<String>,
     deferred_ops: OperationQueue<Operation>,
@@ -78,8 +79,10 @@ pub struct Buffer {
 pub struct BufferSnapshot {
     text: text::BufferSnapshot,
     tree: Option<Tree>,
+    path: Option<Arc<Path>>,
     diagnostics: DiagnosticSet,
     diagnostics_update_count: usize,
+    file_update_count: usize,
     remote_selections: TreeMap<ReplicaId, SelectionSet>,
     selections_update_count: usize,
     is_parsing: bool,
@@ -498,6 +501,7 @@ impl Buffer {
             selections_update_count: 0,
             diagnostics: Default::default(),
             diagnostics_update_count: 0,
+            file_update_count: 0,
             language_server: None,
             completion_triggers: Default::default(),
             deferred_ops: OperationQueue::new(),
@@ -510,9 +514,11 @@ impl Buffer {
         BufferSnapshot {
             text: self.text.snapshot(),
             tree: self.syntax_tree(),
+            path: self.file.as_ref().map(|f| f.path().clone()),
             remote_selections: self.remote_selections.clone(),
             diagnostics: self.diagnostics.clone(),
             diagnostics_update_count: self.diagnostics_update_count,
+            file_update_count: self.file_update_count,
             is_parsing: self.parsing_in_background,
             language: self.language.clone(),
             parse_count: self.parse_count,
@@ -722,6 +728,7 @@ impl Buffer {
         self.saved_version = version;
         if let Some(new_file) = new_file {
             self.file = Some(new_file);
+            self.file_update_count += 1;
         }
         if let Some((state, local_file)) = &self
             .language_server
@@ -744,6 +751,7 @@ impl Buffer {
                 .detach()
         }
         cx.emit(Event::Saved);
+        cx.notify();
     }
 
     pub fn did_reload(
@@ -819,7 +827,9 @@ impl Buffer {
         }
 
         if file_changed {
+            self.file_update_count += 1;
             cx.emit(Event::FileHandleChanged);
+            cx.notify();
         }
         self.file = Some(new_file);
         task
@@ -849,6 +859,10 @@ impl Buffer {
         self.diagnostics_update_count
     }
 
+    pub fn file_update_count(&self) -> usize {
+        self.file_update_count
+    }
+
     pub(crate) fn syntax_tree(&self) -> Option<Tree> {
         if let Some(syntax_tree) = self.syntax_tree.lock().as_mut() {
             self.interpolate_tree(syntax_tree);
@@ -2231,6 +2245,14 @@ impl BufferSnapshot {
     pub fn selections_update_count(&self) -> usize {
         self.selections_update_count
     }
+
+    pub fn path(&self) -> Option<&Arc<Path>> {
+        self.path.as_ref()
+    }
+
+    pub fn file_update_count(&self) -> usize {
+        self.file_update_count
+    }
 }
 
 impl Clone for BufferSnapshot {
@@ -2238,10 +2260,12 @@ impl Clone for BufferSnapshot {
         Self {
             text: self.text.clone(),
             tree: self.tree.clone(),
+            path: self.path.clone(),
             remote_selections: self.remote_selections.clone(),
             diagnostics: self.diagnostics.clone(),
             selections_update_count: self.selections_update_count,
             diagnostics_update_count: self.diagnostics_update_count,
+            file_update_count: self.file_update_count,
             is_parsing: self.is_parsing,
             language: self.language.clone(),
             parse_count: self.parse_count,