diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 0a0ac4a0e0..6f50da6a7d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -11,7 +11,7 @@ use gpui::{ fonts::{FontId, HighlightStyle}, Entity, ModelContext, ModelHandle, }; -use language::{Point, Subscription as BufferSubscription}; +use language::{OffsetUtf16, Point, Subscription as BufferSubscription}; use settings::Settings; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; use sum_tree::{Bias, TreeMap}; @@ -549,6 +549,12 @@ impl ToDisplayPoint for usize { } } +impl ToDisplayPoint for OffsetUtf16 { + fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint { + self.to_offset(&map.buffer_snapshot).to_display_point(map) + } +} + impl ToDisplayPoint for Point { fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint { map.point_to_display_point(*self, Bias::Left) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 589f7b4c52..951796b6bf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -30,7 +30,7 @@ use gpui::{ WeakViewHandle, }; use json::json; -use language::{Bias, DiagnosticSeverity, Selection}; +use language::{Bias, DiagnosticSeverity, OffsetUtf16, Selection}; use project::ProjectPath; use settings::Settings; use smallvec::SmallVec; @@ -1517,6 +1517,43 @@ impl Element for EditorElement { } } + fn rect_for_text_range( + &self, + range_utf16: Range, + bounds: RectF, + _: RectF, + layout: &Self::LayoutState, + _: &Self::PaintState, + _: &gpui::MeasurementContext, + ) -> Option { + let text_bounds = RectF::new( + bounds.origin() + vec2f(layout.gutter_size.x(), 0.0), + layout.text_size, + ); + let content_origin = text_bounds.origin() + vec2f(layout.gutter_margin, 0.); + let scroll_position = layout.snapshot.scroll_position(); + let start_row = scroll_position.y() as u32; + let scroll_top = scroll_position.y() * layout.line_height; + let scroll_left = scroll_position.x() * layout.em_width; + + let range_start = + OffsetUtf16(range_utf16.start).to_display_point(&layout.snapshot.display_snapshot); + if range_start.row() < start_row { + return None; + } + + let line = layout + .line_layouts + .get((range_start.row() - start_row) as usize)?; + let range_start_x = line.x_for_index(range_start.column() as usize); + let range_start_y = range_start.row() as f32 * layout.line_height; + Some(RectF::new( + content_origin + vec2f(range_start_x, range_start_y + layout.line_height) + - vec2f(scroll_left, scroll_top), + vec2f(layout.em_width, layout.line_height), + )) + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/examples/text.rs b/crates/gpui/examples/text.rs index 74e2c2c2ea..8e0153fe38 100644 --- a/crates/gpui/examples/text.rs +++ b/crates/gpui/examples/text.rs @@ -2,11 +2,12 @@ use gpui::{ color::Color, fonts::{Properties, Weight}, text_layout::RunStyle, - DebugContext, Element as _, Quad, + DebugContext, Element as _, MeasurementContext, Quad, }; use log::LevelFilter; use pathfinder_geometry::rect::RectF; use simplelog::SimpleLogger; +use std::ops::Range; fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); @@ -112,6 +113,18 @@ impl gpui::Element for TextElement { false } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index d7b091886b..c098927f4a 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -3,6 +3,7 @@ pub mod action; use crate::{ elements::ElementBox, executor::{self, Task}, + geometry::rect::RectF, keymap::{self, Binding, Keystroke}, platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, presenter::Presenter, @@ -445,6 +446,13 @@ impl InputHandler for WindowInputHandler { ); }); } + + fn rect_for_range(&self, range_utf16: Range) -> Option { + let app = self.app.borrow(); + let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?; + let presenter = presenter.borrow(); + presenter.rect_for_text_range(range_utf16, &app) + } } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 35703b8a1f..7a0da8dad9 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -31,7 +31,9 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, - json, Action, DebugContext, Event, EventContext, LayoutContext, PaintContext, RenderContext, + json, + presenter::MeasurementContext, + Action, DebugContext, Event, EventContext, LayoutContext, PaintContext, RenderContext, SizeConstraint, View, }; use core::panic; @@ -41,7 +43,7 @@ use std::{ borrow::Cow, cell::RefCell, mem, - ops::{Deref, DerefMut}, + ops::{Deref, DerefMut, Range}, rc::Rc, }; @@ -49,6 +51,11 @@ trait AnyElement { fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F; fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext); fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool; + fn rect_for_text_range( + &self, + range_utf16: Range, + cx: &MeasurementContext, + ) -> Option; fn debug(&self, cx: &DebugContext) -> serde_json::Value; fn size(&self) -> Vector2F; @@ -83,6 +90,16 @@ pub trait Element { cx: &mut EventContext, ) -> bool; + fn rect_for_text_range( + &self, + range_utf16: Range, + bounds: RectF, + visible_bounds: RectF, + layout: &Self::LayoutState, + paint: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option; + fn metadata(&self) -> Option<&dyn Any> { None } @@ -287,6 +304,26 @@ impl AnyElement for Lifecycle { } } + fn rect_for_text_range( + &self, + range_utf16: Range, + cx: &MeasurementContext, + ) -> Option { + if let Lifecycle::PostPaint { + element, + bounds, + visible_bounds, + layout, + paint, + .. + } = self + { + element.rect_for_text_range(range_utf16, *bounds, *visible_bounds, layout, paint, cx) + } else { + None + } + } + fn size(&self) -> Vector2F { match self { Lifecycle::Empty | Lifecycle::Init { .. } => panic!("invalid element lifecycle state"), @@ -385,6 +422,14 @@ impl ElementRc { self.element.borrow_mut().dispatch_event(event, cx) } + pub fn rect_for_text_range( + &self, + range_utf16: Range, + cx: &MeasurementContext, + ) -> Option { + self.element.borrow().rect_for_text_range(range_utf16, cx) + } + pub fn size(&self) -> Vector2F { self.element.borrow().size() } diff --git a/crates/gpui/src/elements/align.rs b/crates/gpui/src/elements/align.rs index 5388f7647e..5158b7229e 100644 --- a/crates/gpui/src/elements/align.rs +++ b/crates/gpui/src/elements/align.rs @@ -1,6 +1,8 @@ use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, + json, + presenter::MeasurementContext, + DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; use json::ToJson; @@ -94,6 +96,18 @@ impl Element for Align { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: std::ops::Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, bounds: pathfinder_geometry::rect::RectF, diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 2e10c59049..6fec2dd1dc 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -1,6 +1,7 @@ use super::Element; use crate::{ json::{self, json}, + presenter::MeasurementContext, DebugContext, PaintContext, }; use json::ToJson; @@ -67,6 +68,18 @@ where false } + fn rect_for_text_range( + &self, + _: std::ops::Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/constrained_box.rs b/crates/gpui/src/elements/constrained_box.rs index 5ab01df1e1..012bbe23dd 100644 --- a/crates/gpui/src/elements/constrained_box.rs +++ b/crates/gpui/src/elements/constrained_box.rs @@ -1,9 +1,13 @@ +use std::ops::Range; + use json::ToJson; use serde_json::json; use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, + json, + presenter::MeasurementContext, + DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; @@ -165,6 +169,18 @@ impl Element for ConstrainedBox { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index b8eebad88c..a581e60b74 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + use crate::{ color::Color, geometry::{ @@ -7,6 +9,7 @@ use crate::{ }, json::ToJson, platform::CursorStyle, + presenter::MeasurementContext, scene::{self, Border, CursorRegion, Quad}, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; @@ -271,6 +274,18 @@ impl Element for Container { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/empty.rs b/crates/gpui/src/elements/empty.rs index afe24127b5..9a67115c23 100644 --- a/crates/gpui/src/elements/empty.rs +++ b/crates/gpui/src/elements/empty.rs @@ -1,9 +1,12 @@ +use std::ops::Range; + use crate::{ geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, json::{json, ToJson}, + presenter::MeasurementContext, DebugContext, }; use crate::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint}; @@ -67,6 +70,18 @@ impl Element for Empty { false } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 1fec838788..5fe6900c40 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -1,11 +1,11 @@ use crate::{ - geometry::vector::Vector2F, CursorRegion, DebugContext, Element, ElementBox, Event, - EventContext, LayoutContext, MouseButton, MouseEvent, MouseRegion, NavigationDirection, - PaintContext, SizeConstraint, + geometry::vector::Vector2F, presenter::MeasurementContext, CursorRegion, DebugContext, Element, + ElementBox, Event, EventContext, LayoutContext, MouseButton, MouseEvent, MouseRegion, + NavigationDirection, PaintContext, SizeConstraint, }; use pathfinder_geometry::rect::RectF; use serde_json::json; -use std::{any::TypeId, rc::Rc}; +use std::{any::TypeId, ops::Range, rc::Rc}; pub struct EventHandler { child: ElementBox, @@ -158,6 +158,18 @@ impl Element for EventHandler { } } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/expanded.rs b/crates/gpui/src/elements/expanded.rs index 6f69d8a92a..e8803affab 100644 --- a/crates/gpui/src/elements/expanded.rs +++ b/crates/gpui/src/elements/expanded.rs @@ -1,6 +1,10 @@ +use std::ops::Range; + use crate::{ geometry::{rect::RectF, vector::Vector2F}, - json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, + json, + presenter::MeasurementContext, + DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; use serde_json::json; @@ -74,6 +78,18 @@ impl Element for Expanded { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index cb43c1db68..1d577344c6 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -1,7 +1,8 @@ -use std::{any::Any, f32::INFINITY}; +use std::{any::Any, f32::INFINITY, ops::Range}; use crate::{ json::{self, ToJson, Value}, + presenter::MeasurementContext, Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext, LayoutContext, MouseMovedEvent, PaintContext, RenderContext, ScrollWheelEvent, SizeConstraint, Vector2FExt, View, @@ -334,6 +335,20 @@ impl Element for Flex { handled } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.children + .iter() + .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx)) + } + fn debug( &self, bounds: RectF, @@ -417,6 +432,18 @@ impl Element for FlexItem { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn metadata(&self) -> Option<&dyn Any> { Some(&self.metadata) } diff --git a/crates/gpui/src/elements/hook.rs b/crates/gpui/src/elements/hook.rs index e947c3bac7..c620847372 100644 --- a/crates/gpui/src/elements/hook.rs +++ b/crates/gpui/src/elements/hook.rs @@ -1,6 +1,9 @@ +use std::ops::Range; + use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::json, + presenter::MeasurementContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; @@ -65,6 +68,18 @@ impl Element for Hook { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index 6b55b567b4..dff46804e7 100644 --- a/crates/gpui/src/elements/image.rs +++ b/crates/gpui/src/elements/image.rs @@ -5,11 +5,12 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{json, ToJson}, + presenter::MeasurementContext, scene, Border, DebugContext, Element, Event, EventContext, ImageData, LayoutContext, PaintContext, SizeConstraint, }; use serde::Deserialize; -use std::sync::Arc; +use std::{ops::Range, sync::Arc}; pub struct Image { data: Arc, @@ -89,6 +90,18 @@ impl Element for Image { false } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/keystroke_label.rs b/crates/gpui/src/elements/keystroke_label.rs index 0112b54846..9b21a1b8a8 100644 --- a/crates/gpui/src/elements/keystroke_label.rs +++ b/crates/gpui/src/elements/keystroke_label.rs @@ -76,6 +76,18 @@ impl Element for KeystrokeLabel { element.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs index e6ae9cbd51..1865480868 100644 --- a/crates/gpui/src/elements/label.rs +++ b/crates/gpui/src/elements/label.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + use crate::{ fonts::TextStyle, geometry::{ @@ -5,6 +7,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{ToJson, Value}, + presenter::MeasurementContext, text_layout::{Line, RunStyle}, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; @@ -174,6 +177,18 @@ impl Element for Label { false } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e368b45288..2b44918e43 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -4,6 +4,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::json, + presenter::MeasurementContext, DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext, RenderContext, ScrollWheelEvent, SizeConstraint, View, ViewContext, }; @@ -328,6 +329,39 @@ impl Element for List { handled } + fn rect_for_text_range( + &self, + range_utf16: Range, + bounds: RectF, + _: RectF, + scroll_top: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + let state = self.state.0.borrow(); + let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item); + let mut cursor = state.items.cursor::(); + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + while let Some(item) = cursor.item() { + if item_origin.y() > bounds.max_y() { + break; + } + + if let ListItem::Rendered(element) = item { + if let Some(rect) = element.rect_for_text_range(range_utf16.clone(), cx) { + return Some(rect); + } + + item_origin.set_y(item_origin.y() + element.size().y()); + cursor.next(&()); + } else { + unreachable!(); + } + } + + None + } + fn debug( &self, bounds: RectF, @@ -939,6 +973,18 @@ mod tests { todo!() } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + todo!() + } + fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value { self.id.into() } diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 832aafaa9e..e170ee3163 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, rc::Rc}; +use std::{any::TypeId, ops::Range, rc::Rc}; use super::Padding; use crate::{ @@ -8,8 +8,8 @@ use crate::{ }, platform::CursorStyle, scene::CursorRegion, - DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion, MouseState, - PaintContext, RenderContext, SizeConstraint, View, + DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion, + MouseState, PaintContext, RenderContext, SizeConstraint, View, presenter::MeasurementContext, }; use serde_json::json; @@ -192,6 +192,18 @@ impl Element for MouseEventHandler { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 4ed0ca7d22..bf2ef59579 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -1,6 +1,9 @@ +use std::ops::Range; + use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::ToJson, + presenter::MeasurementContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion, PaintContext, SizeConstraint, }; @@ -126,6 +129,18 @@ impl Element for Overlay { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, cx) + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/stack.rs b/crates/gpui/src/elements/stack.rs index 4531734085..992a8922a7 100644 --- a/crates/gpui/src/elements/stack.rs +++ b/crates/gpui/src/elements/stack.rs @@ -1,6 +1,9 @@ +use std::ops::Range; + use crate::{ geometry::{rect::RectF, vector::Vector2F}, json::{self, json, ToJson}, + presenter::MeasurementContext, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; @@ -64,6 +67,21 @@ impl Element for Stack { false } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.children + .iter() + .rev() + .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx)) + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index d473e1f0fb..544abb3314 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use std::{borrow::Cow, ops::Range}; use serde_json::json; @@ -8,6 +8,7 @@ use crate::{ rect::RectF, vector::{vec2f, Vector2F}, }, + presenter::MeasurementContext, scene, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; @@ -84,6 +85,18 @@ impl Element for Svg { false } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index fb1e2ea331..580240e3fd 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -6,6 +6,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{ToJson, Value}, + presenter::MeasurementContext, text_layout::{Line, RunStyle, ShapedBoundary}, DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache, @@ -63,7 +64,7 @@ impl Element for Text { cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { // Convert the string and highlight ranges into an iterator of highlighted chunks. - + let mut offset = 0; let mut highlight_ranges = self.highlights.iter().peekable(); let chunks = std::iter::from_fn(|| { @@ -81,7 +82,8 @@ impl Element for Text { "Highlight out of text range. Text len: {}, Highlight range: {}..{}", self.text.len(), range.start, - range.end); + range.end + ); result = None; } } else if offset < self.text.len() { @@ -188,6 +190,18 @@ impl Element for Text { false } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &MeasurementContext, + ) -> Option { + None + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index 9a65b2661d..03e5548239 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -6,12 +6,14 @@ use crate::{ fonts::TextStyle, geometry::{rect::RectF, vector::Vector2F}, json::json, + presenter::MeasurementContext, Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint, Task, View, }; use serde::Deserialize; use std::{ cell::{Cell, RefCell}, + ops::Range, rc::Rc, time::Duration, }; @@ -196,6 +198,18 @@ impl Element for Tooltip { self.child.dispatch_event(event, cx) } + fn rect_for_text_range( + &self, + range: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + self.child.rect_for_text_range(range, cx) + } + fn debug( &self, _: RectF, diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 9b2d966a7d..2ab4e91538 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -5,6 +5,7 @@ use crate::{ vector::{vec2f, Vector2F}, }, json::{self, json}, + presenter::MeasurementContext, ElementBox, RenderContext, ScrollWheelEvent, View, }; use json::ToJson; @@ -327,6 +328,21 @@ impl Element for UniformList { handled } + fn rect_for_text_range( + &self, + range: Range, + _: RectF, + _: RectF, + layout: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + layout + .items + .iter() + .find_map(|child| child.rect_for_text_range(range.clone(), cx)) + } + fn debug( &self, bounds: RectF, diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 723e25c55d..4cca93edc8 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -30,7 +30,8 @@ pub mod platform; pub use gpui_macros::test; pub use platform::*; pub use presenter::{ - Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt, + Axis, DebugContext, EventContext, LayoutContext, MeasurementContext, PaintContext, + SizeConstraint, Vector2FExt, }; pub use anyhow; diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index be121ff5b7..aab184f87d 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -91,17 +91,18 @@ pub trait Dispatcher: Send + Sync { pub trait InputHandler { fn selected_text_range(&self) -> Option>; - fn set_selected_text_range(&mut self, range: Range); - fn text_for_range(&self, range: Range) -> Option; + fn set_selected_text_range(&mut self, range_utf16: Range); + fn text_for_range(&self, range_utf16: Range) -> Option; fn replace_text_in_range(&mut self, replacement_range: Option>, text: &str); fn replace_and_mark_text_in_range( &mut self, - range: Option>, + range_utf16: Option>, new_text: &str, new_selected_range: Option>, ); fn marked_text_range(&self) -> Option>; fn unmark_text(&mut self); + fn rect_for_range(&self, range_utf16: Range) -> Option; } pub trait Window: WindowContext { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 2d03e89c2c..ce06ddac37 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1003,8 +1003,33 @@ extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange { .map_or(NSRange::invalid(), |range| range.into()) } -extern "C" fn first_rect_for_character_range(_: &Object, _: Sel, _: NSRange, _: id) -> NSRect { - NSRect::new(NSPoint::new(0., 0.), NSSize::new(20., 20.)) +extern "C" fn first_rect_for_character_range( + this: &Object, + _: Sel, + range: NSRange, + _: id, +) -> NSRect { + let frame = unsafe { + let window = get_window_state(this).borrow().native_window; + NSView::frame(window) + }; + + with_input_handler(this, |input_handler| { + input_handler.rect_for_range(range.to_range()?) + }) + .flatten() + .map_or( + NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)), + |rect| { + NSRect::new( + NSPoint::new( + frame.origin.x + rect.origin_x() as f64, + frame.origin.y + frame.size.height - rect.origin_y() as f64, + ), + NSSize::new(rect.width() as f64, rect.height() as f64), + ) + }, + ) } extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 86a8c4cf30..2b0ad394dd 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -19,7 +19,7 @@ use smallvec::SmallVec; use std::{ collections::{HashMap, HashSet}, marker::PhantomData, - ops::{Deref, DerefMut}, + ops::{Deref, DerefMut, Range}, sync::Arc, }; @@ -224,6 +224,17 @@ impl Presenter { } } + pub fn rect_for_text_range(&self, range_utf16: Range, cx: &AppContext) -> Option { + cx.focused_view_id(self.window_id).and_then(|view_id| { + let cx = MeasurementContext { + app: cx, + rendered_views: &self.rendered_views, + window_id: self.window_id, + }; + cx.rect_for_text_range(view_id, range_utf16) + }) + } + pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool { if let Some(root_view_id) = cx.root_view_id(self.window_id) { let mut invalidated_views = Vec::new(); @@ -777,6 +788,27 @@ impl<'a> DerefMut for EventContext<'a> { } } +pub struct MeasurementContext<'a> { + app: &'a AppContext, + rendered_views: &'a HashMap, + pub window_id: usize, +} + +impl<'a> Deref for MeasurementContext<'a> { + type Target = AppContext; + + fn deref(&self) -> &Self::Target { + self.app + } +} + +impl<'a> MeasurementContext<'a> { + fn rect_for_text_range(&self, view_id: usize, range_utf16: Range) -> Option { + let element = self.rendered_views.get(&view_id)?; + element.rect_for_text_range(range_utf16, self) + } +} + pub struct DebugContext<'a> { rendered_views: &'a HashMap, pub font_cache: &'a FontCache, @@ -936,6 +968,18 @@ impl Element for ChildView { cx.dispatch_event(self.view.id(), event) } + fn rect_for_text_range( + &self, + range_utf16: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + cx: &MeasurementContext, + ) -> Option { + cx.rect_for_text_range(self.view.id(), range_utf16) + } + fn debug( &self, bounds: RectF, diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 3406b4b9e1..b00a706f43 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -395,6 +395,18 @@ impl Element for TerminalEl { } } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &gpui::MeasurementContext, + ) -> Option { + todo!() + } + fn debug( &self, _bounds: gpui::geometry::rect::RectF, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f6c185a19f..a13da1bc7f 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -43,6 +43,7 @@ use std::{ fmt, future::Future, mem, + ops::Range, path::{Path, PathBuf}, rc::Rc, sync::{ @@ -2538,6 +2539,18 @@ impl Element for AvatarRibbon { false } + fn rect_for_text_range( + &self, + _: Range, + _: RectF, + _: RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + _: &gpui::MeasurementContext, + ) -> Option { + None + } + fn debug( &self, bounds: gpui::geometry::rect::RectF,