Render path headers in editor element

Antonio Scandurra and Nathan Sobo created

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

Change summary

crates/diagnostics/src/diagnostics.rs      | 106 ++++++---------------
crates/editor/src/display_map/block_map.rs |  51 +++------
crates/editor/src/display_map/wrap_map.rs  |   8 -
crates/editor/src/element.rs               | 119 ++++++++++++++++-------
4 files changed, 128 insertions(+), 156 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -423,25 +423,16 @@ impl ProjectDiagnosticsEditor {
         self.editor.update(cx, |editor, cx| {
             blocks_to_remove.extend(path_state.header);
             editor.remove_blocks(blocks_to_remove, cx);
-            let header_block = first_excerpt_id.map(|excerpt_id| BlockProperties {
-                position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, language::Anchor::min()),
-                height: 2,
-                render: path_header_renderer(buffer, self.build_settings.clone()),
-                disposition: BlockDisposition::Above,
-            });
             let block_ids = editor.insert_blocks(
-                blocks_to_add
-                    .into_iter()
-                    .map(|block| {
-                        let (excerpt_id, text_anchor) = block.position;
-                        BlockProperties {
-                            position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
-                            height: block.height,
-                            render: block.render,
-                            disposition: block.disposition,
-                        }
-                    })
-                    .chain(header_block.into_iter()),
+                blocks_to_add.into_iter().map(|block| {
+                    let (excerpt_id, text_anchor) = block.position;
+                    BlockProperties {
+                        position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
+                        height: block.height,
+                        render: block.render,
+                        disposition: block.disposition,
+                    }
+                }),
                 cx,
             );
 
@@ -660,51 +651,6 @@ impl workspace::ItemView for ProjectDiagnosticsEditor {
     }
 }
 
-fn path_header_renderer(buffer: ModelHandle<Buffer>, build_settings: BuildSettings) -> RenderBlock {
-    Arc::new(move |cx| {
-        let settings = build_settings(cx);
-        let style = settings.style.diagnostic_path_header;
-        let font_size = (style.text_scale_factor * settings.style.text.font_size).round();
-
-        let mut filename = None;
-        let mut path = None;
-        if let Some(file) = buffer.read(&**cx).file() {
-            filename = file
-                .path()
-                .file_name()
-                .map(|f| f.to_string_lossy().to_string());
-            path = file
-                .path()
-                .parent()
-                .map(|p| p.to_string_lossy().to_string() + "/");
-        }
-
-        Flex::row()
-            .with_child(
-                Label::new(
-                    filename.unwrap_or_else(|| "untitled".to_string()),
-                    style.filename.text.clone().with_font_size(font_size),
-                )
-                .contained()
-                .with_style(style.filename.container)
-                .boxed(),
-            )
-            .with_children(path.map(|path| {
-                Label::new(path, style.path.text.clone().with_font_size(font_size))
-                    .contained()
-                    .with_style(style.path.container)
-                    .boxed()
-            }))
-            .aligned()
-            .left()
-            .contained()
-            .with_style(style.container)
-            .with_padding_left(cx.gutter_padding + cx.scroll_x * cx.em_width)
-            .expanded()
-            .named("path header block")
-    })
-}
-
 fn diagnostic_header_renderer(
     diagnostic: Diagnostic,
     build_settings: BuildSettings,
@@ -843,7 +789,10 @@ fn compare_diagnostics<L: language::ToOffset, R: language::ToOffset>(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use editor::{display_map::BlockContext, DisplayPoint, EditorSnapshot};
+    use editor::{
+        display_map::{BlockContext, TransformBlock},
+        DisplayPoint, EditorSnapshot,
+    };
     use gpui::TestAppContext;
     use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16};
     use serde_json::json;
@@ -1259,18 +1208,23 @@ mod tests {
         editor
             .blocks_in_range(0..editor.max_point().row())
             .filter_map(|(row, block)| {
-                block
-                    .render(&BlockContext {
-                        cx,
-                        anchor_x: 0.,
-                        scroll_x: 0.,
-                        gutter_padding: 0.,
-                        gutter_width: 0.,
-                        line_height: 0.,
-                        em_width: 0.,
-                    })
-                    .name()
-                    .map(|s| (row, s.to_string()))
+                let name = match block {
+                    TransformBlock::Custom(block) => block
+                        .render(&BlockContext {
+                            cx,
+                            anchor_x: 0.,
+                            scroll_x: 0.,
+                            gutter_padding: 0.,
+                            gutter_width: 0.,
+                            line_height: 0.,
+                            em_width: 0.,
+                        })
+                        .name()?
+                        .to_string(),
+                    TransformBlock::ExcerptHeader { .. } => "path header block".to_string(),
+                };
+
+                Some((row, name))
             })
             .collect()
     }

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

@@ -90,10 +90,7 @@ struct Transform {
 
 #[derive(Clone)]
 pub enum TransformBlock {
-    Custom {
-        block: Arc<Block>,
-        column: u32,
-    },
+    Custom(Arc<Block>),
     ExcerptHeader {
         buffer: BufferSnapshot,
         range: Range<text::Anchor>,
@@ -104,14 +101,14 @@ pub enum TransformBlock {
 impl TransformBlock {
     fn disposition(&self) -> BlockDisposition {
         match self {
-            TransformBlock::Custom { block, .. } => block.disposition,
+            TransformBlock::Custom(block) => block.disposition,
             TransformBlock::ExcerptHeader { .. } => BlockDisposition::Above,
         }
     }
 
-    fn height(&self) -> u8 {
+    pub fn height(&self) -> u8 {
         match self {
-            TransformBlock::Custom { block, .. } => block.height,
+            TransformBlock::Custom(block) => block.height,
             TransformBlock::ExcerptHeader { height, .. } => *height,
         }
     }
@@ -120,11 +117,7 @@ impl TransformBlock {
 impl Debug for TransformBlock {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            Self::Custom { block, column } => f
-                .debug_struct("Custom")
-                .field("block", block)
-                .field("column", column)
-                .finish(),
+            Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
             Self::ExcerptHeader { buffer, .. } => f
                 .debug_struct("ExcerptHeader")
                 .field("path", &buffer.path())
@@ -319,7 +312,6 @@ impl BlockMap {
                     .iter()
                     .map(|block| {
                         let mut position = block.position.to_point(&buffer);
-                        let column = wrap_snapshot.from_point(position, Bias::Left).column();
                         match block.disposition {
                             BlockDisposition::Above => position.column = 0,
                             BlockDisposition::Below => {
@@ -327,13 +319,7 @@ impl BlockMap {
                             }
                         }
                         let position = wrap_snapshot.from_point(position, Bias::Left);
-                        (
-                            position.row(),
-                            TransformBlock::Custom {
-                                block: block.clone(),
-                                column,
-                            },
-                        )
+                        (position.row(), TransformBlock::Custom(block.clone()))
                     }),
             );
             blocks_in_edit.extend(
@@ -362,10 +348,7 @@ impl BlockMap {
                     ) => Ordering::Equal,
                     (TransformBlock::ExcerptHeader { .. }, _) => Ordering::Less,
                     (_, TransformBlock::ExcerptHeader { .. }) => Ordering::Greater,
-                    (
-                        TransformBlock::Custom { block: block_a, .. },
-                        TransformBlock::Custom { block: block_b, .. },
-                    ) => block_a
+                    (TransformBlock::Custom(block_a), TransformBlock::Custom(block_b)) => block_a
                         .disposition
                         .cmp(&block_b.disposition)
                         .then_with(|| block_a.id.cmp(&block_b.id)),
@@ -908,6 +891,10 @@ impl Block {
     pub fn render(&self, cx: &BlockContext) -> ElementBox {
         self.render.lock()(cx)
     }
+
+    pub fn position(&self) -> &Anchor {
+        &self.position
+    }
 }
 
 impl Debug for Block {
@@ -1008,10 +995,9 @@ mod tests {
         let blocks = snapshot
             .blocks_in_range(0..8)
             .map(|(start_row, block)| {
-                let (block, column) = block.as_custom().unwrap();
+                let block = block.as_custom().unwrap();
                 (
                     start_row..start_row + block.height as u32,
-                    column,
                     block
                         .render(&BlockContext {
                             cx,
@@ -1033,9 +1019,9 @@ mod tests {
         assert_eq!(
             blocks,
             &[
-                (1..3, 2, "block 2".to_string()),
-                (3..4, 0, "block 1".to_string()),
-                (7..10, 3, "block 3".to_string()),
+                (1..2, "block 1".to_string()),
+                (2..4, "block 2".to_string()),
+                (7..10, "block 3".to_string()),
             ]
         );
 
@@ -1324,7 +1310,6 @@ mod tests {
             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;
@@ -1426,7 +1411,7 @@ mod tests {
             assert_eq!(
                 blocks_snapshot
                     .blocks_in_range(0..(expected_row_count as u32))
-                    .map(|(row, block)| (row, block.as_custom().map(|(b, _)| b.id)))
+                    .map(|(row, block)| { (row, block.as_custom().map(|b| b.id)) })
                     .collect::<Vec<_>>(),
                 expected_block_positions
             );
@@ -1508,9 +1493,9 @@ mod tests {
     }
 
     impl TransformBlock {
-        fn as_custom(&self) -> Option<(&Block, u32)> {
+        fn as_custom(&self) -> Option<&Block> {
             match self {
-                TransformBlock::Custom { block, column } => Some((block, *column)),
+                TransformBlock::Custom(block) => Some(block),
                 TransformBlock::ExcerptHeader { .. } => None,
             }
         }

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

@@ -588,10 +588,6 @@ impl WrapSnapshot {
         }
     }
 
-    pub fn text_summary(&self) -> TextSummary {
-        self.transforms.summary().output
-    }
-
     pub fn max_point(&self) -> WrapPoint {
         WrapPoint(self.transforms.summary().output.lines)
     }
@@ -955,10 +951,6 @@ impl WrapPoint {
         &mut self.0.row
     }
 
-    pub fn column(&self) -> u32 {
-        self.0.column
-    }
-
     pub fn column_mut(&mut self) -> &mut u32 {
         &mut self.0.column
     }

crates/editor/src/element.rs 🔗

@@ -3,11 +3,12 @@ use super::{
     Anchor, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, Input,
     Scroll, Select, SelectPhase, SoftWrap, ToPoint, MAX_LINE_LEN,
 };
+use crate::display_map::TransformBlock;
 use clock::ReplicaId;
 use collections::{BTreeMap, HashMap};
 use gpui::{
     color::Color,
-    elements::layout_highlighted_chunks,
+    elements::*,
     fonts::{HighlightStyle, Underline},
     geometry::{
         rect::RectF,
@@ -649,44 +650,84 @@ impl EditorElement {
         line_layouts: &[text_layout::Line],
         cx: &mut LayoutContext,
     ) -> Vec<(u32, ElementBox)> {
-        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()
+        let scroll_x = snapshot.scroll_position.x();
+        snapshot
+            .blocks_in_range(rows.clone())
+            .map(|(block_row, block)| {
+                let mut element = match block {
+                    TransformBlock::Custom(block) => {
+                        let align_to = block
+                            .position()
+                            .to_point(&snapshot.buffer_snapshot)
+                            .to_display_point(snapshot);
+                        let anchor_x = text_x
+                            + if rows.contains(&align_to.row()) {
+                                line_layouts[(align_to.row() - rows.start) as usize]
+                                    .x_for_index(align_to.column() as usize)
+                            } else {
+                                layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
+                                    .x_for_index(align_to.column() as usize)
+                            };
+
+                        block.render(&BlockContext {
+                            cx,
+                            anchor_x,
+                            gutter_padding,
+                            line_height,
+                            scroll_x,
+                            gutter_width,
+                            em_width,
+                        })
+                    }
+                    TransformBlock::ExcerptHeader { buffer, .. } => {
+                        let style = &self.settings.style.diagnostic_path_header;
+                        let font_size =
+                            (style.text_scale_factor * self.settings.style.text.font_size).round();
+
+                        let mut filename = None;
+                        let mut parent_path = None;
+                        if let Some(path) = buffer.path() {
+                            filename = path.file_name().map(|f| f.to_string_lossy().to_string());
+                            parent_path =
+                                path.parent().map(|p| p.to_string_lossy().to_string() + "/");
+                        }
+
+                        Flex::row()
+                            .with_child(
+                                Label::new(
+                                    filename.unwrap_or_else(|| "untitled".to_string()),
+                                    style.filename.text.clone().with_font_size(font_size),
+                                )
+                                .contained()
+                                .with_style(style.filename.container)
+                                .boxed(),
+                            )
+                            .with_children(parent_path.map(|path| {
+                                Label::new(path, style.path.text.clone().with_font_size(font_size))
+                                    .contained()
+                                    .with_style(style.path.container)
+                                    .boxed()
+                            }))
+                            .aligned()
+                            .left()
+                            .contained()
+                            .with_style(style.container)
+                            .with_padding_left(gutter_padding + scroll_x * em_width)
+                            .expanded()
+                            .named("path header block")
+                    }
+                };
+
+                element.layout(
+                    SizeConstraint {
+                        min: Vector2F::zero(),
+                        max: vec2f(width, block.height() as f32 * line_height),
+                    },
+                    cx,
+                );
+                (block_row, element)
+            })
+            .collect()
     }
 }