`CharExt::column_count` was hard-coded to return 1 for every char. The code editor's layouter advances x-position by this value per grapheme (see `code_editor.rs:1217`), so CJK glyphs — which the text shaper draws at ~2× the Latin monospace advance — overlap one another. Cursor placement, selection rectangles, and wrap points suffer the same off-by-half because they all read from `column_count`. Match the Unicode East Asian Width property so Wide and Fullwidth characters (plus common emoji that render at double-width) report 2 columns. Keeps a small literal match table instead of pulling in the \`unicode-width\` crate, since only broad blocks are needed and perf on the layout hot path matters. Ranges covered: U+3000..U+30FF CJK punctuation / Hiragana / Katakana U+3400..U+4DBF CJK Unified Ideographs Extension A U+4E00..U+9FFF CJK Unified Ideographs U+AC00..U+D7AF Hangul Syllables U+F900..U+FAFF CJK Compatibility Ideographs U+FF00..U+FF60 Fullwidth forms U+FFE0..U+FFE6 Fullwidth sign forms U+20000..U+2FFFF CJK Unified Ideographs Extensions B..F U+1F300..U+1F9FF Emoticons / symbols / transport / supplemental Effect: Chinese/Japanese/Korean/emoji in code blocks render with correct spacing in CodeView (and in any widget that layouts via CodeSession). Latin-only workflows are unaffected. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
61 lines
1.9 KiB
Rust
61 lines
1.9 KiB
Rust
pub trait CharExt {
|
||
fn is_opening_delimiter(self) -> bool;
|
||
fn is_closing_delimiter(self) -> bool;
|
||
fn column_count(self) -> usize;
|
||
fn opposite_delimiter(&self) -> Option<char>;
|
||
}
|
||
|
||
impl CharExt for char {
|
||
fn is_opening_delimiter(self) -> bool {
|
||
match self {
|
||
'(' | '[' | '{' => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
fn is_closing_delimiter(self) -> bool {
|
||
match self {
|
||
')' | ']' | '}' => true,
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
fn column_count(self) -> usize {
|
||
// Unicode East Asian Width: characters in East Asian Wide or Fullwidth
|
||
// categories occupy two display columns in a monospace grid. Without
|
||
// this, CJK text in the code editor overlaps because each glyph draws
|
||
// at 2× advance but the layouter only reserves 1 column.
|
||
match self as u32 {
|
||
// CJK Symbols and Punctuation, Hiragana, Katakana
|
||
0x3000..=0x30FF
|
||
// CJK Unified Ideographs Extension A
|
||
| 0x3400..=0x4DBF
|
||
// CJK Unified Ideographs (main block)
|
||
| 0x4E00..=0x9FFF
|
||
// Hangul Syllables
|
||
| 0xAC00..=0xD7AF
|
||
// CJK Compatibility Ideographs
|
||
| 0xF900..=0xFAFF
|
||
// Fullwidth forms + Halfwidth/Fullwidth punctuation
|
||
| 0xFF00..=0xFF60
|
||
| 0xFFE0..=0xFFE6
|
||
// CJK Unified Ideographs Extensions B..F
|
||
| 0x20000..=0x2FFFF
|
||
// Emoticons, misc symbols & pictographs, transport, supplemental symbols
|
||
| 0x1F300..=0x1F9FF => 2,
|
||
_ => 1,
|
||
}
|
||
}
|
||
|
||
fn opposite_delimiter(&self) -> Option<char> {
|
||
Some(match self {
|
||
'(' => ')',
|
||
')' => '(',
|
||
'[' => ']',
|
||
']' => '[',
|
||
'{' => '}',
|
||
'}' => '{',
|
||
_ => return None,
|
||
})
|
||
}
|
||
}
|