diff --git a/crates/editor/benches/display_map.rs b/crates/editor/benches/display_map.rs index 148c7bd4ed2..c48f0c50b72 100644 --- a/crates/editor/benches/display_map.rs +++ b/crates/editor/benches/display_map.rs @@ -1,10 +1,12 @@ -use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; -use editor::MultiBuffer; -use gpui::TestDispatcher; +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use editor::{MultiBuffer, display_map::*}; +use gpui::{AppContext as _, HighlightStyle, Hsla, TestDispatcher, font, px}; use itertools::Itertools; use multi_buffer::MultiBufferOffset; +use project::project_settings::DiagnosticSeverity; use rand::{Rng, SeedableRng, rngs::StdRng}; -use std::num::NonZeroU32; +use settings::SettingsStore; +use std::{num::NonZeroU32, time::Duration}; use text::Bias; use util::RandomCharIter; @@ -101,5 +103,112 @@ fn to_fold_point_benchmark(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, to_tab_point_benchmark, to_fold_point_benchmark); +fn create_highlight_endpoints_benchmark(c: &mut Criterion) { + const LINE_COUNT: usize = 20_000; + const LINE_VIEW_PORT_COUNT: usize = 100; + const HIGHLIGHTS_PER_LINE: usize = 4; + + let dispatcher = TestDispatcher::new(1); + let mut cx = gpui::TestAppContext::build(dispatcher, None); + cx.update(|cx| { + let store = SettingsStore::test(cx); + cx.set_global(store); + editor::init(cx); + }); + + let mut text = String::new(); + let mut highlight_ranges = Vec::with_capacity(LINE_COUNT * HIGHLIGHTS_PER_LINE); + for line in 0..LINE_COUNT { + text.push_str("fn item_"); + text.push_str(&format!("{line:05}")); + text.push_str("() { "); + + let start = text.len(); + text.push_str("alpha_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str(" + "); + let start = text.len(); + text.push_str("beta_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str(" + "); + let start = text.len(); + text.push_str("gamma_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str(" + "); + let start = text.len(); + text.push_str("delta_highlight"); + highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len())); + + text.push_str("; }\n"); + } + + let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); + let buffer_snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx)); + let highlight_ranges = highlight_ranges + .into_iter() + .map(|range| { + buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_before(range.end) + }) + .collect(); + + let map = cx.new(|cx| { + DisplayMap::new( + buffer, + font("Courier"), + px(16.0), + None, + 1, + 1, + FoldPlaceholder::default(), + DiagnosticSeverity::Warning, + cx, + ) + }); + cx.update(|cx| { + map.update(cx, |map, cx| { + map.highlight_text( + HighlightKey::Editor, + highlight_ranges, + HighlightStyle { + color: Some(Hsla::blue()), + ..Default::default() + }, + false, + cx, + ); + }); + }); + let snapshot = cx.update(|cx| map.update(cx, |map, cx| map.snapshot(cx))); + + let mut group = c.benchmark_group("Create highlight endpoints"); + group.sample_size(10); + group.measurement_time(Duration::from_secs(10)); + group.bench_with_input( + BenchmarkId::new("text_highlights", LINE_VIEW_PORT_COUNT), + &snapshot, + |bench, snapshot| { + bench.iter(|| { + black_box(snapshot.chunks( + DisplayRow(400)..DisplayRow(400 + LINE_VIEW_PORT_COUNT as u32), + language::LanguageAwareStyling { + tree_sitter: false, + diagnostics: false, + }, + Default::default(), + )); + }); + }, + ); + group.finish(); +} + +criterion_group!( + benches, + to_tab_point_benchmark, + to_fold_point_benchmark, + create_highlight_endpoints_benchmark +); criterion_main!(benches); diff --git a/crates/editor/src/display_map/custom_highlights.rs b/crates/editor/src/display_map/custom_highlights.rs index 6e93e562172..92746c0992b 100644 --- a/crates/editor/src/display_map/custom_highlights.rs +++ b/crates/editor/src/display_map/custom_highlights.rs @@ -1,7 +1,7 @@ use collections::BTreeMap; use gpui::HighlightStyle; use language::{Chunk, LanguageAwareStyling}; -use multi_buffer::{MultiBufferChunks, MultiBufferOffset, MultiBufferSnapshot, ToOffset as _}; +use multi_buffer::{MultiBufferChunks, MultiBufferOffset, MultiBufferSnapshot}; use std::{ cmp, iter::{self, Peekable}, @@ -81,6 +81,8 @@ fn create_highlight_endpoints( if let Some(text_highlights) = text_highlights { let start = buffer.anchor_after(range.start); let end = buffer.anchor_after(range.end); + let mut v = Vec::new(); + for (&tag, text_highlights) in text_highlights.iter() { let style = text_highlights.0; let ranges = &text_highlights.1; @@ -94,25 +96,39 @@ fn create_highlight_endpoints( }) .unwrap_or_else(|i| i); - highlight_endpoints.reserve(2 * end_ix); + let ranges_ = &ranges[start_ix..][..end_ix]; + v.clear(); + v.reserve(ranges_.len()); + highlight_endpoints.reserve(2 * ranges_.len()); - for range in &ranges[start_ix..][..end_ix] { - let start = range.start.to_offset(buffer); - let end = range.end.to_offset(buffer); - if start == end { - continue; - } - highlight_endpoints.push(HighlightEndpoint { - offset: start, - tag, - style: Some(style), - }); - highlight_endpoints.push(HighlightEndpoint { - offset: end, - tag, - style: None, - }); - } + let mut iter = ranges_.iter(); + buffer.summaries_for_anchors_cb( + ranges_.iter().map(|r| &r.start), + |start: MultiBufferOffset| { + v.push((start, iter.next().unwrap().end)); + }, + ); + v.sort_by(|a, b| a.1.cmp(&b.1, buffer)); + let mut iter = v.iter(); + buffer.summaries_for_anchors_cb( + v.iter().map(|(_, end)| end), + |end: MultiBufferOffset| { + let start = iter.next().unwrap().0; + if start == end { + return; + } + highlight_endpoints.push(HighlightEndpoint { + offset: start, + tag, + style: Some(style), + }); + highlight_endpoints.push(HighlightEndpoint { + offset: end, + tag, + style: None, + }); + }, + ); } } if let Some(semantic_token_highlights) = semantic_token_highlights { @@ -133,27 +149,50 @@ fn create_highlight_endpoints( .then(cmp::Ordering::Less) }) .unwrap_or_else(|i| i); - for token in &semantic_token_highlights[start_ix..] { - if token.range.start.cmp(&end, buffer).is_ge() { - break; - } + let end_ix = semantic_token_highlights[start_ix..] + .binary_search_by(|probe| { + probe + .range + .start + .cmp(&end, buffer) + .then(cmp::Ordering::Greater) + }) + .unwrap_or_else(|i| i); - let start = token.range.start.to_offset(buffer); - let end = token.range.end.to_offset(buffer); - if start == end { - continue; - } - highlight_endpoints.push(HighlightEndpoint { - offset: start, - tag: HighlightKey::SemanticToken, - style: Some(interner[token.style]), - }); - highlight_endpoints.push(HighlightEndpoint { - offset: end, - tag: HighlightKey::SemanticToken, - style: None, - }); - } + let ranges_ = &semantic_token_highlights[start_ix..][..end_ix]; + let mut ranges_with_offsets = Vec::with_capacity(ranges_.len()); + highlight_endpoints.reserve(2 * ranges_.len()); + + let mut iter = ranges_.iter(); + buffer.summaries_for_anchors_cb( + ranges_.iter().map(|token| &token.range.start), + |start: MultiBufferOffset| { + ranges_with_offsets.push((start, iter.next().unwrap())); + }, + ); + ranges_with_offsets.sort_by(|a, b| a.1.range.end.cmp(&b.1.range.end, buffer)); + let mut iter = ranges_with_offsets.iter(); + buffer.summaries_for_anchors_cb( + ranges_with_offsets + .iter() + .map(|(_, token)| &token.range.end), + |end: MultiBufferOffset| { + let (start, token) = iter.next().unwrap(); + if *start == end { + return; + } + highlight_endpoints.push(HighlightEndpoint { + offset: *start, + tag: HighlightKey::SemanticToken, + style: Some(interner[token.style]), + }); + highlight_endpoints.push(HighlightEndpoint { + offset: end, + tag: HighlightKey::SemanticToken, + style: None, + }); + }, + ); } } highlight_endpoints.sort(); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 809f23bc394..1641c460eb2 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -5003,6 +5003,20 @@ impl MultiBufferSnapshot { } pub fn summaries_for_anchors<'a, MBD, I>(&'a self, anchors: I) -> Vec + where + MBD: MultiBufferDimension + + Ord + + Sub + + AddAssign, + MBD::TextDimension: Sub + Ord, + I: 'a + IntoIterator, + { + let mut summaries = Vec::new(); + self.summaries_for_anchors_cb(anchors, |summary| summaries.push(summary)); + summaries + } + + pub fn summaries_for_anchors_cb<'a, MBD, I>(&'a self, anchors: I, mut cb: impl FnMut(MBD)) where MBD: MultiBufferDimension + Ord @@ -5018,18 +5032,17 @@ impl MultiBufferSnapshot { .cursor::, OutputDimension>>(()); diff_transforms_cursor.next(); - let mut summaries = Vec::new(); while let Some(anchor) = anchors.peek() { let target = anchor.seek_target(self); let excerpt_anchor = match anchor { Anchor::Min => { - summaries.push(MBD::default()); + cb(MBD::default()); anchors.next(); continue; } Anchor::Excerpt(excerpt_anchor) => excerpt_anchor, Anchor::Max => { - summaries.push(MBD::from_summary(&self.text_summary())); + cb(MBD::from_summary(&self.text_summary())); anchors.next(); continue; } @@ -5047,7 +5060,7 @@ impl MultiBufferSnapshot { excerpt_start_position, &mut diff_transforms_cursor, ); - summaries.push(position); + cb(position); anchors.next(); continue; } @@ -5083,7 +5096,7 @@ impl MultiBufferSnapshot { diff_transforms_cursor.seek_forward(&position, Bias::Left); } - summaries.push(self.summary_for_anchor_with_excerpt_position( + cb(self.summary_for_anchor_with_excerpt_position( excerpt_anchor, position, &mut diff_transforms_cursor, @@ -5097,12 +5110,10 @@ impl MultiBufferSnapshot { excerpt_start_position, &mut diff_transforms_cursor, ); - summaries.push(position); + cb(position); anchors.next(); } } - - summaries } pub fn dimensions_from_points<'a, MBD>(