@@ -30828,7 +30828,7 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
let fn_foo = || empty_range(0, 0);
let impl_bar = || empty_range(4, 0);
- let fn_new = || empty_range(5, 4);
+ let fn_new = || empty_range(5, 0);
let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
cx.update_editor(|e, window, cx| {
@@ -30914,6 +30914,36 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
// we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
(gpui::Point { x: 0., y: 1.5 })
);
+
+ // Verify clicking at a specific x position within a sticky header places
+ // the cursor at the corresponding column.
+ let (text_origin_x, em_width) = cx.update_editor(|editor, _, _| {
+ let position_map = editor.last_position_map.as_ref().unwrap();
+ (
+ position_map.text_hitbox.bounds.origin.x,
+ position_map.em_layout_width,
+ )
+ });
+
+ // Click on "impl Bar {" sticky header at column 5 (the 'B' in 'Bar').
+ // The text "impl Bar {" starts at column 0, so column 5 = 'B'.
+ let click_x = text_origin_x + em_width * 5.5;
+ cx.update_editor(|e, window, cx| {
+ e.scroll(gpui::Point { x: 0., y: 4.5 }, None, window, cx);
+ });
+ cx.run_until_parked();
+ cx.simulate_click(
+ gpui::Point {
+ x: click_x,
+ y: 0.25 * line_height,
+ },
+ Modifiers::none(),
+ );
+ cx.run_until_parked();
+ let (scroll_pos, selections) =
+ cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)));
+ assert_eq!(scroll_pos, gpui::Point { x: 0., y: 4. });
+ assert_eq!(selections, vec![empty_range(4, 5)]);
}
#[gpui::test]
@@ -4597,7 +4597,6 @@ impl EditorElement {
let mut lines = Vec::<StickyHeaderLine>::new();
for StickyHeader {
- item,
sticky_row,
start_point,
offset,
@@ -4637,7 +4636,6 @@ impl EditorElement {
line_height * offset as f32,
line,
line_number,
- item.range.start,
line_height,
scroll_pixel_position,
content_origin,
@@ -4703,7 +4701,6 @@ impl EditorElement {
end_rows.push(end_row);
rows.push(StickyHeader {
- item: item.clone(),
sticky_row,
start_point,
offset,
@@ -6701,22 +6698,33 @@ impl EditorElement {
}
});
+ let position_map = layout.position_map.clone();
+
for (line_index, line) in sticky_headers.lines.iter().enumerate() {
let editor = self.editor.clone();
let hitbox = line.hitbox.clone();
- let target_anchor = line.target_anchor;
+ let row = line.row;
+ let line_layout = line.line.clone();
+ let position_map = position_map.clone();
window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
if !phase.bubble() {
return;
}
if event.button == MouseButton::Left && hitbox.is_hovered(window) {
+ let point_for_position =
+ position_map.point_for_position_on_line(event.position, row, &line_layout);
+
editor.update(cx, |editor, cx| {
+ let snapshot = editor.snapshot(window, cx);
+ let anchor = snapshot
+ .display_snapshot
+ .display_point_to_anchor(point_for_position.previous_valid, Bias::Left);
editor.change_selections(
SelectionEffects::scroll(Autoscroll::top_relative(line_index)),
window,
cx,
- |selections| selections.select_ranges([target_anchor..target_anchor]),
+ |selections| selections.select_ranges([anchor..anchor]),
);
cx.stop_propagation();
});
@@ -11261,11 +11269,10 @@ struct StickyHeaders {
struct StickyHeaderLine {
row: DisplayRow,
offset: Pixels,
- line: LineWithInvisibles,
+ line: Rc<LineWithInvisibles>,
line_number: Option<ShapedLine>,
elements: SmallVec<[AnyElement; 1]>,
available_text_width: Pixels,
- target_anchor: Anchor,
hitbox: Hitbox,
}
@@ -11323,7 +11330,7 @@ impl StickyHeaders {
},
);
- window.set_cursor_style(CursorStyle::PointingHand, &line.hitbox);
+ window.set_cursor_style(CursorStyle::IBeam, &line.hitbox);
}
}
}
@@ -11334,7 +11341,6 @@ impl StickyHeaderLine {
offset: Pixels,
mut line: LineWithInvisibles,
line_number: Option<ShapedLine>,
- target_anchor: Anchor,
line_height: Pixels,
scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
content_origin: gpui::Point<Pixels>,
@@ -11364,11 +11370,10 @@ impl StickyHeaderLine {
Self {
row,
offset,
- line,
+ line: Rc::new(line),
line_number,
elements,
available_text_width,
- target_anchor,
hitbox: window.insert_hitbox(hitbox_bounds, HitboxBehavior::BlockMouseExceptScroll),
}
}
@@ -11950,6 +11955,41 @@ impl PositionMap {
column_overshoot_after_line_end,
}
}
+
+ fn point_for_position_on_line(
+ &self,
+ position: gpui::Point<Pixels>,
+ row: DisplayRow,
+ line: &LineWithInvisibles,
+ ) -> PointForPosition {
+ let text_bounds = self.text_hitbox.bounds;
+ let scroll_position = self.snapshot.scroll_position();
+ let position = position - text_bounds.origin;
+ let x = position.x + (scroll_position.x as f32 * self.em_layout_width);
+
+ let alignment_offset = line.alignment_offset(self.text_align, self.content_width);
+ let x_relative_to_text = x - alignment_offset;
+ let (column, x_overshoot_after_line_end) =
+ if let Some(ix) = line.index_for_x(x_relative_to_text) {
+ (ix as u32, px(0.))
+ } else {
+ (line.len as u32, px(0.).max(x_relative_to_text - line.width))
+ };
+
+ let mut exact_unclipped = DisplayPoint::new(row, column);
+ let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
+ let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
+
+ let column_overshoot_after_line_end =
+ (x_overshoot_after_line_end / self.em_layout_width) as u32;
+ *exact_unclipped.column_mut() += column_overshoot_after_line_end;
+ PointForPosition {
+ previous_valid,
+ next_valid,
+ exact_unclipped,
+ column_overshoot_after_line_end,
+ }
+ }
}
pub(crate) struct BlockLayout {
@@ -12286,7 +12326,6 @@ impl HighlightedRange {
}
pub(crate) struct StickyHeader {
- pub item: language::OutlineItem<Anchor>,
pub sticky_row: DisplayRow,
pub start_point: Point,
pub offset: ScrollOffset,