diff --git a/Cargo.lock b/Cargo.lock index 921ec3a4f0..1b3c4de81d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3717,6 +3717,7 @@ dependencies = [ "tree-sitter-rust", "tree-sitter-typescript", "ui", + "unicode-segmentation", "unindent", "url", "util", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index cfd9284f80..bff1935f86 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -76,6 +76,7 @@ theme.workspace = true tree-sitter-html = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true } +unicode-segmentation.workspace = true unindent = { workspace = true, optional = true } ui.workspace = true url.workspace = true diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 79a2fbdb11..c176213682 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -66,7 +66,8 @@ use std::{ use sum_tree::{Bias, TreeMap}; use tab_map::{TabMap, TabSnapshot}; use text::LineIndent; -use ui::{div, px, IntoElement, ParentElement, Styled, WindowContext}; +use ui::{div, px, IntoElement, ParentElement, SharedString, Styled, WindowContext}; +use unicode_segmentation::UnicodeSegmentation; use wrap_map::{WrapMap, WrapSnapshot}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -880,12 +881,10 @@ impl DisplaySnapshot { layout_line.closest_index_for_x(x) as u32 } - pub fn display_chars_at( - &self, - mut point: DisplayPoint, - ) -> impl Iterator + '_ { + pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option { point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left)); - self.text_chunks(point.row()) + let chars = self + .text_chunks(point.row()) .flat_map(str::chars) .skip_while({ let mut column = 0; @@ -895,16 +894,24 @@ impl DisplaySnapshot { !at_point } }) - .map(move |ch| { - let result = (ch, point); - if ch == '\n' { - *point.row_mut() += 1; - *point.column_mut() = 0; - } else { - *point.column_mut() += ch.len_utf8() as u32; + .take_while({ + let mut prev = false; + move |char| { + let now = char.is_ascii(); + let end = char.is_ascii() && (char.is_ascii_whitespace() || prev); + prev = now; + !end } - result - }) + }); + chars.collect::().graphemes(true).next().map(|s| { + if let Some(invisible) = s.chars().next().filter(|&c| is_invisible(c)) { + replacement(invisible).unwrap_or(s).to_owned().into() + } else if s == "\n" { + " ".into() + } else { + s.to_owned().into() + } + }) } pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator + '_ { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2c3bed7eb7..3ece171b05 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -68,6 +68,7 @@ use sum_tree::Bias; use theme::{ActiveTheme, Appearance, PlayerColor}; use ui::prelude::*; use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip}; +use unicode_segmentation::UnicodeSegmentation; use util::RangeExt; use util::ResultExt; use workspace::{item::Item, Workspace}; @@ -1027,24 +1028,17 @@ impl EditorElement { } let block_text = if let CursorShape::Block = selection.cursor_shape { snapshot - .display_chars_at(cursor_position) - .next() + .grapheme_at(cursor_position) .or_else(|| { if cursor_column == 0 { - snapshot - .placeholder_text() - .and_then(|s| s.chars().next()) - .map(|c| (c, cursor_position)) + snapshot.placeholder_text().and_then(|s| { + s.graphemes(true).next().map(|s| s.to_string().into()) + }) } else { None } }) - .and_then(|(character, _)| { - let text = if character == '\n' { - SharedString::from(" ") - } else { - SharedString::from(character.to_string()) - }; + .and_then(|text| { let len = text.len(); let font = cursor_row_layout