render character under block cursor

Keith Simmons created

Change summary

crates/editor/src/editor.rs    |  4 +-
crates/editor/src/element.rs   | 61 +++++++++++++++++++++++++++--------
crates/gpui/src/text_layout.rs | 20 ++++++++++-
3 files changed, 66 insertions(+), 19 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1023,9 +1023,9 @@ impl Editor {
         cx.notify();
     }
 
-    pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape) {
+    pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
         self.cursor_shape = cursor_shape;
-        // TODO: Do we need to notify?
+        cx.notify();
     }
 
     pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {

crates/editor/src/element.rs 🔗

@@ -16,7 +16,7 @@ use gpui::{
         PathBuilder,
     },
     json::{self, ToJson},
-    text_layout::{self, RunStyle, TextLayoutCache},
+    text_layout::{self, Line, RunStyle, TextLayoutCache},
     AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
     MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
 };
@@ -347,7 +347,7 @@ impl EditorElement {
 
         let mut cursors = SmallVec::<[Cursor; 32]>::new();
         for (replica_id, selections) in &layout.selections {
-            let style = style.replica_selection_style(*replica_id);
+            let selection_style = style.replica_selection_style(*replica_id);
             let corner_radius = 0.15 * layout.line_height;
 
             for selection in selections {
@@ -355,7 +355,7 @@ impl EditorElement {
                     selection.start..selection.end,
                     start_row,
                     end_row,
-                    style.selection,
+                    selection_style.selection,
                     corner_radius,
                     corner_radius * 2.,
                     layout,
@@ -372,24 +372,49 @@ impl EditorElement {
                         let cursor_row_layout =
                             &layout.line_layouts[(cursor_position.row() - start_row) as usize];
                         let cursor_column = cursor_position.column() as usize;
+
                         let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
-                        let mut character_width =
+                        let mut block_width =
                             cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
-                        // TODO: Is there a better option here for the character size
-                        // at the end of the line?
-                        // Default to 1/3 the line height
-                        if character_width == 0.0 {
-                            character_width = layout.line_height / 3.0;
+                        if block_width == 0.0 {
+                            block_width = layout.em_width;
                         }
 
+                        let block_text =
+                            if matches!(self.cursor_shape, CursorShape::Block) {
+                                layout.snapshot.chars_at(cursor_position).next().and_then(
+                                    |character| {
+                                        let font_id =
+                                            cursor_row_layout.font_for_index(cursor_column)?;
+                                        let text = character.to_string();
+
+                                        Some(cx.text_layout_cache.layout_str(
+                                            &text,
+                                            cursor_row_layout.font_size(),
+                                            &[(
+                                                text.len(),
+                                                RunStyle {
+                                                    font_id,
+                                                    color: style.background,
+                                                    underline: None,
+                                                },
+                                            )],
+                                        ))
+                                    },
+                                )
+                            } else {
+                                None
+                            };
+
                         let x = cursor_character_x - scroll_left;
                         let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
                         cursors.push(Cursor {
-                            color: style.cursor,
-                            character_width,
+                            color: selection_style.cursor,
+                            block_width,
                             origin: content_origin + vec2f(x, y),
                             line_height: layout.line_height,
                             shape: self.cursor_shape,
+                            block_text,
                         });
                     }
                 }
@@ -1182,6 +1207,7 @@ fn layout_line(
         while !line.is_char_boundary(len) {
             len -= 1;
         }
+
         line.truncate(len);
     }
 
@@ -1242,16 +1268,17 @@ pub enum CursorShape {
 
 impl Default for CursorShape {
     fn default() -> Self {
-        CursorShape::Bar
+        CursorShape::Block
     }
 }
 
 struct Cursor {
     origin: Vector2F,
-    character_width: f32,
+    block_width: f32,
     line_height: f32,
     color: Color,
     shape: CursorShape,
+    block_text: Option<Line>,
 }
 
 impl Cursor {
@@ -1259,11 +1286,11 @@ impl Cursor {
         let bounds = match self.shape {
             CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)),
             CursorShape::Block => {
-                RectF::new(self.origin, vec2f(self.character_width, self.line_height))
+                RectF::new(self.origin, vec2f(self.block_width, self.line_height))
             }
             CursorShape::Underscore => RectF::new(
                 self.origin + Vector2F::new(0.0, self.line_height - 2.0),
-                vec2f(self.character_width, 2.0),
+                vec2f(self.block_width, 2.0),
             ),
         };
 
@@ -1273,6 +1300,10 @@ impl Cursor {
             border: Border::new(0., Color::black()),
             corner_radius: 0.,
         });
+
+        if let Some(block_text) = &self.block_text {
+            block_text.paint(self.origin, bounds, self.line_height, cx);
+        }
     }
 }
 

crates/gpui/src/text_layout.rs 🔗

@@ -186,7 +186,7 @@ pub struct Run {
     pub glyphs: Vec<Glyph>,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct Glyph {
     pub id: GlyphId,
     pub position: Vector2F,
@@ -210,10 +210,14 @@ impl Line {
         self.layout.width
     }
 
+    pub fn font_size(&self) -> f32 {
+        self.layout.font_size
+    }
+
     pub fn x_for_index(&self, index: usize) -> f32 {
         for run in &self.layout.runs {
             for glyph in &run.glyphs {
-                if glyph.index == index {
+                if glyph.index >= index {
                     return glyph.position.x();
                 }
             }
@@ -221,6 +225,18 @@ impl Line {
         self.layout.width
     }
 
+    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
+        for run in &self.layout.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return Some(run.font_id);
+                }
+            }
+        }
+
+        None
+    }
+
     pub fn index_for_x(&self, x: f32) -> Option<usize> {
         if x >= self.layout.width {
             None