Fix block cursor on graphemes (#19867)

Release Notes:

- Fixed block cursor rendering only first char of multii-char graphemes.
This commit is contained in:
Conrad Irwin 2024-10-28 21:05:24 -06:00 committed by GitHub
parent 1b84fee708
commit 719a7f7890
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 30 additions and 27 deletions

1
Cargo.lock generated
View file

@ -3717,6 +3717,7 @@ dependencies = [
"tree-sitter-rust", "tree-sitter-rust",
"tree-sitter-typescript", "tree-sitter-typescript",
"ui", "ui",
"unicode-segmentation",
"unindent", "unindent",
"url", "url",
"util", "util",

View file

@ -76,6 +76,7 @@ theme.workspace = true
tree-sitter-html = { workspace = true, optional = true } tree-sitter-html = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true } tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true } tree-sitter-typescript = { workspace = true, optional = true }
unicode-segmentation.workspace = true
unindent = { workspace = true, optional = true } unindent = { workspace = true, optional = true }
ui.workspace = true ui.workspace = true
url.workspace = true url.workspace = true

View file

@ -66,7 +66,8 @@ use std::{
use sum_tree::{Bias, TreeMap}; use sum_tree::{Bias, TreeMap};
use tab_map::{TabMap, TabSnapshot}; use tab_map::{TabMap, TabSnapshot};
use text::LineIndent; 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}; use wrap_map::{WrapMap, WrapSnapshot};
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -880,12 +881,10 @@ impl DisplaySnapshot {
layout_line.closest_index_for_x(x) as u32 layout_line.closest_index_for_x(x) as u32
} }
pub fn display_chars_at( pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<SharedString> {
&self,
mut point: DisplayPoint,
) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left)); 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) .flat_map(str::chars)
.skip_while({ .skip_while({
let mut column = 0; let mut column = 0;
@ -895,16 +894,24 @@ impl DisplaySnapshot {
!at_point !at_point
} }
}) })
.map(move |ch| { .take_while({
let result = (ch, point); let mut prev = false;
if ch == '\n' { move |char| {
*point.row_mut() += 1; let now = char.is_ascii();
*point.column_mut() = 0; let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
} else { prev = now;
*point.column_mut() += ch.len_utf8() as u32; !end
} }
result });
}) chars.collect::<String>().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<Item = (char, usize)> + '_ { pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {

View file

@ -68,6 +68,7 @@ use sum_tree::Bias;
use theme::{ActiveTheme, Appearance, PlayerColor}; use theme::{ActiveTheme, Appearance, PlayerColor};
use ui::prelude::*; use ui::prelude::*;
use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip}; use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
use unicode_segmentation::UnicodeSegmentation;
use util::RangeExt; use util::RangeExt;
use util::ResultExt; use util::ResultExt;
use workspace::{item::Item, Workspace}; use workspace::{item::Item, Workspace};
@ -1027,24 +1028,17 @@ impl EditorElement {
} }
let block_text = if let CursorShape::Block = selection.cursor_shape { let block_text = if let CursorShape::Block = selection.cursor_shape {
snapshot snapshot
.display_chars_at(cursor_position) .grapheme_at(cursor_position)
.next()
.or_else(|| { .or_else(|| {
if cursor_column == 0 { if cursor_column == 0 {
snapshot snapshot.placeholder_text().and_then(|s| {
.placeholder_text() s.graphemes(true).next().map(|s| s.to_string().into())
.and_then(|s| s.chars().next()) })
.map(|c| (c, cursor_position))
} else { } else {
None None
} }
}) })
.and_then(|(character, _)| { .and_then(|text| {
let text = if character == '\n' {
SharedString::from(" ")
} else {
SharedString::from(character.to_string())
};
let len = text.len(); let len = text.len();
let font = cursor_row_layout let font = cursor_row_layout