mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
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:
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;
|
||||
|
||||
Reference in new issue
Block a user