diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e3e805cf91bd2668ba7dabc90b9623bdd0b63dc6..6d3c7374e2c6e6fce4903066630acd2b58ba258f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 6ec1606bbf6d9bfa5c881ecbb0dea016f5c9e31b..dc01a05dbe0c9c04398afc47a5cae1c2bd7b4e5d 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -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)> { + /// Computes the cursor position based on the cursor point and terminal dimensions. + fn cursor_position(cursor_point: DisplayCursor, size: TerminalBounds) -> Option> { 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