perf(diagnostics): measure graphemes lazily (#25723)

Lazily measure non-ASCII graphemes instead of allocating an eager
boundary table.

Criterion single_unicode_tab: 1.1575 µs → 596.36 ns (-48.6%). ASCII
cases remain neutral or better. Kibana output is unchanged; end-to-end
performance is neutral without type-aware linting.

AI-assisted.
This commit is contained in:
Boshen authored and GitHub committed 2026-08-16 19:06:18 +08:00
1 parent 2dd7446b8f
commit 621808eb31
1 file changed
+21 -35
@@ -10,10 +10,13 @@
//! helpers translate byte offsets into terminal columns, accounting for tabs,
//! ANSI escapes, and wide/combining Unicode graphemes.
use std::str::{CharIndices, from_utf8};
use std::{
iter::Peekable,
str::{CharIndices, from_utf8},
};
use smallvec::SmallVec;
use unicode_segmentation::UnicodeSegmentation;
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
use unicode_width::UnicodeWidthStr;
use super::{handler::GraphicalReportHandler, span::FancySpan};
@@ -87,13 +90,12 @@ impl Line<'_> {
/// Iterator over the visual (terminal-column) width of each `char` in a line.
///
/// ASCII text takes a fast path where every printable char is width 1. For
/// non-ASCII text we pre-compute grapheme boundaries and only charge the
/// grapheme's width to its first `char`, so combining marks contribute 0.
/// non-ASCII text we lazily visit graphemes and only charge the grapheme's
/// width to its first `char`, so combining marks contribute 0.
/// ANSI escape sequences (`\x1b … m`) are consumed at zero width.
struct CharWidthIterator<'a> {
chars: CharIndices<'a>,
grapheme_boundaries: Option<Vec<(usize, usize)>>, // (byte_pos, width) - None for ASCII
current_grapheme_idx: usize,
graphemes: Option<Peekable<GraphemeIndices<'a>>>,
column: usize,
escaped: bool,
}
@@ -111,17 +113,10 @@ impl Iterator for CharWidthIterator<'_> {
0
}
(false, _) => {
if let Some(ref boundaries) = self.grapheme_boundaries {
// Unicode path: check if we're at a grapheme boundary
if self.current_grapheme_idx < boundaries.len()
&& boundaries[self.current_grapheme_idx].0 == byte_pos
{
let width = boundaries[self.current_grapheme_idx].1;
self.current_grapheme_idx += 1;
width
} else {
0 // Not at a grapheme boundary
}
if let Some(graphemes) = &mut self.graphemes {
graphemes
.next_if(|(pos, _)| *pos == byte_pos)
.map_or(0, |(_, grapheme)| grapheme.width())
} else {
// ASCII path: all non-control chars are width 1
1
@@ -142,25 +137,9 @@ impl Iterator for CharWidthIterator<'_> {
impl GraphicalReportHandler {
/// Returns an iterator over the visual width of each character in a line.
pub(super) fn line_visual_char_width(text: &str) -> impl Iterator<Item = usize> + '_ + use<'_> {
// Only compute grapheme boundaries for non-ASCII text
let grapheme_boundaries = if text.is_ascii() {
None
} else {
// Collect grapheme boundaries with their widths
Some(
text.grapheme_indices(true)
.map(|(pos, grapheme)| (pos, grapheme.width()))
.collect(),
)
};
let graphemes = (!text.is_ascii()).then(|| text.grapheme_indices(true).peekable());
CharWidthIterator {
chars: text.char_indices(),
grapheme_boundaries,
current_grapheme_idx: 0,
column: 0,
escaped: false,
}
CharWidthIterator { chars: text.char_indices(), graphemes, column: 0, escaped: false }
}
/// Returns the visual column position of a byte offset on a specific line.
@@ -259,6 +238,13 @@ mod tests {
type ExpectedLine<'a> = (usize, usize, usize, &'a str);
#[test]
fn visual_widths_preserve_grapheme_geometry() {
let widths =
GraphicalReportHandler::line_visual_char_width("e\u{301}火").collect::<Vec<_>>();
assert_eq!(widths, [1, 0, 2]);
}
#[test]
fn get_lines_preserves_line_geometry() {
const BASE: usize = 10;