Make completion documentation scroll & fix accompanying panic from tag

Julia and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>

Change summary

crates/editor/src/editor.rs        | 26 +++++++---
crates/editor/src/hover_popover.rs |  4 
crates/gpui/src/elements/flex.rs   | 75 ++++++++++++++++++-------------
3 files changed, 64 insertions(+), 41 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -119,7 +119,7 @@ pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis
 
 pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
 
-pub fn render_parsed_markdown(
+pub fn render_parsed_markdown<Tag: 'static>(
     parsed: &language::ParsedMarkdown,
     editor_style: &EditorStyle,
     cx: &mut ViewContext<Editor>,
@@ -153,7 +153,7 @@ pub fn render_parsed_markdown(
                     style: CursorStyle::PointingHand,
                 });
                 cx.scene().push_mouse_region(
-                    MouseRegion::new::<RenderedMarkdown>(view_id, region_id, bounds)
+                    MouseRegion::new::<(RenderedMarkdown, Tag)>(view_id, region_id, bounds)
                         .on_click::<Editor, _>(MouseButton::Left, move |_, _, cx| {
                             cx.platform().open_url(&url)
                         }),
@@ -1247,6 +1247,8 @@ impl CompletionsMenu {
         })
         .with_width_from_item(widest_completion_ix);
 
+        enum MultiLineDocumentation {}
+
         Flex::row()
             .with_child(list)
             .with_children({
@@ -1256,13 +1258,21 @@ impl CompletionsMenu {
                 let documentation = &completion.documentation;
 
                 match documentation {
-                    Some(Documentation::MultiLinePlainText(text)) => {
-                        Some(Text::new(text.clone(), style.text.clone()))
-                    }
+                    Some(Documentation::MultiLinePlainText(text)) => Some(
+                        Flex::column()
+                            .scrollable::<MultiLineDocumentation>(0, None, cx)
+                            .with_child(
+                                Text::new(text.clone(), style.text.clone()).with_soft_wrap(true),
+                            ),
+                    ),
 
-                    Some(Documentation::MultiLineMarkdown(parsed)) => {
-                        Some(render_parsed_markdown(parsed, &style, cx))
-                    }
+                    Some(Documentation::MultiLineMarkdown(parsed)) => Some(
+                        Flex::column()
+                            .scrollable::<MultiLineDocumentation>(0, None, cx)
+                            .with_child(render_parsed_markdown::<MultiLineDocumentation>(
+                                parsed, &style, cx,
+                            )),
+                    ),
 
                     _ => None,
                 }

crates/editor/src/hover_popover.rs 🔗

@@ -474,8 +474,8 @@ impl InfoPopover {
     ) -> AnyElement<Editor> {
         MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
             Flex::column()
-                .scrollable::<HoverBlock>(1, None, cx)
-                .with_child(crate::render_parsed_markdown(
+                .scrollable::<HoverBlock>(0, None, cx)
+                .with_child(crate::render_parsed_markdown::<HoverBlock>(
                     &self.parsed_content,
                     style,
                     cx,

crates/gpui/src/elements/flex.rs 🔗

@@ -2,7 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
 
 use crate::{
     json::{self, ToJson, Value},
-    AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext,
+    AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, TypeTag, Vector2FExt,
+    ViewContext,
 };
 use pathfinder_geometry::{
     rect::RectF,
@@ -10,10 +11,10 @@ use pathfinder_geometry::{
 };
 use serde_json::json;
 
-#[derive(Default)]
 struct ScrollState {
     scroll_to: Cell<Option<usize>>,
     scroll_position: Cell<f32>,
+    type_tag: TypeTag,
 }
 
 pub struct Flex<V> {
@@ -66,8 +67,14 @@ impl<V: 'static> Flex<V> {
     where
         Tag: 'static,
     {
-        let scroll_state = cx.default_element_state::<Tag, Rc<ScrollState>>(element_id);
-        scroll_state.read(cx).scroll_to.set(scroll_to);
+        let scroll_state = cx.element_state::<Tag, Rc<ScrollState>>(
+            element_id,
+            Rc::new(ScrollState {
+                scroll_to: Cell::new(scroll_to),
+                scroll_position: Default::default(),
+                type_tag: TypeTag::new::<Tag>(),
+            }),
+        );
         self.scroll_state = Some((scroll_state, cx.handle().id()));
         self
     }
@@ -276,38 +283,44 @@ impl<V: 'static> Element<V> for Flex<V> {
         if let Some((scroll_state, id)) = &self.scroll_state {
             let scroll_state = scroll_state.read(cx).clone();
             cx.scene().push_mouse_region(
-                crate::MouseRegion::new::<Self>(*id, 0, bounds)
-                    .on_scroll({
-                        let axis = self.axis;
-                        move |e, _: &mut V, cx| {
-                            if remaining_space < 0. {
-                                let scroll_delta = e.delta.raw();
-
-                                let mut delta = match axis {
-                                    Axis::Horizontal => {
-                                        if scroll_delta.x().abs() >= scroll_delta.y().abs() {
-                                            scroll_delta.x()
-                                        } else {
-                                            scroll_delta.y()
-                                        }
+                crate::MouseRegion::from_handlers(
+                    scroll_state.type_tag,
+                    *id,
+                    0,
+                    bounds,
+                    Default::default(),
+                )
+                .on_scroll({
+                    let axis = self.axis;
+                    move |e, _: &mut V, cx| {
+                        if remaining_space < 0. {
+                            let scroll_delta = e.delta.raw();
+
+                            let mut delta = match axis {
+                                Axis::Horizontal => {
+                                    if scroll_delta.x().abs() >= scroll_delta.y().abs() {
+                                        scroll_delta.x()
+                                    } else {
+                                        scroll_delta.y()
                                     }
-                                    Axis::Vertical => scroll_delta.y(),
-                                };
-                                if !e.delta.precise() {
-                                    delta *= 20.;
                                 }
+                                Axis::Vertical => scroll_delta.y(),
+                            };
+                            if !e.delta.precise() {
+                                delta *= 20.;
+                            }
 
-                                scroll_state
-                                    .scroll_position
-                                    .set(scroll_state.scroll_position.get() - delta);
+                            scroll_state
+                                .scroll_position
+                                .set(scroll_state.scroll_position.get() - delta);
 
-                                cx.notify();
-                            } else {
-                                cx.propagate_event();
-                            }
+                            cx.notify();
+                        } else {
+                            cx.propagate_event();
                         }
-                    })
-                    .on_move(|_, _: &mut V, _| { /* Capture move events */ }),
+                    }
+                })
+                .on_move(|_, _: &mut V, _| { /* Capture move events */ }),
             )
         }