Detailed changes
@@ -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)
@@ -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<usize>,
+ bounds: RectF,
+ _: RectF,
+ layout: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &gpui::MeasurementContext,
+ ) -> Option<RectF> {
+ 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,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>) -> Option<RectF> {
+ 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"))]
@@ -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<usize>,
+ cx: &MeasurementContext,
+ ) -> Option<RectF>;
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<usize>,
+ bounds: RectF,
+ visible_bounds: RectF,
+ layout: &Self::LayoutState,
+ paint: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF>;
+
fn metadata(&self) -> Option<&dyn Any> {
None
}
@@ -287,6 +304,26 @@ impl<T: Element> AnyElement for Lifecycle<T> {
}
}
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ 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<usize>,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.element.borrow().rect_for_text_range(range_utf16, cx)
+ }
+
pub fn size(&self) -> Vector2F {
self.element.borrow().size()
}
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
bounds: pathfinder_geometry::rect::RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ 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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn metadata(&self) -> Option<&dyn Any> {
Some(&self.metadata)
}
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<ImageData>,
@@ -89,6 +90,18 @@ impl Element for Image {
false
}
+ fn rect_for_text_range(
+ &self,
+ _: Range<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -76,6 +76,18 @@ impl Element for KeystrokeLabel {
element.dispatch_event(event, cx)
}
+ fn rect_for_text_range(
+ &self,
+ _: Range<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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<usize>,
+ bounds: RectF,
+ _: RectF,
+ scroll_top: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ 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::<Count>();
+ 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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ todo!()
+ }
+
fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
self.id.into()
}
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range_utf16, cx)
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.children
+ .iter()
+ .rev()
+ .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ self.child.rect_for_text_range(range, cx)
+ }
+
fn debug(
&self,
_: RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ layout: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ layout
+ .items
+ .iter()
+ .find_map(|child| child.rect_for_text_range(range.clone(), cx))
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -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;
@@ -91,17 +91,18 @@ pub trait Dispatcher: Send + Sync {
pub trait InputHandler {
fn selected_text_range(&self) -> Option<Range<usize>>;
- fn set_selected_text_range(&mut self, range: Range<usize>);
- fn text_for_range(&self, range: Range<usize>) -> Option<String>;
+ fn set_selected_text_range(&mut self, range_utf16: Range<usize>);
+ fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
fn replace_and_mark_text_in_range(
&mut self,
- range: Option<Range<usize>>,
+ range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
);
fn marked_text_range(&self) -> Option<Range<usize>>;
fn unmark_text(&mut self);
+ fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
}
pub trait Window: WindowContext {
@@ -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) {
@@ -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<usize>, cx: &AppContext) -> Option<RectF> {
+ 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<usize, ElementBox>,
+ 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<usize>) -> Option<RectF> {
+ let element = self.rendered_views.get(&view_id)?;
+ element.rect_for_text_range(range_utf16, self)
+ }
+}
+
pub struct DebugContext<'a> {
rendered_views: &'a HashMap<usize, ElementBox>,
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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ cx: &MeasurementContext,
+ ) -> Option<RectF> {
+ cx.rect_for_text_range(self.view.id(), range_utf16)
+ }
+
fn debug(
&self,
bounds: RectF,
@@ -395,6 +395,18 @@ impl Element for TerminalEl {
}
}
+ fn rect_for_text_range(
+ &self,
+ _: Range<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &gpui::MeasurementContext,
+ ) -> Option<RectF> {
+ todo!()
+ }
+
fn debug(
&self,
_bounds: gpui::geometry::rect::RectF,
@@ -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<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &gpui::MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
fn debug(
&self,
bounds: gpui::geometry::rect::RectF,