Calculate an overshoot when mousing to build columnar selections

Antonio Scandurra created

Change summary

crates/editor/src/element.rs          | 20 +++++++++---
crates/editor/src/lib.rs              | 44 ++++++++++++++++++----------
crates/gpui/src/font_cache.rs         | 11 +++++++
crates/gpui/src/fonts.rs              |  4 ++
crates/gpui/src/platform.rs           |  1 
crates/gpui/src/platform/mac/fonts.rs |  8 +++++
6 files changed, 66 insertions(+), 22 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -69,10 +69,13 @@ impl EditorElement {
         }
 
         let snapshot = self.snapshot(cx.app);
-        let position = paint.point_for_position(&snapshot, layout, position);
+        let (position, overshoot) = paint.point_for_position(&snapshot, layout, position);
 
         if shift && alt {
-            cx.dispatch_action(Select(SelectPhase::BeginColumnar { position }));
+            cx.dispatch_action(Select(SelectPhase::BeginColumnar {
+                position,
+                overshoot,
+            }));
         } else if shift {
             cx.dispatch_action(Select(SelectPhase::Extend {
                 position,
@@ -138,10 +141,11 @@ impl EditorElement {
             let font_cache = cx.font_cache.clone();
             let text_layout_cache = cx.text_layout_cache.clone();
             let snapshot = self.snapshot(cx.app);
-            let position = paint.point_for_position(&snapshot, layout, position);
+            let (position, overshoot) = paint.point_for_position(&snapshot, layout, position);
 
             cx.dispatch_action(Select(SelectPhase::Update {
                 position,
+                overshoot,
                 scroll_position: (snapshot.scroll_position() + scroll_delta).clamp(
                     Vector2F::zero(),
                     layout.scroll_max(&font_cache, &text_layout_cache),
@@ -687,6 +691,7 @@ impl Element for EditorElement {
         let text_width = size.x() - gutter_width;
         let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.);
         let em_width = style.text.em_width(cx.font_cache);
+        let em_advance = style.text.em_advance(cx.font_cache);
         let overscroll = vec2f(em_width, 0.);
         let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
         let snapshot = self.update_view(cx.app, |view, cx| {
@@ -786,6 +791,7 @@ impl Element for EditorElement {
             block_layouts,
             line_height,
             em_width,
+            em_advance,
             selections,
             max_visible_line_width,
         };
@@ -914,6 +920,7 @@ pub struct LayoutState {
     block_layouts: Vec<(Range<u32>, BlockStyle)>,
     line_height: f32,
     em_width: f32,
+    em_advance: f32,
     selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
     overscroll: Vector2F,
     text_offset: Vector2F,
@@ -982,7 +989,7 @@ impl PaintState {
         snapshot: &Snapshot,
         layout: &LayoutState,
         position: Vector2F,
-    ) -> DisplayPoint {
+    ) -> (DisplayPoint, u32) {
         let scroll_position = snapshot.scroll_position();
         let position = position - self.text_bounds.origin();
         let y = position.y().max(0.0).min(layout.size.y());
@@ -994,12 +1001,13 @@ impl PaintState {
         let column = if x >= 0.0 {
             line.index_for_x(x)
                 .map(|ix| ix as u32)
-                .unwrap_or(snapshot.line_len(row))
+                .unwrap_or_else(|| snapshot.line_len(row))
         } else {
             0
         };
+        let overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32;
 
-        DisplayPoint::new(row, column)
+        (DisplayPoint::new(row, column), overshoot)
     }
 }
 

crates/editor/src/lib.rs 🔗

@@ -284,6 +284,7 @@ pub enum SelectPhase {
     },
     BeginColumnar {
         position: DisplayPoint,
+        overshoot: u32,
     },
     Extend {
         position: DisplayPoint,
@@ -291,6 +292,7 @@ pub enum SelectPhase {
     },
     Update {
         position: DisplayPoint,
+        overshoot: u32,
         scroll_position: Vector2F,
     },
     End,
@@ -661,15 +663,19 @@ impl Editor {
                 add,
                 click_count,
             } => self.begin_selection(*position, *add, *click_count, cx),
-            SelectPhase::BeginColumnar { position } => self.begin_columnar_selection(*position, cx),
+            SelectPhase::BeginColumnar {
+                position,
+                overshoot,
+            } => self.begin_columnar_selection(*position, *overshoot, cx),
             SelectPhase::Extend {
                 position,
                 click_count,
             } => self.extend_selection(*position, *click_count, cx),
             SelectPhase::Update {
                 position,
+                overshoot,
                 scroll_position,
-            } => self.update_selection(*position, *scroll_position, cx),
+            } => self.update_selection(*position, *overshoot, *scroll_position, cx),
             SelectPhase::End => self.end_selection(cx),
         }
     }
@@ -780,7 +786,12 @@ impl Editor {
         cx.notify();
     }
 
-    fn begin_columnar_selection(&mut self, position: DisplayPoint, cx: &mut ViewContext<Self>) {
+    fn begin_columnar_selection(
+        &mut self,
+        position: DisplayPoint,
+        overshoot: u32,
+        cx: &mut ViewContext<Self>,
+    ) {
         if !self.focused {
             cx.focus_self();
             cx.emit(Event::Activate);
@@ -795,6 +806,7 @@ impl Editor {
         self.select_columns(
             tail.to_display_point(&display_map),
             position,
+            overshoot,
             &display_map,
             cx,
         );
@@ -803,16 +815,15 @@ impl Editor {
     fn update_selection(
         &mut self,
         position: DisplayPoint,
+        overshoot: u32,
         scroll_position: Vector2F,
         cx: &mut ViewContext<Self>,
     ) {
-        log::info!("update selection");
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
 
         if let Some(tail) = self.columnar_selection_tail.as_ref() {
             let tail = tail.to_display_point(&display_map);
-            log::info!("columnar tail {:?}", tail);
-            self.select_columns(tail, position, &display_map, cx);
+            self.select_columns(tail, position, overshoot, &display_map, cx);
         } else if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() {
             let buffer = self.buffer.read(cx);
             let head;
@@ -904,13 +915,14 @@ impl Editor {
         &mut self,
         tail: DisplayPoint,
         head: DisplayPoint,
+        overshoot: u32,
         display_map: &DisplayMapSnapshot,
         cx: &mut ViewContext<Self>,
     ) {
         let start_row = cmp::min(tail.row(), head.row());
         let end_row = cmp::max(tail.row(), head.row());
-        let start_column = cmp::min(tail.column(), head.column());
-        let end_column = cmp::max(tail.column(), head.column());
+        let start_column = cmp::min(tail.column(), head.column() + overshoot);
+        let end_column = cmp::max(tail.column(), head.column() + overshoot);
 
         let selections = (start_row..=end_row)
             .filter_map(|row| {
@@ -3554,7 +3566,7 @@ mod tests {
         );
 
         editor.update(cx, |view, cx| {
-            view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
         });
 
         assert_eq!(
@@ -3563,7 +3575,7 @@ mod tests {
         );
 
         editor.update(cx, |view, cx| {
-            view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
         });
 
         assert_eq!(
@@ -3573,7 +3585,7 @@ mod tests {
 
         editor.update(cx, |view, cx| {
             view.end_selection(cx);
-            view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
         });
 
         assert_eq!(
@@ -3583,7 +3595,7 @@ mod tests {
 
         editor.update(cx, |view, cx| {
             view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
-            view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
         });
 
         assert_eq!(
@@ -3619,7 +3631,7 @@ mod tests {
         });
 
         view.update(cx, |view, cx| {
-            view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
             assert_eq!(
                 view.selection_ranges(cx),
                 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
@@ -3628,7 +3640,7 @@ mod tests {
 
         view.update(cx, |view, cx| {
             view.cancel(&Cancel, cx);
-            view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
             assert_eq!(
                 view.selection_ranges(cx),
                 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
@@ -3644,11 +3656,11 @@ mod tests {
 
         view.update(cx, |view, cx| {
             view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
-            view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
             view.end_selection(cx);
 
             view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
-            view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), cx);
+            view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
             view.end_selection(cx);
             assert_eq!(
                 view.selection_ranges(cx),

crates/gpui/src/font_cache.rs 🔗

@@ -157,6 +157,17 @@ impl FontCache {
         bounds.width() * self.em_scale(font_id, font_size)
     }
 
+    pub fn em_advance(&self, font_id: FontId, font_size: f32) -> f32 {
+        let glyph_id;
+        let advance;
+        {
+            let state = self.0.read();
+            glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap();
+            advance = state.fonts.advance(font_id, glyph_id).unwrap();
+        }
+        advance.x() * self.em_scale(font_id, font_size)
+    }
+
     pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 {
         let height = self.metric(font_id, |m| m.bounding_box.height());
         (height * self.em_scale(font_id, font_size)).ceil()

crates/gpui/src/fonts.rs 🔗

@@ -151,6 +151,10 @@ impl TextStyle {
         font_cache.em_width(self.font_id, self.font_size)
     }
 
+    pub fn em_advance(&self, font_cache: &FontCache) -> f32 {
+        font_cache.em_advance(self.font_id, self.font_size)
+    }
+
     pub fn descent(&self, font_cache: &FontCache) -> f32 {
         font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache)
     }

crates/gpui/src/platform.rs 🔗

@@ -148,6 +148,7 @@ pub trait FontSystem: Send + Sync {
     ) -> anyhow::Result<FontId>;
     fn font_metrics(&self, font_id: FontId) -> FontMetrics;
     fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF>;
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F>;
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
     fn rasterize_glyph(
         &self,

crates/gpui/src/platform/mac/fonts.rs 🔗

@@ -69,6 +69,10 @@ impl platform::FontSystem for FontSystem {
         self.0.read().typographic_bounds(font_id, glyph_id)
     }
 
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
+        self.0.read().advance(font_id, glyph_id)
+    }
+
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
         self.0.read().glyph_for_char(font_id, ch)
     }
@@ -137,6 +141,10 @@ impl FontSystemState {
         Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?)
     }
 
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
+        Ok(self.fonts[font_id.0].advance(glyph_id)?)
+    }
+
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
         self.fonts[font_id.0].glyph_for_char(ch)
     }