Render block elements

Antonio Scandurra and Julia created

Co-Authored-By: Julia <julia@zed.dev>

Change summary

crates/editor2/src/editor.rs         | 139 ++++++++++++-----------------
crates/editor2/src/element.rs        | 138 ++++++++++++++--------------
crates/ui2/src/components/tooltip.rs |   6 
3 files changed, 133 insertions(+), 150 deletions(-)

Detailed changes

crates/editor2/src/editor.rs 🔗

@@ -22,7 +22,7 @@ mod editor_tests;
 pub mod test;
 use ::git::diff::DiffHunk;
 use aho_corasick::AhoCorasick;
-use anyhow::{Context as _, Result};
+use anyhow::{anyhow, Context as _, Result};
 use blink_manager::BlinkManager;
 use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings};
 use clock::ReplicaId;
@@ -43,8 +43,8 @@ use gpui::{
     AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
     EventEmitter, FocusHandle, FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla,
     InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
-    StatelessInteractive, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
-    ViewContext, VisualContext, WeakView, WindowContext,
+    StatefulInteractive, StatelessInteractive, Styled, Subscription, Task, TextStyle,
+    UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -69,7 +69,7 @@ pub use multi_buffer::{
 };
 use ordered_float::OrderedFloat;
 use parking_lot::{Mutex, RwLock};
-use project::{FormatTrigger, Location, Project, ProjectTransaction};
+use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
 use rand::prelude::*;
 use rpc::proto::*;
 use scroll::{
@@ -97,7 +97,7 @@ use text::{OffsetUtf16, Rope};
 use theme::{
     ActiveTheme, DiagnosticStyle, PlayerColor, SyntaxTheme, Theme, ThemeColors, ThemeSettings,
 };
-use ui::{IconButton, StyledExt};
+use ui::{v_stack, HighlightedLabel, IconButton, StyledExt, TextTooltip};
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::{
     item::ItemEvent, searchable::SearchEvent, ItemNavHistory, SplitDirection, ViewId, Workspace,
@@ -8869,46 +8869,50 @@ impl Editor {
     //         });
     //     }
 
-    //     fn jump(
-    //         workspace: &mut Workspace,
-    //         path: ProjectPath,
-    //         position: Point,
-    //         anchor: language::Anchor,
-    //         cx: &mut ViewContext<Workspace>,
-    //     ) {
-    //         let editor = workspace.open_path(path, None, true, cx);
-    //         cx.spawn(|_, mut cx| async move {
-    //             let editor = editor
-    //                 .await?
-    //                 .downcast::<Editor>()
-    //                 .ok_or_else(|| anyhow!("opened item was not an editor"))?
-    //                 .downgrade();
-    //             editor.update(&mut cx, |editor, cx| {
-    //                 let buffer = editor
-    //                     .buffer()
-    //                     .read(cx)
-    //                     .as_singleton()
-    //                     .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
-    //                 let buffer = buffer.read(cx);
-    //                 let cursor = if buffer.can_resolve(&anchor) {
-    //                     language::ToPoint::to_point(&anchor, buffer)
-    //                 } else {
-    //                     buffer.clip_point(position, Bias::Left)
-    //                 };
+    fn jump(
+        &mut self,
+        path: ProjectPath,
+        position: Point,
+        anchor: language::Anchor,
+        cx: &mut ViewContext<Self>,
+    ) {
+        let workspace = self.workspace();
+        cx.spawn(|_, mut cx| async move {
+            let workspace = workspace.ok_or_else(|| anyhow!("cannot jump without workspace"))?;
+            let editor = workspace.update(&mut cx, |workspace, cx| {
+                workspace.open_path(path, None, true, cx)
+            })?;
+            let editor = editor
+                .await?
+                .downcast::<Editor>()
+                .ok_or_else(|| anyhow!("opened item was not an editor"))?
+                .downgrade();
+            editor.update(&mut cx, |editor, cx| {
+                let buffer = editor
+                    .buffer()
+                    .read(cx)
+                    .as_singleton()
+                    .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
+                let buffer = buffer.read(cx);
+                let cursor = if buffer.can_resolve(&anchor) {
+                    language::ToPoint::to_point(&anchor, buffer)
+                } else {
+                    buffer.clip_point(position, Bias::Left)
+                };
 
-    //                 let nav_history = editor.nav_history.take();
-    //                 editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
-    //                     s.select_ranges([cursor..cursor]);
-    //                 });
-    //                 editor.nav_history = nav_history;
+                let nav_history = editor.nav_history.take();
+                editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
+                    s.select_ranges([cursor..cursor]);
+                });
+                editor.nav_history = nav_history;
 
-    //                 anyhow::Ok(())
-    //             })??;
+                anyhow::Ok(())
+            })??;
 
-    //             anyhow::Ok(())
-    //         })
-    //         .detach_and_log_err(cx);
-    //     }
+            anyhow::Ok(())
+        })
+        .detach_and_log_err(cx);
+    }
 
     fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
         let snapshot = self.buffer.read(cx).read(cx);
@@ -9973,43 +9977,20 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
     }
     let message = diagnostic.message;
     Arc::new(move |cx: &mut BlockContext| {
-        todo!()
-        // let message = message.clone();
-        // let settings = ThemeSettings::get_global(cx);
-        // let tooltip_style = settings.theme.tooltip.clone();
-        // let theme = &settings.theme.editor;
-        // let style = diagnostic_style(diagnostic.severity, is_valid, theme);
-        // let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
-        // let anchor_x = cx.anchor_x;
-        // enum BlockContextToolip {}
-        // MouseEventHandler::new::<BlockContext, _>(cx.block_id, cx, |_, _| {
-        //     Flex::column()
-        //         .with_children(highlighted_lines.iter().map(|(line, highlights)| {
-        //             Label::new(
-        //                 line.clone(),
-        //                 style.message.clone().with_font_size(font_size),
-        //             )
-        //             .with_highlights(highlights.clone())
-        //             .contained()
-        //             .with_margin_left(anchor_x)
-        //         }))
-        //         .aligned()
-        //         .left()
-        //         .into_any()
-        // })
-        // .with_cursor_style(CursorStyle::PointingHand)
-        // .on_click(MouseButton::Left, move |_, _, cx| {
-        //     cx.write_to_clipboard(ClipboardItem::new(message.clone()));
-        // })
-        // // We really need to rethink this ID system...
-        // .with_tooltip::<BlockContextToolip>(
-        //     cx.block_id,
-        //     "Copy diagnostic message",
-        //     None,
-        //     tooltip_style,
-        //     cx,
-        // )
-        // .into_any()
+        let message = message.clone();
+        v_stack()
+            .id(cx.block_id)
+            .children(highlighted_lines.iter().map(|(line, highlights)| {
+                div()
+                    .child(HighlightedLabel::new(line.clone(), highlights.clone()))
+                    .ml(cx.anchor_x)
+            }))
+            .cursor_pointer()
+            .on_click(move |_, _, cx| {
+                cx.write_to_clipboard(ClipboardItem::new(message.clone()));
+            })
+            .tooltip(|_, cx| cx.build_view(|cx| TextTooltip::new("Copy diagnostic message")))
+            .render()
     })
 }
 

