@@ -1817,67 +1817,74 @@ impl EditorElement {
let cursor_next_x = cursor_row_layout.x_for_index(cursor_column + 1)
+ cursor_row_layout
.alignment_offset(self.style.text.text_align, text_hitbox.size.width);
- let mut block_width = cursor_next_x - cursor_character_x;
- if block_width == Pixels::ZERO {
- block_width = em_advance;
+ let mut cell_width = cursor_next_x - cursor_character_x;
+ if cell_width == Pixels::ZERO {
+ cell_width = em_advance;
}
- let block_text = if selection.cursor_shape == CursorShape::Block
- && !redacted_ranges.iter().any(|range| {
- range.start <= cursor_position && cursor_position < range.end
+
+ let mut block_width = cell_width;
+ let mut block_text = None;
+
+ let is_cursor_in_redacted_range = redacted_ranges
+ .iter()
+ .any(|range| range.start <= cursor_position && cursor_position < range.end);
+
+ if selection.cursor_shape == CursorShape::Block && !is_cursor_in_redacted_range
+ {
+ if let Some(text) = snapshot.grapheme_at(cursor_position).or_else(|| {
+ if snapshot.is_empty() {
+ snapshot.placeholder_text().and_then(|s| {
+ s.graphemes(true).next().map(|s| s.to_string().into())
+ })
+ } else {
+ None
+ }
}) {
- snapshot
- .grapheme_at(cursor_position)
- .or_else(|| {
- if snapshot.is_empty() {
- snapshot.placeholder_text().and_then(|s| {
- s.graphemes(true).next().map(|s| s.to_string().into())
- })
- } else {
- None
+ let is_ascii_whitespace_only =
+ text.as_ref().chars().all(|c| c.is_ascii_whitespace());
+ let len = text.len();
+
+ let mut font = cursor_row_layout
+ .font_id_for_index(cursor_column)
+ .and_then(|cursor_font_id| {
+ window.text_system().get_font_for_id(cursor_font_id)
+ })
+ .unwrap_or(self.style.text.font());
+ font.features = self.style.text.font_features.clone();
+
+ // Invert the text color for the block cursor. Ensure that the text
+ // color is opaque enough to be visible against the background color.
+ //
+ // 0.75 is an arbitrary threshold to determine if the background color is
+ // opaque enough to use as a text color.
+ //
+ // TODO: In the future we should ensure themes have a `text_inverse` color.
+ let color = if cx.theme().colors().editor_background.a < 0.75 {
+ match cx.theme().appearance {
+ Appearance::Dark => Hsla::black(),
+ Appearance::Light => Hsla::white(),
}
- })
- .map(|text| {
- let len = text.len();
-
- let mut font = cursor_row_layout
- .font_id_for_index(cursor_column)
- .and_then(|cursor_font_id| {
- window.text_system().get_font_for_id(cursor_font_id)
- })
- .unwrap_or(self.style.text.font());
- font.features = self.style.text.font_features.clone();
-
- // Invert the text color for the block cursor. Ensure that the text
- // color is opaque enough to be visible against the background color.
- //
- // 0.75 is an arbitrary threshold to determine if the background color is
- // opaque enough to use as a text color.
- //
- // TODO: In the future we should ensure themes have a `text_inverse` color.
- let color = if cx.theme().colors().editor_background.a < 0.75 {
- match cx.theme().appearance {
- Appearance::Dark => Hsla::black(),
- Appearance::Light => Hsla::white(),
- }
- } else {
- cx.theme().colors().editor_background
- };
+ } else {
+ cx.theme().colors().editor_background
+ };
- window.text_system().shape_line(
- text,
- cursor_row_layout.font_size,
- &[TextRun {
- len,
- font,
- color,
- ..Default::default()
- }],
- None,
- )
- })
- } else {
- None
- };
+ let shaped = window.text_system().shape_line(
+ text,
+ cursor_row_layout.font_size,
+ &[TextRun {
+ len,
+ font,
+ color,
+ ..Default::default()
+ }],
+ None,
+ );
+ if !is_ascii_whitespace_only {
+ block_width = block_width.max(shaped.width);
+ }
+ block_text = Some(shaped);
+ }
+ }
let x = cursor_character_x - scroll_pixel_position.x.into();
let y = ((cursor_position.row().as_f64() - scroll_position.y)
@@ -4,7 +4,7 @@ use gpui::{
Element, ElementId, Entity, FocusHandle, Font, FontFeatures, FontStyle, FontWeight,
GlobalElementId, HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement, Interactivity,
IntoElement, LayoutId, Length, ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels,
- Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle,
+ Point, StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle,
UTF16Selection, UnderlineStyle, WeakEntity, WhiteSpace, Window, div, fill, point, px, relative,
size,
};
@@ -56,6 +56,7 @@ pub struct LayoutState {
}
/// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.
+#[derive(Copy, Clone)]
struct DisplayCursor {
line: i32,
col: usize,
@@ -499,28 +500,13 @@ impl TerminalElement {
(rects, batched_runs)
}
- /// Computes the cursor position and expected block width, may return a zero width if x_for_index returns
- /// the same position for sequential indexes. Use em_width instead
- fn shape_cursor(
- cursor_point: DisplayCursor,
- size: TerminalBounds,
- text_fragment: &ShapedLine,
- ) -> Option<(Point<Pixels>, Pixels)> {
+ /// Computes the cursor position based on the cursor point and terminal dimensions.
+ fn cursor_position(cursor_point: DisplayCursor, size: TerminalBounds) -> Option<Point<Pixels>> {
if cursor_point.line() < size.total_lines() as i32 {
- let cursor_width = if text_fragment.width == Pixels::ZERO {
- size.cell_width()
- } else {
- text_fragment.width
- };
-
- // Cursor should always surround as much of the text as possible,
- // hence when on pixel boundaries round the origin down and the width up
- Some((
- point(
- (cursor_point.col() as f32 * size.cell_width()).floor(),
- (cursor_point.line() as f32 * size.line_height()).floor(),
- ),
- cursor_width.ceil(),
+ // When on pixel boundaries round the origin down
+ Some(point(
+ (cursor_point.col() as f32 * size.cell_width()).floor(),
+ (cursor_point.line() as f32 * size.line_height()).floor(),
))
} else {
None
@@ -1148,13 +1134,20 @@ impl Element for TerminalElement {
)
};
- let ime_cursor_bounds =
- TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
- |(cursor_position, block_width)| Bounds {
- origin: cursor_position,
- size: size(block_width, dimensions.line_height),
- },
- );
+ // For whitespace, use cell width to avoid cursor stretching.
+ // For other characters, use the larger of shaped width and cell width
+ // to properly cover wide characters like emojis.
+ let cursor_width = if cursor_char.is_whitespace() {
+ dimensions.cell_width()
+ } else {
+ cursor_text.width.max(dimensions.cell_width())
+ };
+
+ let ime_cursor_bounds = TerminalElement::cursor_position(cursor_point, dimensions)
+ .map(|cursor_position| Bounds {
+ origin: cursor_position,
+ size: size(cursor_width.ceil(), dimensions.line_height),
+ });
let cursor = if let AlacCursorShape::Hidden = cursor.shape {
None