From 43ccb51f5461e8e7b5ff37a1af464bd8be1108d5 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 5 Feb 2025 11:56:56 -0500 Subject: [PATCH] Cherry-pick char_index_for_point fix to v0.172.x (#24286) Manual cherry-pick of #23989 Release Notes: - Fixed a bug where Zed could crash with certain input sources on macOS Co-authored-by: Mikayla Maki Co-authored-by: Louis Brunner Co-authored-by: ben --- crates/editor/src/editor.rs | 32 +++- crates/editor/src/element.rs | 159 +++++++------------ crates/editor/src/mouse_context_menu.rs | 3 +- crates/gpui/examples/input.rs | 14 ++ crates/gpui/src/geometry.rs | 31 ++++ crates/gpui/src/input.rs | 19 +++ crates/gpui/src/platform.rs | 18 +++ crates/gpui/src/platform/mac/window.rs | 53 +++++-- crates/terminal_view/src/terminal_element.rs | 9 ++ 9 files changed, 218 insertions(+), 120 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 99ebc6486e6b54..e33b4326bd95a7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -63,10 +63,10 @@ pub use editor_settings::{ CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar, }; pub use editor_settings_controls::*; -use element::LineWithInvisibles; pub use element::{ CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition, }; +use element::{LineWithInvisibles, PositionMap}; use futures::{future, FutureExt}; use fuzzy::StringMatchCandidate; use zed_predict_onboarding::ZedPredictModal; @@ -723,6 +723,7 @@ pub struct Editor { >, >, last_bounds: Option>, + last_position_map: Option>, expect_bounds_change: Option>, tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>, tasks_update_task: Option>, @@ -1382,6 +1383,7 @@ impl Editor { gutter_hovered: false, pixel_position_of_newest_cursor: None, last_bounds: None, + last_position_map: None, expect_bounds_change: None, gutter_dimensions: GutterDimensions::default(), style: None, @@ -14095,7 +14097,7 @@ impl Editor { .and_then(|item| item.to_any().downcast_ref::()) } - fn character_size(&self, window: &mut Window) -> gpui::Point { + fn character_size(&self, window: &mut Window) -> gpui::Size { let text_layout_details = self.text_layout_details(window); let style = &text_layout_details.editor_style; let font_id = window.text_system().resolve_font(&style.text.font()); @@ -14109,7 +14111,7 @@ impl Editor { .size .width; - gpui::Point::new(em_width, line_height) + gpui::Size::new(em_width, line_height) } } @@ -15610,9 +15612,9 @@ impl EntityInputHandler for Editor { cx: &mut Context, ) -> Option> { let text_layout_details = self.text_layout_details(window); - let gpui::Point { - x: em_width, - y: line_height, + let gpui::Size { + width: em_width, + height: line_height, } = self.character_size(window); let snapshot = self.snapshot(window, cx); @@ -15630,6 +15632,24 @@ impl EntityInputHandler for Editor { size: size(em_width, line_height), }) } + + fn character_index_for_point( + &mut self, + point: gpui::Point, + _window: &mut Window, + _cx: &mut Context, + ) -> Option { + let position_map = self.last_position_map.as_ref()?; + if !position_map.text_hitbox.contains(&point) { + return None; + } + let display_point = position_map.point_for_position(point).previous_valid; + let anchor = position_map + .snapshot + .display_point_to_anchor(display_point, Bias::Left); + let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot); + Some(utf16_offset.0) + } } trait SelectionExt { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 328aa44120bf4d..da9e4b54c37826 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -502,7 +502,6 @@ impl EditorElement { let position_map = layout.position_map.clone(); window.on_key_event({ let editor = self.editor.clone(); - let text_hitbox = layout.text_hitbox.clone(); move |event: &ModifiersChangedEvent, phase, window, cx| { if phase != DispatchPhase::Bubble { return; @@ -511,7 +510,7 @@ impl EditorElement { if editor.hover_state.focused(window, cx) { return; } - Self::modifiers_changed(editor, event, &position_map, &text_hitbox, window, cx) + Self::modifiers_changed(editor, event, &position_map, window, cx) }) } }); @@ -521,17 +520,16 @@ impl EditorElement { editor: &mut Editor, event: &ModifiersChangedEvent, position_map: &PositionMap, - text_hitbox: &Hitbox, window: &mut Window, cx: &mut Context, ) { let mouse_position = window.mouse_position(); - if !text_hitbox.is_hovered(window) { + if !position_map.text_hitbox.is_hovered(window) { return; } editor.update_hovered_link( - position_map.point_for_position(text_hitbox.bounds, mouse_position), + position_map.point_for_position(mouse_position), &position_map.snapshot, event.modifiers, window, @@ -539,14 +537,11 @@ impl EditorElement { ) } - #[allow(clippy::too_many_arguments)] fn mouse_left_down( editor: &mut Editor, event: &MouseDownEvent, hovered_hunk: Option>, position_map: &PositionMap, - text_hitbox: &Hitbox, - gutter_hitbox: &Hitbox, line_numbers: &HashMap, window: &mut Window, cx: &mut Context, @@ -555,6 +550,8 @@ impl EditorElement { return; } + let text_hitbox = &position_map.text_hitbox; + let gutter_hitbox = &position_map.gutter_hitbox; let mut click_count = event.click_count; let mut modifiers = event.modifiers; @@ -611,8 +608,7 @@ impl EditorElement { } } - let point_for_position = - position_map.point_for_position(text_hitbox.bounds, event.position); + let point_for_position = position_map.point_for_position(event.position); let position = point_for_position.previous_valid; if modifiers.shift && modifiers.alt { editor.select( @@ -687,15 +683,13 @@ impl EditorElement { editor: &mut Editor, event: &MouseDownEvent, position_map: &PositionMap, - text_hitbox: &Hitbox, window: &mut Window, cx: &mut Context, ) { - if !text_hitbox.is_hovered(window) { + if !position_map.text_hitbox.is_hovered(window) { return; } - let point_for_position = - position_map.point_for_position(text_hitbox.bounds, event.position); + let point_for_position = position_map.point_for_position(event.position); mouse_context_menu::deploy_context_menu( editor, Some(event.position), @@ -710,16 +704,14 @@ impl EditorElement { editor: &mut Editor, event: &MouseDownEvent, position_map: &PositionMap, - text_hitbox: &Hitbox, window: &mut Window, cx: &mut Context, ) { - if !text_hitbox.is_hovered(window) || window.default_prevented() { + if !position_map.text_hitbox.is_hovered(window) || window.default_prevented() { return; } - let point_for_position = - position_map.point_for_position(text_hitbox.bounds, event.position); + let point_for_position = position_map.point_for_position(event.position); let position = point_for_position.previous_valid; editor.select( @@ -737,10 +729,10 @@ impl EditorElement { editor: &mut Editor, event: &MouseUpEvent, position_map: &PositionMap, - text_hitbox: &Hitbox, window: &mut Window, cx: &mut Context, ) { + let text_hitbox = &position_map.text_hitbox; let end_selection = editor.has_pending_selection(); let pending_nonempty_selections = editor.has_pending_nonempty_selection(); @@ -755,7 +747,7 @@ impl EditorElement { }; if !pending_nonempty_selections && multi_cursor_modifier && text_hitbox.is_hovered(window) { - let point = position_map.point_for_position(text_hitbox.bounds, event.position); + let point = position_map.point_for_position(event.position); editor.handle_click_hovered_link(point, event.modifiers, window, cx); cx.stop_propagation(); @@ -771,8 +763,7 @@ impl EditorElement { #[cfg(any(target_os = "linux", target_os = "freebsd"))] if EditorSettings::get_global(cx).middle_click_paste { if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) { - let point_for_position = - position_map.point_for_position(text_hitbox.bounds, event.position); + let point_for_position = position_map.point_for_position(event.position); let position = point_for_position.previous_valid; editor.select( @@ -795,7 +786,6 @@ impl EditorElement { editor: &mut Editor, event: &MouseMoveEvent, position_map: &PositionMap, - text_bounds: Bounds, window: &mut Window, cx: &mut Context, ) { @@ -803,7 +793,8 @@ impl EditorElement { return; } - let point_for_position = position_map.point_for_position(text_bounds, event.position); + let text_bounds = position_map.text_hitbox.bounds; + let point_for_position = position_map.point_for_position(event.position); let mut scroll_delta = gpui::Point::::default(); let vertical_margin = position_map.line_height.min(text_bounds.size.height / 3.0); let top = text_bounds.origin.y + vertical_margin; @@ -855,19 +846,18 @@ impl EditorElement { editor: &mut Editor, event: &MouseMoveEvent, position_map: &PositionMap, - text_hitbox: &Hitbox, - gutter_hitbox: &Hitbox, window: &mut Window, cx: &mut Context, ) { + let text_hitbox = &position_map.text_hitbox; + let gutter_hitbox = &position_map.gutter_hitbox; let modifiers = event.modifiers; let gutter_hovered = gutter_hitbox.is_hovered(window); editor.set_gutter_hovered(gutter_hovered, cx); // Don't trigger hover popover if mouse is hovering over context menu if text_hitbox.is_hovered(window) { - let point_for_position = - position_map.point_for_position(text_hitbox.bounds, event.position); + let point_for_position = position_map.point_for_position(event.position); editor.update_hovered_link( point_for_position, @@ -3789,8 +3779,7 @@ impl EditorElement { window: &mut Window, cx: &mut App, ) -> Vec { - let point_for_position = - position_map.point_for_position(text_hitbox.bounds, window.mouse_position()); + let point_for_position = position_map.point_for_position(window.mouse_position()); let mut controls = vec![]; @@ -3927,7 +3916,10 @@ impl EditorElement { let scroll_top = layout.position_map.snapshot.scroll_position().y; let gutter_bg = cx.theme().colors().editor_gutter_background; window.paint_quad(fill(layout.gutter_hitbox.bounds, gutter_bg)); - window.paint_quad(fill(layout.text_hitbox.bounds, self.style.background)); + window.paint_quad(fill( + layout.position_map.text_hitbox.bounds, + self.style.background, + )); if let EditorMode::Full = layout.mode { let mut active_rows = layout.active_rows.iter().peekable(); @@ -3952,8 +3944,8 @@ impl EditorElement { end: layout.gutter_hitbox.right(), }), CurrentLineHighlight::Line => Some(Range { - start: layout.text_hitbox.bounds.left(), - end: layout.text_hitbox.bounds.right(), + start: layout.position_map.text_hitbox.bounds.left(), + end: layout.position_map.text_hitbox.bounds.right(), }), CurrentLineHighlight::All => Some(Range { start: layout.hitbox.left(), @@ -4027,7 +4019,7 @@ impl EditorElement { layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width; for (wrap_position, active) in layout.wrap_guides.iter() { - let x = (layout.text_hitbox.origin.x + let x = (layout.position_map.text_hitbox.origin.x + *wrap_position + layout.position_map.em_width / 2.) - scroll_left; @@ -4039,7 +4031,7 @@ impl EditorElement { || scrollbar_y.as_ref().map_or(false, |sy| sy.visible) }; - if x < layout.text_hitbox.origin.x + if x < layout.position_map.text_hitbox.origin.x || (show_scrollbars && x > self.scrollbar_left(&layout.hitbox.bounds)) { continue; @@ -4052,8 +4044,8 @@ impl EditorElement { }; window.paint_quad(fill( Bounds { - origin: point(x, layout.text_hitbox.origin.y), - size: size(px(1.), layout.text_hitbox.size.height), + origin: point(x, layout.position_map.text_hitbox.origin.y), + size: size(px(1.), layout.position_map.text_hitbox.size.height), }, color, )); @@ -4428,7 +4420,7 @@ impl EditorElement { fn paint_text(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { window.with_content_mask( Some(ContentMask { - bounds: layout.text_hitbox.bounds, + bounds: layout.position_map.text_hitbox.bounds, }), |window| { let cursor_style = if self @@ -4442,7 +4434,7 @@ impl EditorElement { } else { CursorStyle::IBeam }; - window.set_cursor_style(cursor_style, &layout.text_hitbox); + window.set_cursor_style(cursor_style, &layout.position_map.text_hitbox); let invisible_display_ranges = self.paint_highlights(layout, window); self.paint_lines(&invisible_display_ranges, layout, window, cx); @@ -4464,7 +4456,7 @@ impl EditorElement { layout: &mut EditorLayout, window: &mut Window, ) -> SmallVec<[Range; 32]> { - window.paint_layer(layout.text_hitbox.bounds, |window| { + window.paint_layer(layout.position_map.text_hitbox.bounds, |window| { let mut invisible_display_ranges = SmallVec::<[Range; 32]>::new(); let line_end_overshoot = 0.15 * layout.position_map.line_height; for (range, color) in &layout.highlighted_ranges { @@ -4543,7 +4535,7 @@ impl EditorElement { // A softer than perfect black let redaction_color = gpui::rgb(0x0e1111); - window.paint_layer(layout.text_hitbox.bounds, |window| { + window.paint_layer(layout.position_map.text_hitbox.bounds, |window| { for range in layout.redacted_ranges.iter() { self.paint_highlighted_range( range.clone(), @@ -5117,13 +5109,13 @@ impl EditorElement { .collect(), }; - highlighted_range.paint(layout.text_hitbox.bounds, window); + highlighted_range.paint(layout.position_map.text_hitbox.bounds, window); } } fn paint_inline_blame(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { if let Some(mut inline_blame) = layout.inline_blame.take() { - window.paint_layer(layout.text_hitbox.bounds, |window| { + window.paint_layer(layout.position_map.text_hitbox.bounds, |window| { inline_blame.paint(window, cx); }) } @@ -5242,8 +5234,6 @@ impl EditorElement { window.on_mouse_event({ let position_map = layout.position_map.clone(); let editor = self.editor.clone(); - let text_hitbox = layout.text_hitbox.clone(); - let gutter_hitbox = layout.gutter_hitbox.clone(); let multi_buffer_range = layout .display_hunks @@ -5275,32 +5265,16 @@ impl EditorElement { event, multi_buffer_range.clone(), &position_map, - &text_hitbox, - &gutter_hitbox, line_numbers.as_ref(), window, cx, ); }), MouseButton::Right => editor.update(cx, |editor, cx| { - Self::mouse_right_down( - editor, - event, - &position_map, - &text_hitbox, - window, - cx, - ); + Self::mouse_right_down(editor, event, &position_map, window, cx); }), MouseButton::Middle => editor.update(cx, |editor, cx| { - Self::mouse_middle_down( - editor, - event, - &position_map, - &text_hitbox, - window, - cx, - ); + Self::mouse_middle_down(editor, event, &position_map, window, cx); }), _ => {} }; @@ -5311,12 +5285,11 @@ impl EditorElement { window.on_mouse_event({ let editor = self.editor.clone(); let position_map = layout.position_map.clone(); - let text_hitbox = layout.text_hitbox.clone(); move |event: &MouseUpEvent, phase, window, cx| { if phase == DispatchPhase::Bubble { editor.update(cx, |editor, cx| { - Self::mouse_up(editor, event, &position_map, &text_hitbox, window, cx) + Self::mouse_up(editor, event, &position_map, window, cx) }); } } @@ -5324,8 +5297,6 @@ impl EditorElement { window.on_mouse_event({ let position_map = layout.position_map.clone(); let editor = self.editor.clone(); - let text_hitbox = layout.text_hitbox.clone(); - let gutter_hitbox = layout.gutter_hitbox.clone(); move |event: &MouseMoveEvent, phase, window, cx| { if phase == DispatchPhase::Bubble { @@ -5336,25 +5307,10 @@ impl EditorElement { if event.pressed_button == Some(MouseButton::Left) || event.pressed_button == Some(MouseButton::Middle) { - Self::mouse_dragged( - editor, - event, - &position_map, - text_hitbox.bounds, - window, - cx, - ) + Self::mouse_dragged(editor, event, &position_map, window, cx) } - Self::mouse_moved( - editor, - event, - &position_map, - &text_hitbox, - &gutter_hitbox, - window, - cx, - ) + Self::mouse_moved(editor, event, &position_map, window, cx) }); } } @@ -7159,6 +7115,12 @@ impl Element for EditorElement { em_width, em_advance, snapshot, + gutter_hitbox: gutter_hitbox.clone(), + text_hitbox: text_hitbox.clone(), + }); + + self.editor.update(cx, |editor, _| { + editor.last_position_map = Some(position_map.clone()) }); let hunk_controls = self.layout_diff_hunk_controls( @@ -7182,7 +7144,6 @@ impl Element for EditorElement { wrap_guides, indent_guides, hitbox, - text_hitbox, gutter_hitbox, display_hunks, content_origin, @@ -7354,7 +7315,6 @@ impl IntoElement for EditorElement { pub struct EditorLayout { position_map: Rc, hitbox: Hitbox, - text_hitbox: Hitbox, gutter_hitbox: Hitbox, content_origin: gpui::Point, scrollbars_layout: AxisPair>, @@ -7534,15 +7494,17 @@ struct CreaseTrailerLayout { bounds: Bounds, } -struct PositionMap { - size: Size, - line_height: Pixels, - scroll_pixel_position: gpui::Point, - scroll_max: gpui::Point, - em_width: Pixels, - em_advance: Pixels, - line_layouts: Vec, - snapshot: EditorSnapshot, +pub(crate) struct PositionMap { + pub size: Size, + pub line_height: Pixels, + pub scroll_pixel_position: gpui::Point, + pub scroll_max: gpui::Point, + pub em_width: Pixels, + pub em_advance: Pixels, + pub line_layouts: Vec, + pub snapshot: EditorSnapshot, + pub text_hitbox: Hitbox, + pub gutter_hitbox: Hitbox, } #[derive(Debug, Copy, Clone)] @@ -7564,11 +7526,8 @@ impl PointForPosition { } impl PositionMap { - fn point_for_position( - &self, - text_bounds: Bounds, - position: gpui::Point, - ) -> PointForPosition { + pub(crate) fn point_for_position(&self, position: gpui::Point) -> PointForPosition { + let text_bounds = self.text_hitbox.bounds; let scroll_position = self.snapshot.scroll_position(); let position = position - text_bounds.origin; let y = position.y.max(px(0.)).min(self.size.height); diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index b9a9aa623de3c7..64c5d730d27386 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -229,9 +229,10 @@ pub fn deploy_context_menu( cx, ), None => { + let character_size = editor.character_size(window); let menu_position = MenuPosition::PinnedToEditor { source: source_anchor, - offset: editor.character_size(window), + offset: gpui::point(character_size.width, character_size.height), }; Some(MouseContextMenu::new( menu_position, diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index bef1546f06c372..73cfe848873ed3 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -364,6 +364,20 @@ impl EntityInputHandler for TextInput { ), )) } + + fn character_index_for_point( + &mut self, + point: gpui::Point, + _window: &mut Window, + _cx: &mut Context, + ) -> Option { + let line_point = self.last_bounds?.localize(&point)?; + let last_layout = self.last_layout.as_ref()?; + + assert_eq!(last_layout.text, self.content); + let utf8_index = last_layout.index_for_x(point.x - line_point.x)?; + Some(self.offset_to_utf16(utf8_index)) + } } struct TextElement { diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index e5361a47330517..228f74afbbf60e 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -217,6 +217,19 @@ impl Point { } } +impl Point +where + T: Sub + Debug + Clone + Default, +{ + /// Get the position of this point, relative to the given origin + pub fn relative_to(&self, origin: &Point) -> Point { + point( + self.x.clone() - origin.x.clone(), + self.y.clone() - origin.y.clone(), + ) + } +} + impl Mul for Point where T: Mul + Clone + Default + Debug, @@ -376,6 +389,13 @@ pub struct Size { pub height: T, } +impl Size { + /// Create a new Size, a synonym for [`size`] + pub fn new(width: T, height: T) -> Self { + size(width, height) + } +} + /// Constructs a new `Size` with the provided width and height. /// /// # Arguments @@ -1456,6 +1476,17 @@ where } } +impl Bounds +where + T: Add + PartialOrd + Clone + Default + Debug + Sub, +{ + /// Convert a point to the coordinate space defined by this Bounds + pub fn localize(&self, point: &Point) -> Option> { + self.contains(point) + .then(|| point.relative_to(&self.origin)) + } +} + /// Checks if the bounds represent an empty area. /// /// # Returns diff --git a/crates/gpui/src/input.rs b/crates/gpui/src/input.rs index 38b5c15ecde9c7..371c2771cdfafd 100644 --- a/crates/gpui/src/input.rs +++ b/crates/gpui/src/input.rs @@ -62,6 +62,14 @@ pub trait EntityInputHandler: 'static + Sized { window: &mut Window, cx: &mut Context, ) -> Option>; + + /// See [`InputHandler::character_index_for_point`] for details + fn character_index_for_point( + &mut self, + point: crate::Point, + window: &mut Window, + cx: &mut Context, + ) -> Option; } /// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`] @@ -159,4 +167,15 @@ impl InputHandler for ElementInputHandler { view.bounds_for_range(range_utf16, self.element_bounds, window, cx) }) } + + fn character_index_for_point( + &mut self, + point: crate::Point, + window: &mut Window, + cx: &mut App, + ) -> Option { + self.view.update(cx, |view, cx| { + view.character_index_for_point(point, window, cx) + }) + } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ac397dddcc3f30..571113231ee495 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -789,6 +789,14 @@ impl PlatformInputHandler { cx, ) } + + #[allow(unused)] + pub fn character_index_for_point(&mut self, point: Point) -> Option { + self.cx + .update(|window, cx| self.handler.character_index_for_point(point, window, cx)) + .ok() + .flatten() + } } /// A struct representing a selection in a text buffer, in UTF16 characters. @@ -879,6 +887,16 @@ pub trait InputHandler: 'static { cx: &mut App, ) -> Option>; + /// Get the character offset for the given point in terms of UTF16 characters + /// + /// Corresponds to [characterIndexForPoint:](https://developer.apple.com/documentation/appkit/nstextinputclient/characterindex(for:)) + fn character_index_for_point( + &mut self, + point: Point, + window: &mut Window, + cx: &mut App, + ) -> Option; + /// Allows a given input context to opt into getting raw key repeats instead of /// sending these to the platform. /// TODO: Ideally we should be able to set ApplePressAndHoldEnabled in NSUserDefaults diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index dc715a071b5367..02ea45a9645783 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -17,8 +17,8 @@ use cocoa::{ }, base::{id, nil}, foundation::{ - NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect, - NSSize, NSString, NSUInteger, + NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSNotFound, + NSPoint, NSRect, NSSize, NSString, NSUInteger, }, }; use core_graphics::display::{CGDirectDisplayID, CGPoint, CGRect}; @@ -223,6 +223,11 @@ unsafe fn build_classes() { accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, ); + decl.add_method( + sel!(characterIndexForPoint:), + character_index_for_point as extern "C" fn(&Object, Sel, NSPoint) -> u64, + ); + decl.register() }; } @@ -1683,17 +1688,7 @@ extern "C" fn first_rect_for_character_range( range: NSRange, _: id, ) -> NSRect { - let frame: NSRect = unsafe { - let state = get_window_state(this); - let lock = state.lock(); - let mut frame = NSWindow::frame(lock.native_window); - let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect]; - let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask]; - if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) { - frame.origin.y -= frame.size.height - content_layout_rect.size.height; - } - frame - }; + let frame = get_frame(this); with_input_handler(this, |input_handler| { input_handler.bounds_for_range(range.to_range()?) }) @@ -1714,6 +1709,20 @@ extern "C" fn first_rect_for_character_range( ) } +fn get_frame(this: &Object) -> NSRect { + unsafe { + let state = get_window_state(this); + let lock = state.lock(); + let mut frame = NSWindow::frame(lock.native_window); + let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect]; + let style_mask: NSWindowStyleMask = msg_send![lock.native_window, styleMask]; + if !style_mask.contains(NSWindowStyleMask::NSFullSizeContentViewWindowMask) { + frame.origin.y -= frame.size.height - content_layout_rect.size.height; + } + frame + } +} + extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) { unsafe { let is_attributed_string: BOOL = @@ -1827,6 +1836,24 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL { YES } +extern "C" fn character_index_for_point(this: &Object, _: Sel, position: NSPoint) -> u64 { + let position = screen_point_to_gpui_point(this, position); + with_input_handler(this, |input_handler| { + input_handler.character_index_for_point(position) + }) + .flatten() + .map(|index| index as u64) + .unwrap_or(NSNotFound as u64) +} + +fn screen_point_to_gpui_point(this: &Object, position: NSPoint) -> Point { + let frame = get_frame(this); + let window_x = position.x - frame.origin.x; + let window_y = frame.size.height - (position.y - frame.origin.y); + let position = point(px(window_x as f32), px(window_y as f32)); + position +} + extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation { let window_state = unsafe { get_window_state(this) }; let position = drag_event_position(&window_state, dragging_info); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index bd4399a2de0377..595e8e215abe5c 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1075,6 +1075,15 @@ impl InputHandler for TerminalInputHandler { fn apple_press_and_hold_enabled(&mut self) -> bool { false } + + fn character_index_for_point( + &mut self, + _point: Point, + _window: &mut Window, + _cx: &mut App, + ) -> Option { + None + } } pub fn is_blank(cell: &IndexedCell) -> bool {