mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-04 02:05:31 +00:00
gpui: Improve performance of laying out long lines (#19215)
TL;DR: Another O(n^2) strikes. In #19194 we received a report about a 7Mb JSON file that Zed struggles with. Naturally this file showcased a O(n^2) in line layout; this file has one long line. During line layout for Mac we have to convert between UTF-16 and UTF-8 indices in the string, as CoreText works with UTF-16 and Rust strings are UTF-8. The problem stemmed from the fact that we were re-seeking our string converter on each glyph, which boils down to: we were reparsing [0..curr_string_position] bytes up to full length of the string, which is the O(n^2) in question. This PR changes this behaviour to reuse the Index Converter if the position we're seeking to is not yet reached. Basically, we're treating the converter as forward iterator and we try to seek with the same iterator, if possible. Where previously you could not even open the file in OP (within reasonable time frame, I waited for 40 seconds before giving up), now you can do it in.. slightly over a second. The best part is: the experience is still not ideal. Typing in the buffer is sluggish. Still, this is a start. Release Notes: - Mac: Improved performance with very long lines
This commit is contained in:
parent
397e4bee0a
commit
4fa75a78b9
2 changed files with 11 additions and 15 deletions
|
@ -13087,18 +13087,11 @@ fn snippet_completions(
|
|||
return vec![];
|
||||
}
|
||||
let snapshot = buffer.read(cx).text_snapshot();
|
||||
let chunks = snapshot.reversed_chunks_in_range(text::Anchor::MIN..buffer_position);
|
||||
|
||||
let mut lines = chunks.lines();
|
||||
let Some(line_at) = lines.next().filter(|line| !line.is_empty()) else {
|
||||
return vec![];
|
||||
};
|
||||
let chars = snapshot.reversed_chars_for_range(text::Anchor::MIN..buffer_position);
|
||||
|
||||
let scope = language.map(|language| language.default_scope());
|
||||
let classifier = CharClassifier::new(scope).for_completion(true);
|
||||
let mut last_word = line_at
|
||||
.chars()
|
||||
.rev()
|
||||
let mut last_word = chars
|
||||
.take_while(|c| classifier.is_word(*c))
|
||||
.collect::<String>();
|
||||
last_word = last_word.chars().rev().collect();
|
||||
|
|
|
@ -470,9 +470,10 @@ impl MacTextSystemState {
|
|||
|
||||
// Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
|
||||
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
|
||||
|
||||
let mut runs = Vec::new();
|
||||
for run in line.glyph_runs().into_iter() {
|
||||
let glyph_runs = line.glyph_runs();
|
||||
let mut runs = Vec::with_capacity(glyph_runs.len() as usize);
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
for run in glyph_runs.into_iter() {
|
||||
let attributes = run.attributes().unwrap();
|
||||
let font = unsafe {
|
||||
attributes
|
||||
|
@ -482,7 +483,6 @@ impl MacTextSystemState {
|
|||
};
|
||||
let font_id = self.id_for_native_font(font);
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut glyphs = SmallVec::new();
|
||||
for ((glyph_id, position), glyph_utf16_ix) in run
|
||||
.glyphs()
|
||||
|
@ -491,6 +491,10 @@ impl MacTextSystemState {
|
|||
.zip(run.string_indices().iter())
|
||||
{
|
||||
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
|
||||
if ix_converter.utf16_ix > glyph_utf16_ix {
|
||||
// We cannot reuse current index converter, as it can only seek forward. Restart the search.
|
||||
ix_converter = StringIndexConverter::new(text);
|
||||
}
|
||||
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
|
||||
glyphs.push(ShapedGlyph {
|
||||
id: GlyphId(*glyph_id as u32),
|
||||
|
@ -500,9 +504,8 @@ impl MacTextSystemState {
|
|||
});
|
||||
}
|
||||
|
||||
runs.push(ShapedRun { font_id, glyphs })
|
||||
runs.push(ShapedRun { font_id, glyphs });
|
||||
}
|
||||
|
||||
let typographic_bounds = line.get_typographic_bounds();
|
||||
LineLayout {
|
||||
runs,
|
||||
|
|
Loading…
Reference in a new issue