crates/editor2/src/element.rs 🔗

@@ -19,9 +19,10 @@ use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use gpui::{
     point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowWindow,
-    Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId, ElementInputHandler,
-    Entity, Hsla, Line, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
-    Pixels, ScrollWheelEvent, Size, Style, TextRun, TextStyle, ViewContext, WindowContext,
+    Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
+    ElementInputHandler, Entity, Hsla, Line, MouseButton, MouseDownEvent, MouseMoveEvent,
+    MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, Size, Style, TextRun, TextStyle,
+    ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
@@ -1176,30 +1177,31 @@ impl EditorElement {
         }
     }
 
-    // fn paint_blocks(
-    //     &mut self,
-    //     bounds: Bounds<Pixels>,
-    //     visible_bounds: Bounds<Pixels>,
-    //     layout: &mut LayoutState,
-    //     editor: &mut Editor,
-    //     cx: &mut ViewContext<Editor>,
-    // ) {
-    //     let scroll_position = layout.position_map.snapshot.scroll_position();
-    //     let scroll_left = scroll_position.x * layout.position_map.em_width;
-    //     let scroll_top = scroll_position.y * layout.position_map.line_height;
-
-    //     for block in &mut layout.blocks {
-    //         let mut origin = bounds.origin
-    //             + point(
-    //                 0.,
-    //                 block.row as f32 * layout.position_map.line_height - scroll_top,
-    //             );
-    //         if !matches!(block.style, BlockStyle::Sticky) {
-    //             origin += point(-scroll_left, 0.);
-    //         }
-    //         block.element.paint(origin, visible_bounds, editor, cx);
-    //     }
-    // }
+    fn paint_blocks(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        layout: &mut LayoutState,
+        editor: &mut Editor,
+        cx: &mut ViewContext<Editor>,
+    ) {
+        let scroll_position = layout.position_map.snapshot.scroll_position();
+        let scroll_left = scroll_position.x * layout.position_map.em_width;
+        let scroll_top = scroll_position.y * layout.position_map.line_height;
+
+        for block in &mut layout.blocks {
+            let mut origin = bounds.origin
+                + point(
+                    Pixels::ZERO,
+                    block.row as f32 * layout.position_map.line_height - scroll_top,
+                );
+            if !matches!(block.style, BlockStyle::Sticky) {
+                origin += point(-scroll_left, Pixels::ZERO);
+            }
+            block
+                .element
+                .draw(origin, block.available_space, editor, cx);
+        }
+    }
 
     fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> Pixels {
         let style = &self.style;
@@ -1942,7 +1944,7 @@ impl EditorElement {
             fold_ranges,
             line_number_layouts,
             display_hunks,
-            // blocks,
+            blocks,
             selections,
             context_menu,
             code_actions_indicator,
@@ -1978,7 +1980,11 @@ impl EditorElement {
                 TransformBlock::ExcerptHeader { .. } => false,
                 TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
             });
-        let mut render_block = |block: &TransformBlock, width: Pixels, block_id: usize| {
+        let mut render_block = |block: &TransformBlock,
+                                available_space: Size<AvailableSpace>,
+                                block_id: usize,
+                                editor: &mut Editor,
+                                cx: &mut ViewContext<Editor>| {
             let mut element = match block {
                 TransformBlock::Custom(block) => {
                     let align_to = block
@@ -2031,28 +2037,15 @@ impl EditorElement {
                         let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
 
                         // todo!("avoid ElementId collision risk here")
-                        IconButton::new(usize::from(*id), ui::Icon::ArrowUpRight)
-                            .on_click(move |editor, cx| {
-                                if let Some(workspace) = editor
-                                    .workspace
-                                    .as_ref()
-                                    .and_then(|(workspace, _)| workspace.upgrade(cx))
-                                {
-                                    workspace.update(cx, |workspace, cx| {
-                                        Editor::jump(
-                                            workspace,
-                                            jump_path.clone(),
-                                            jump_position,
-                                            jump_anchor,
-                                            cx,
-                                        );
-                                    });
-                                }
+                        let icon_button_id: usize = id.clone().into();
+                        IconButton::new(icon_button_id, ui::Icon::ArrowUpRight)
+                            .on_click(move |editor: &mut Editor, cx| {
+                                editor.jump(jump_path.clone(), jump_position, jump_anchor, cx);
                             })
                             .tooltip("Jump to Buffer") // todo!(pass an action as well to show key binding)
                     });
 
-                    if *starts_new_buffer {
+                    let element = if *starts_new_buffer {
                         let path = buffer.resolve_file_path(cx, include_root);
                         let mut filename = None;
                         let mut parent_path = None;
@@ -2066,40 +2059,34 @@ impl EditorElement {
                         h_stack()
                             .child(filename.unwrap_or_else(|| "untitled".to_string()))
                             .children(parent_path)
-                            .children(jump_icon)
-                            .p_x(gutter_padding)
+                            .children(jump_icon) // .p_x(gutter_padding)
                     } else {
                         let text_style = style.text.clone();
-                        h_stack()
-                            .child("⋯")
-                            .children(jump_icon)
-                            .p_x(gutter_padding)
-                            .expanded()
-                            .into_any_named("collapsed context")
-                    }
+                        h_stack().child("⋯").children(jump_icon) // .p_x(gutter_padding)
+                    };
+                    element.render()
                 }
             };
 
-            // element.layout(
-            //     SizeConstraint {
-            //         min: gpui::Point::<Pixels>::zero(),
-            //         max: point(width, block.height() as f32 * line_height),
-            //     },
-            //     editor,
-            //     cx,
-            // );
-            element
+            let size = element.measure(available_space, editor, cx);
+            (element, size)
         };
 
         let mut fixed_block_max_width = Pixels::ZERO;
         let mut blocks = Vec::new();
         for (row, block) in fixed_blocks {
-            let element = render_block(block, f32::INFINITY, block_id);
+            let available_space = size(
+                AvailableSpace::MinContent,
+                AvailableSpace::Definite(block.height() as f32 * line_height),
+            );
+            let (element, element_size) =
+                render_block(block, available_space, block_id, editor, cx);
             block_id += 1;
-            fixed_block_max_width = fixed_block_max_width.max(element.size().x + em_width);
+            fixed_block_max_width = fixed_block_max_width.max(element_size.width + em_width);
             blocks.push(BlockLayout {
                 row,
                 element,
+                available_space,
                 style: BlockStyle::Fixed,
             });
         }
@@ -2115,11 +2102,16 @@ impl EditorElement {
                     .max(gutter_width + scroll_width),
                 BlockStyle::Fixed => unreachable!(),
             };
-            let element = render_block(block, width, block_id);
+            let available_space = size(
+                AvailableSpace::Definite(width),
+                AvailableSpace::Definite(block.height() as f32 * line_height),
+            );
+            let (element, _) = render_block(block, available_space, block_id, editor, cx);
             block_id += 1;
             blocks.push(BlockLayout {
                 row,
                 element,
+                available_space,
                 style,
             });
         }
@@ -2630,11 +2622,18 @@ impl Element<Editor> for EditorElement {
                     &layout.position_map,
                     cx,
                 );
+
                 self.paint_background(gutter_bounds, text_bounds, &layout, cx);
                 if layout.gutter_size.width > Pixels::ZERO {
                     self.paint_gutter(gutter_bounds, &mut layout, editor, cx);
                 }
+
                 self.paint_text(text_bounds, &mut layout, editor, cx);
+
+                if !layout.blocks.is_empty() {
+                    self.paint_blocks(bounds, &mut layout, editor, cx);
+                }
+
                 let input_handler = ElementInputHandler::new(bounds, cx);
                 cx.handle_input(&editor.focus_handle, input_handler);
             });
@@ -3255,7 +3254,7 @@ pub struct LayoutState {
     highlighted_rows: Option<Range<u32>>,
     line_number_layouts: Vec<Option<gpui::Line>>,
     display_hunks: Vec<DisplayDiffHunk>,
-    // blocks: Vec<BlockLayout>,
+    blocks: Vec<BlockLayout>,
     highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
     fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)>,
     selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
@@ -3358,6 +3357,7 @@ impl PositionMap {
 struct BlockLayout {
     row: u32,
     element: AnyElement<Editor>,
+    available_space: Size<AvailableSpace>,
     style: BlockStyle,
 }
 

crates/ui2/src/components/tooltip.rs 🔗

@@ -9,8 +9,10 @@ pub struct TextTooltip {
 }
 
 impl TextTooltip {
-    pub fn new(str: SharedString) -> Self {
-        Self { title: str }
+    pub fn new(title: impl Into<SharedString>) -> Self {
+        Self {
+            title: title.into(),
+        }
     }
 }