diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 170d7fb25d..cc1deec0c1 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -2127,8 +2127,25 @@ impl Element for TextElement { cx, ); + // When the mode shows line numbers, set the mouse cursor to + // Arrow over the gutter so it does not read as the editor's + // text-insertion area. + let has_line_numbers = self.state.read(cx).mode.line_number(); + if has_line_numbers { + window.set_cursor_style( + gpui::CursorStyle::Arrow, + &prepaint.fold_icon_layout.line_number_hitbox, + ); + } + + let line_number_hitbox = if has_line_numbers { + Some(prepaint.fold_icon_layout.line_number_hitbox.clone()) + } else { + None + }; self.state.update(cx, |state, cx| { state.last_layout = Some(prepaint.last_layout.clone()); + state.line_number_hitbox = line_number_hitbox; state.last_bounds = Some(bounds); state.last_cursor = Some(state.cursor()); state.set_input_bounds(input_bounds, cx); diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index b58a3255df..dde3568726 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -357,6 +357,12 @@ pub struct InputState { /// The marked range is the temporary insert text on IME typing. pub(super) ime_marked_range: Option, pub(super) last_layout: Option, + /// The hitbox covering the line-number gutter region from the last + /// paint (Sub-epic E P4). Available whenever the mode shows line + /// numbers; consumed via [`InputState::line_number_hitbox`] so + /// downstream click routing can map mouse events back to gutter + /// rows without re-reading the layout. + pub(super) line_number_hitbox: Option, pub(super) last_cursor: Option, /// The input container bounds pub(super) input_bounds: Bounds, @@ -492,6 +498,7 @@ impl InputState { validate: None, mode: InputMode::default(), last_layout: None, + line_number_hitbox: None, last_bounds: None, last_selected_range: None, last_cursor: None, @@ -706,6 +713,39 @@ impl InputState { self.mode.diagnostics_mut() } + /// Hitbox covering the gutter (line-number column) from the most + /// recent paint. Provided as a primitive for consumer-side + /// click routing. Returns `None` until at least one frame has + /// painted or when the mode does not show line numbers. + #[inline] + pub fn line_number_hitbox(&self) -> Option<&gpui::Hitbox> { + self.line_number_hitbox.as_ref() + } + + /// Bounds, in window coordinates, of buffer line `row` from the + /// most recent paint. Useful for placing pop-ups or hitboxes + /// against a specific line without re-reading the layout. Returns + /// `None` when `row` is outside the visible range, when the layout + /// has no entry for the row (folded), or when no paint has run yet. + pub fn visible_line_bounds(&self, row: u32) -> Option> { + let layout = self.last_layout.as_ref()?; + let row = row as usize; + + let mut offset_y = layout.visible_top; + for (line, &buffer_line) in layout.lines.iter().zip(layout.visible_buffer_lines.iter()) { + let height = layout.line_height * line.wrapped_lines.len() as f32; + if buffer_line == row { + let last_bounds = self.last_bounds?; + return Some(Bounds::new( + last_bounds.origin + point(px(0.), offset_y), + gpui::size(last_bounds.size.width, height), + )); + } + offset_y += height; + } + None + } + /// Set placeholder pub fn set_placeholder( &mut self,