editor: Add scrollbar to info popovers (#20184)

Related to https://github.com/zed-industries/zed/issues/5034

Release Notes:

- Added scrollbar to info popovers in editor.
This commit is contained in:
Piotr Osiewicz 2024-11-05 00:08:38 +01:00 committed by GitHub
parent 966b18e142
commit dc02894db4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 21 deletions

View file

@ -7,7 +7,7 @@ use crate::{
}; };
use gpui::{ use gpui::{
div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement, div, px, AnyElement, AsyncWindowContext, FontWeight, Hsla, InteractiveElement, IntoElement,
MouseButton, ParentElement, Pixels, ScrollHandle, Size, StatefulInteractiveElement, MouseButton, ParentElement, Pixels, ScrollHandle, Size, Stateful, StatefulInteractiveElement,
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext, StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -21,7 +21,7 @@ use std::rc::Rc;
use std::{borrow::Cow, cell::RefCell}; use std::{borrow::Cow, cell::RefCell};
use std::{ops::Range, sync::Arc, time::Duration}; use std::{ops::Range, sync::Arc, time::Duration};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, window_is_transparent}; use ui::{prelude::*, window_is_transparent, Scrollbar, ScrollbarState};
use util::TryFutureExt; use util::TryFutureExt;
pub const HOVER_DELAY_MILLIS: u64 = 350; pub const HOVER_DELAY_MILLIS: u64 = 350;
pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200; pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
@ -144,10 +144,12 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
let blocks = vec![inlay_hover.tooltip]; let blocks = vec![inlay_hover.tooltip];
let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await; let parsed_content = parse_blocks(&blocks, &language_registry, None, &mut cx).await;
let scroll_handle = ScrollHandle::new();
let hover_popover = InfoPopover { let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()), symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content, parsed_content,
scroll_handle: ScrollHandle::new(), scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(false)), keyboard_grace: Rc::new(RefCell::new(false)),
anchor: None, anchor: None,
}; };
@ -435,12 +437,14 @@ fn show_hover(
let language = hover_result.language; let language = hover_result.language;
let parsed_content = let parsed_content =
parse_blocks(&blocks, &language_registry, language, &mut cx).await; parse_blocks(&blocks, &language_registry, language, &mut cx).await;
let scroll_handle = ScrollHandle::new();
info_popover_tasks.push(( info_popover_tasks.push((
range.clone(), range.clone(),
InfoPopover { InfoPopover {
symbol_range: RangeInEditor::Text(range), symbol_range: RangeInEditor::Text(range),
parsed_content, parsed_content,
scroll_handle: ScrollHandle::new(), scrollbar_state: ScrollbarState::new(scroll_handle.clone()),
scroll_handle,
keyboard_grace: Rc::new(RefCell::new(ignore_timeout)), keyboard_grace: Rc::new(RefCell::new(ignore_timeout)),
anchor: Some(anchor), anchor: Some(anchor),
}, },
@ -680,13 +684,13 @@ impl HoverState {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct InfoPopover {
pub struct InfoPopover { pub(crate) symbol_range: RangeInEditor,
pub symbol_range: RangeInEditor, pub(crate) parsed_content: Option<View<Markdown>>,
pub parsed_content: Option<View<Markdown>>, pub(crate) scroll_handle: ScrollHandle,
pub scroll_handle: ScrollHandle, pub(crate) scrollbar_state: ScrollbarState,
pub keyboard_grace: Rc<RefCell<bool>>, pub(crate) keyboard_grace: Rc<RefCell<bool>>,
pub anchor: Option<Anchor>, pub(crate) anchor: Option<Anchor>,
} }
impl InfoPopover { impl InfoPopover {
@ -695,10 +699,6 @@ impl InfoPopover {
let mut d = div() let mut d = div()
.id("info_popover") .id("info_popover")
.elevation_2(cx) .elevation_2(cx)
.overflow_y_scroll()
.track_scroll(&self.scroll_handle)
.max_w(max_size.width)
.max_h(max_size.height)
// Prevent a mouse down/move on the popover from being propagated to the editor, // Prevent a mouse down/move on the popover from being propagated to the editor,
// because that would dismiss the popover. // because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation()) .on_mouse_move(|_, cx| cx.stop_propagation())
@ -706,11 +706,21 @@ impl InfoPopover {
let mut keyboard_grace = keyboard_grace.borrow_mut(); let mut keyboard_grace = keyboard_grace.borrow_mut();
*keyboard_grace = false; *keyboard_grace = false;
cx.stop_propagation(); cx.stop_propagation();
}) });
.p_2();
if let Some(markdown) = &self.parsed_content { if let Some(markdown) = &self.parsed_content {
d = d.child(markdown.clone()); d = d
.child(
div()
.id("info-md-container")
.overflow_y_scroll()
.max_w(max_size.width)
.max_h(max_size.height)
.p_2()
.track_scroll(&self.scroll_handle)
.child(markdown.clone()),
)
.child(self.render_vertical_scrollbar(cx));
} }
d.into_any_element() d.into_any_element()
} }
@ -724,6 +734,38 @@ impl InfoPopover {
cx.notify(); cx.notify();
self.scroll_handle.set_offset(current); self.scroll_handle.set_offset(current);
} }
fn render_vertical_scrollbar(&self, cx: &mut ViewContext<Editor>) -> Stateful<Div> {
div()
.occlude()
.id("info-popover-vertical-scroll")
.on_mouse_move(cx.listener(|_, _, cx| {
cx.notify();
cx.stop_propagation()
}))
.on_hover(|_, cx| {
cx.stop_propagation();
})
.on_any_mouse_down(|_, cx| {
cx.stop_propagation();
})
.on_mouse_up(
MouseButton::Left,
cx.listener(|_, _, cx| {
cx.stop_propagation();
}),
)
.on_scroll_wheel(cx.listener(|_, _, cx| {
cx.notify();
}))
.h_full()
.absolute()
.right_1()
.top_1()
.bottom_0()
.w(px(12.))
.cursor_default()
.children(Scrollbar::vertical(self.scrollbar_state.clone()))
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -16,7 +16,7 @@ pub struct Scrollbar {
} }
/// Wrapper around scroll handles. /// Wrapper around scroll handles.
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum ScrollableHandle { pub enum ScrollableHandle {
Uniform(UniformListScrollHandle), Uniform(UniformListScrollHandle),
NonUniform(ScrollHandle), NonUniform(ScrollHandle),
@ -91,7 +91,7 @@ impl From<ScrollHandle> for ScrollableHandle {
} }
/// A scrollbar state that should be persisted across frames. /// A scrollbar state that should be persisted across frames.
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct ScrollbarState { pub struct ScrollbarState {
// If Some(), there's an active drag, offset by percentage from the origin of a thumb. // If Some(), there's an active drag, offset by percentage from the origin of a thumb.
drag: Rc<Cell<Option<f32>>>, drag: Rc<Cell<Option<f32>>>,
@ -142,7 +142,6 @@ impl ScrollbarState {
} }
let mut percentage = current_offset / main_dimension_size; let mut percentage = current_offset / main_dimension_size;
let viewport_size = self.scroll_handle.viewport().size; let viewport_size = self.scroll_handle.viewport().size;
let end_offset = (current_offset + viewport_size.along(axis).0) / main_dimension_size; let end_offset = (current_offset + viewport_size.along(axis).0) / main_dimension_size;
// Scroll handle might briefly report an offset greater than the length of a list; // Scroll handle might briefly report an offset greater than the length of a list;
// in such case we'll adjust the starting offset as well to keep the scrollbar thumb length stable. // in such case we'll adjust the starting offset as well to keep the scrollbar thumb length stable.