@@ -1,5 +1,6 @@
use crate::{
hover_popover::{self, InlayHover},
+ scroll::ScrollAmount,
Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition, InlayId,
PointForPosition, SelectPhase,
};
@@ -38,7 +39,11 @@ impl RangeInEditor {
}
}
- fn point_within_range(&self, trigger_point: &TriggerPoint, snapshot: &EditorSnapshot) -> bool {
+ pub fn point_within_range(
+ &self,
+ trigger_point: &TriggerPoint,
+ snapshot: &EditorSnapshot,
+ ) -> bool {
match (self, trigger_point) {
(Self::Text(range), TriggerPoint::Text(point)) => {
let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le();
@@ -169,6 +174,21 @@ impl Editor {
.detach();
}
+ pub fn scroll_hover(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) -> bool {
+ let selection = self.selections.newest_anchor().head();
+ let snapshot = self.snapshot(cx);
+
+ let Some(popover) = self.hover_state.info_popovers.iter().find(|popover| {
+ popover
+ .symbol_range
+ .point_within_range(&TriggerPoint::Text(selection), &snapshot)
+ }) else {
+ return false;
+ };
+ popover.scroll(amount, cx);
+ true
+ }
+
fn cmd_click_reveal_task(
&mut self,
point: PointForPosition,
@@ -1,14 +1,15 @@
use crate::{
display_map::{InlayOffset, ToDisplayPoint},
hover_links::{InlayHighlight, RangeInEditor},
+ scroll::ScrollAmount,
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
EditorStyle, ExcerptId, Hover, RangeToAnchorExt,
};
use futures::{stream::FuturesUnordered, FutureExt};
use gpui::{
div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, MouseButton,
- ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, Task,
- ViewContext, WeakView,
+ ParentElement, Pixels, ScrollHandle, SharedString, Size, StatefulInteractiveElement, Styled,
+ Task, ViewContext, WeakView,
};
use language::{markdown, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown};
@@ -118,6 +119,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
let hover_popover = InfoPopover {
symbol_range: RangeInEditor::Inlay(inlay_hover.range.clone()),
parsed_content,
+ scroll_handle: ScrollHandle::new(),
};
this.update(&mut cx, |this, cx| {
@@ -317,6 +319,7 @@ fn show_hover(
InfoPopover {
symbol_range: RangeInEditor::Text(range),
parsed_content,
+ scroll_handle: ScrollHandle::new(),
},
)
})
@@ -423,7 +426,7 @@ async fn parse_blocks(
}
}
-#[derive(Default)]
+#[derive(Default, Debug)]
pub struct HoverState {
pub info_popovers: Vec<InfoPopover>,
pub diagnostic_popover: Option<DiagnosticPopover>,
@@ -487,10 +490,11 @@ impl HoverState {
}
}
-#[derive(Debug, Clone)]
+#[derive(Clone, Debug)]
pub struct InfoPopover {
- symbol_range: RangeInEditor,
- parsed_content: ParsedMarkdown,
+ pub symbol_range: RangeInEditor,
+ pub parsed_content: ParsedMarkdown,
+ pub scroll_handle: ScrollHandle,
}
impl InfoPopover {
@@ -504,23 +508,33 @@ impl InfoPopover {
div()
.id("info_popover")
.elevation_2(cx)
- .p_2()
.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,
// because that would dismiss the popover.
.on_mouse_move(|_, cx| cx.stop_propagation())
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
- .child(crate::render_parsed_markdown(
+ .child(div().p_2().child(crate::render_parsed_markdown(
"content",
&self.parsed_content,
style,
workspace,
cx,
- ))
+ )))
.into_any_element()
}
+
+ pub fn scroll(&self, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
+ let mut current = self.scroll_handle.offset();
+ current.y -= amount.pixels(
+ cx.line_height(),
+ self.scroll_handle.bounds().size.height - px(16.),
+ ) / 2.0;
+ cx.notify();
+ self.scroll_handle.set_offset(current);
+ }
}
#[derive(Debug, Clone)]
@@ -1,7 +1,8 @@
use crate::Editor;
use serde::Deserialize;
+use ui::{px, Pixels};
-#[derive(Clone, PartialEq, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum ScrollAmount {
// Scroll N lines (positive is towards the end of the document)
Line(f32),
@@ -25,4 +26,11 @@ impl ScrollAmount {
.unwrap_or(0.),
}
}
+
+ pub fn pixels(&self, line_height: Pixels, height: Pixels) -> Pixels {
+ match self {
+ ScrollAmount::Line(x) => px(line_height.0 * x),
+ ScrollAmount::Page(x) => px(height.0 * x),
+ }
+ }
}
@@ -2437,7 +2437,7 @@ where
}
}
-#[derive(Default)]
+#[derive(Default, Debug)]
struct ScrollHandleState {
offset: Rc<RefCell<Point<Pixels>>>,
bounds: Bounds<Pixels>,
@@ -2449,7 +2449,7 @@ struct ScrollHandleState {
/// A handle to the scrollable aspects of an element.
/// Used for accessing scroll state, like the current scroll offset,
/// and for mutating the scroll state, like scrolling to a specific child.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
impl Default for ScrollHandle {
@@ -2526,6 +2526,14 @@ impl ScrollHandle {
}
}
+ /// Set the offset explicitly. The offset is the distance from the top left of the
+ /// parent container to the top left of the first child.
+ /// As you scroll further down the offset becomes more negative.
+ pub fn set_offset(&self, mut position: Point<Pixels>) {
+ let state = self.0.borrow();
+ *state.offset.borrow_mut() = position;
+ }
+
/// Get the logical scroll top, based on a child index and a pixel offset.
pub fn logical_scroll_top(&self) -> (usize, Pixels) {
let ix = self.top_item();