diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 05880f9c2a3de2325b8826af0eb3641da0162a4a..fefebb67a6f655c5b81b013c1d447b713e72575d 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b94add33f04a456bd4e5ad8a887f36bc0e04e29b..59b474b1c91c0ad62eb9c260facb2ab46ef4f9c6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4597,7 +4597,6 @@ impl EditorElement { let mut lines = Vec::::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, line_number: Option, 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, - target_anchor: Anchor, line_height: Pixels, scroll_pixel_position: gpui::Point, content_origin: gpui::Point, @@ -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, + 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, pub sticky_row: DisplayRow, pub start_point: Point, pub offset: ScrollOffset,