From 3a0d3cee874392321e9e9fb66135dc9dd9dc82c2 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 3 Apr 2024 12:21:17 +0200 Subject: [PATCH] Compute scrollbar markers asynchronously (#10080) Refs #9647 Fixes https://github.com/zed-industries/zed/issues/9792 This pull request moves the computation of scrollbar markers off the main thread, to prevent them from grinding the editor to a halt when we have a lot of them (e.g., when there are lots of search results on a large file). With these changes we also avoid generating multiple quads for adjacent markers, thus fixing an issue where we stop drawing other primitives because we've drawn too many quads in the scrollbar. Release Notes: - Improved editor performance when displaying lots of search results, diagnostics, or symbol highlights in the scrollbar ([#9792](https://github.com/zed-industries/zed/issues/9792)). --------- Co-authored-by: Antonio Scandurra Co-authored-by: Nathan --- Cargo.lock | 8 + Cargo.toml | 1 + crates/assistant/src/assistant_panel.rs | 6 +- crates/diagnostics/src/diagnostics.rs | 3 +- crates/editor/src/display_map.rs | 22 +- crates/editor/src/display_map/block_map.rs | 32 +- crates/editor/src/display_map/inlay_map.rs | 59 +-- crates/editor/src/editor.rs | 94 +++-- crates/editor/src/editor_tests.rs | 6 +- crates/editor/src/element.rs | 371 +++++++++++------- .../editor/src/highlight_matching_bracket.rs | 2 +- crates/editor/src/hover_popover.rs | 2 +- crates/editor/src/items.rs | 16 +- crates/editor/src/test/editor_test_context.rs | 2 +- crates/gpui/src/window.rs | 15 +- crates/language_tools/src/lsp_log.rs | 8 +- crates/language_tools/src/syntax_tree_view.rs | 2 +- crates/search/Cargo.toml | 1 + crates/search/src/buffer_search.rs | 10 +- crates/search/src/project_search.rs | 73 ++-- crates/sum_tree/src/tree_map.rs | 52 ++- crates/terminal/src/terminal.rs | 2 +- crates/terminal_view/src/terminal_view.rs | 11 +- crates/vim/src/utils.rs | 2 +- crates/workspace/Cargo.toml | 1 + crates/workspace/src/searchable.rs | 110 +++--- 26 files changed, 532 insertions(+), 379 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba3d1e8140..a2e5ed0d7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,12 @@ dependencies = [ "util", ] +[[package]] +name = "any_vec" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78f17bacc1bc7b91fef7b1885c10772eb2b9e4e989356f6f0f6a972240f97cd" + [[package]] name = "anyhow" version = "1.0.75" @@ -8296,6 +8302,7 @@ dependencies = [ name = "search" version = "0.1.0" dependencies = [ + "any_vec", "anyhow", "bitflags 2.4.2", "client", @@ -12112,6 +12119,7 @@ dependencies = [ name = "workspace" version = "0.1.0" dependencies = [ + "any_vec", "anyhow", "async-recursion 1.0.5", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 72079ff004..765a99ac3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,6 +220,7 @@ zed = { path = "crates/zed" } zed_actions = { path = "crates/zed_actions" } anyhow = "1.0.57" +any_vec = "0.13" async-compression = { version = "0.4", features = ["gzip", "futures-io"] } async-fs = "1.6" async-recursion = "1.0.0" diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 61604d7ef6..435388cf81 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -345,7 +345,7 @@ impl AssistantPanel { style: BlockStyle::Flex, position: snapshot.anchor_before(point_selection.head()), height: 2, - render: Arc::new({ + render: Box::new({ let inline_assistant = inline_assistant.clone(); move |cx: &mut BlockContext| { *measurements.lock() = BlockMeasurements { @@ -695,7 +695,7 @@ impl AssistantPanel { editor.clear_background_highlights::(cx); } else { editor.highlight_background::( - background_ranges, + &background_ranges, |theme| theme.editor_active_line_background, // todo!("use the appropriate color") cx, ); @@ -2266,7 +2266,7 @@ impl ConversationEditor { .unwrap(), height: 2, style: BlockStyle::Sticky, - render: Arc::new({ + render: Box::new({ let conversation = self.conversation.clone(); move |_cx| { let message_id = message.id; diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index be5e4a7f8a..7dec4be5cd 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -32,7 +32,6 @@ use std::{ mem, ops::Range, path::PathBuf, - sync::Arc, }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; @@ -805,7 +804,7 @@ impl Item for ProjectDiagnosticsEditor { fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let (message, code_ranges) = highlight_diagnostic_message(&diagnostic); let message: SharedString = message; - Arc::new(move |cx| { + Box::new(move |cx| { let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into(); h_flex() .id("diagnostic header") diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 03ff628b8f..f274991853 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -26,7 +26,7 @@ mod wrap_map; use crate::EditorStyle; use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId}; pub use block_map::{BlockMap, BlockPoint}; -use collections::{BTreeMap, HashMap, HashSet}; +use collections::{HashMap, HashSet}; use fold_map::FoldMap; use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle}; use inlay_map::InlayMap; @@ -63,7 +63,7 @@ pub trait ToDisplayPoint { } type TextHighlights = TreeMap, Arc<(HighlightStyle, Vec>)>>; -type InlayHighlights = BTreeMap>; +type InlayHighlights = TreeMap>; /// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints, /// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting. @@ -257,10 +257,15 @@ impl DisplayMap { style: HighlightStyle, ) { for highlight in highlights { - self.inlay_highlights - .entry(type_id) - .or_default() - .insert(highlight.inlay, (style, highlight)); + let update = self.inlay_highlights.update(&type_id, |highlights| { + highlights.insert(highlight.inlay, (style, highlight.clone())) + }); + if update.is_none() { + self.inlay_highlights.insert( + type_id, + TreeMap::from_ordered_entries([(highlight.inlay, (style, highlight))]), + ); + } } } @@ -354,6 +359,7 @@ pub struct HighlightedChunk<'a> { pub is_tab: bool, } +#[derive(Clone)] pub struct DisplaySnapshot { pub buffer_snapshot: MultiBufferSnapshot, pub fold_snapshot: fold_map::FoldSnapshot, @@ -872,7 +878,7 @@ impl DisplaySnapshot { #[cfg(any(test, feature = "test-support"))] pub(crate) fn inlay_highlights( &self, - ) -> Option<&HashMap> { + ) -> Option<&TreeMap> { let type_id = TypeId::of::(); self.inlay_highlights.get(&type_id) } @@ -1093,7 +1099,7 @@ pub mod tests { position, height, disposition, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), } }) .collect::>(); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 2d7280114f..6de21b0d08 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -37,6 +37,7 @@ pub struct BlockMap { pub struct BlockMapWriter<'a>(&'a mut BlockMap); +#[derive(Clone)] pub struct BlockSnapshot { wrap_snapshot: WrapSnapshot, transforms: SumTree, @@ -54,7 +55,7 @@ struct BlockRow(u32); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] struct WrapRow(u32); -pub type RenderBlock = Arc AnyElement>; +pub type RenderBlock = Box AnyElement>; pub struct Block { id: BlockId, @@ -65,15 +66,11 @@ pub struct Block { disposition: BlockDisposition, } -#[derive(Clone)] -pub struct BlockProperties

-where - P: Clone, -{ +pub struct BlockProperties

{ pub position: P, pub height: u8, pub style: BlockStyle, - pub render: Arc AnyElement>, + pub render: Box AnyElement>, pub disposition: BlockDisposition, } @@ -1041,21 +1038,21 @@ mod tests { position: buffer_snapshot.anchor_after(Point::new(1, 0)), height: 1, disposition: BlockDisposition::Above, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), }, BlockProperties { style: BlockStyle::Fixed, position: buffer_snapshot.anchor_after(Point::new(1, 2)), height: 2, disposition: BlockDisposition::Above, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), }, BlockProperties { style: BlockStyle::Fixed, position: buffer_snapshot.anchor_after(Point::new(3, 3)), height: 3, disposition: BlockDisposition::Below, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), }, ]); @@ -1209,14 +1206,14 @@ mod tests { style: BlockStyle::Fixed, position: buffer_snapshot.anchor_after(Point::new(1, 12)), disposition: BlockDisposition::Above, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), height: 1, }, BlockProperties { style: BlockStyle::Fixed, position: buffer_snapshot.anchor_after(Point::new(1, 1)), disposition: BlockDisposition::Below, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), height: 1, }, ]); @@ -1311,7 +1308,7 @@ mod tests { position, height, disposition, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), } }) .collect::>(); @@ -1325,7 +1322,14 @@ mod tests { wrap_map.sync(tab_snapshot, tab_edits, cx) }); let mut block_map = block_map.write(wraps_snapshot, wrap_edits); - let block_ids = block_map.insert(block_properties.clone()); + let block_ids = + block_map.insert(block_properties.iter().map(|props| BlockProperties { + position: props.position, + height: props.height, + style: props.style, + render: Box::new(|_| div().into_any()), + disposition: props.disposition, + })); for (block_id, props) in block_ids.into_iter().zip(block_properties) { custom_blocks.push((block_id, props)); } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index a64200709e..387885b406 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1695,38 +1695,39 @@ mod tests { while inlay_indices.len() < inlay_highlight_count { inlay_indices.insert(rng.gen_range(0..inlays.len())); } - let new_highlights = inlay_indices - .into_iter() - .filter_map(|i| { - let (_, inlay) = &inlays[i]; - let inlay_text_len = inlay.text.len(); - match inlay_text_len { - 0 => None, - 1 => Some(InlayHighlight { - inlay: inlay.id, - inlay_position: inlay.position, - range: 0..1, - }), - n => { - let inlay_text = inlay.text.to_string(); - let mut highlight_end = rng.gen_range(1..n); - let mut highlight_start = rng.gen_range(0..highlight_end); - while !inlay_text.is_char_boundary(highlight_end) { - highlight_end += 1; - } - while !inlay_text.is_char_boundary(highlight_start) { - highlight_start -= 1; - } - Some(InlayHighlight { + let new_highlights = TreeMap::from_ordered_entries( + inlay_indices + .into_iter() + .filter_map(|i| { + let (_, inlay) = &inlays[i]; + let inlay_text_len = inlay.text.len(); + match inlay_text_len { + 0 => None, + 1 => Some(InlayHighlight { inlay: inlay.id, inlay_position: inlay.position, - range: highlight_start..highlight_end, - }) + range: 0..1, + }), + n => { + let inlay_text = inlay.text.to_string(); + let mut highlight_end = rng.gen_range(1..n); + let mut highlight_start = rng.gen_range(0..highlight_end); + while !inlay_text.is_char_boundary(highlight_end) { + highlight_end += 1; + } + while !inlay_text.is_char_boundary(highlight_start) { + highlight_start -= 1; + } + Some(InlayHighlight { + inlay: inlay.id, + inlay_position: inlay.position, + range: highlight_start..highlight_end, + }) + } } - } - }) - .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))) - .collect(); + }) + .map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))), + ); log::info!("highlighting inlay ranges {new_highlights:?}"); inlay_highlights.insert(TypeId::of::<()>(), new_highlights); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6363202c91..f331bf5769 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -64,9 +64,9 @@ use gpui::{ AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, - MouseButton, ParentElement, Pixels, Render, SharedString, StrikethroughStyle, Styled, - StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, - ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext, + MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle, + Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, + View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -116,6 +116,7 @@ use std::{ time::{Duration, Instant}, }; pub use sum_tree::Bias; +use sum_tree::TreeMap; use text::{BufferId, OffsetUtf16, Rope}; use theme::{ observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, @@ -355,7 +356,31 @@ type CompletionId = usize; // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; // type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option; -type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec>); +type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range]>); + +struct ScrollbarMarkerState { + scrollbar_size: Size, + dirty: bool, + markers: Arc<[PaintQuad]>, + pending_refresh: Option>>, +} + +impl ScrollbarMarkerState { + fn should_refresh(&self, scrollbar_size: Size) -> bool { + self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty) + } +} + +impl Default for ScrollbarMarkerState { + fn default() -> Self { + Self { + scrollbar_size: Size::default(), + dirty: false, + markers: Arc::from([]), + pending_refresh: None, + } + } +} /// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`] /// @@ -394,7 +419,8 @@ pub struct Editor { placeholder_text: Option>, highlight_order: usize, highlighted_rows: HashMap, Hsla)>>, - background_highlights: BTreeMap, + background_highlights: TreeMap, + scrollbar_marker_state: ScrollbarMarkerState, nav_history: Option, context_menu: RwLock>, mouse_context_menu: Option, @@ -444,6 +470,7 @@ pub struct Editor { >, } +#[derive(Clone)] pub struct EditorSnapshot { pub mode: EditorMode, show_gutter: bool, @@ -1440,6 +1467,7 @@ impl Editor { highlight_order: 0, highlighted_rows: HashMap::default(), background_highlights: Default::default(), + scrollbar_marker_state: ScrollbarMarkerState::default(), nav_history: None, context_menu: RwLock::new(None), mouse_context_menu: None, @@ -3730,7 +3758,7 @@ impl Editor { workspace.add_item_to_active_pane(Box::new(editor.clone()), cx); editor.update(cx, |editor, cx| { editor.highlight_background::( - ranges_to_highlight, + &ranges_to_highlight, |theme| theme.editor_highlighted_line_background, cx, ); @@ -3860,12 +3888,12 @@ impl Editor { } this.highlight_background::( - read_ranges, + &read_ranges, |theme| theme.editor_document_highlight_read_background, cx, ); this.highlight_background::( - write_ranges, + &write_ranges, |theme| theme.editor_document_highlight_write_background, cx, ); @@ -7967,7 +7995,7 @@ impl Editor { }); editor.update(cx, |editor, cx| { editor.highlight_background::( - ranges_to_highlight, + &ranges_to_highlight, |theme| theme.editor_highlighted_line_background, cx, ); @@ -8058,15 +8086,15 @@ impl Editor { editor }); - let ranges = this - .clear_background_highlights::(cx) - .into_iter() - .flat_map(|(_, ranges)| ranges.into_iter()) - .chain( - this.clear_background_highlights::(cx) - .into_iter() - .flat_map(|(_, ranges)| ranges.into_iter()), - ) + let write_highlights = + this.clear_background_highlights::(cx); + let read_highlights = + this.clear_background_highlights::(cx); + let ranges = write_highlights + .iter() + .flat_map(|(_, ranges)| ranges.iter()) + .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter())) + .cloned() .collect(); this.highlight_text::( @@ -8084,7 +8112,7 @@ impl Editor { style: BlockStyle::Flex, position: range.start, height: 1, - render: Arc::new({ + render: Box::new({ let rename_editor = rename_editor.clone(); move |cx: &mut BlockContext| { let mut text_style = cx.editor_style.text.clone(); @@ -9016,13 +9044,13 @@ impl Editor { pub fn highlight_background( &mut self, - ranges: Vec>, + ranges: &[Range], color_fetcher: fn(&ThemeColors) -> Hsla, cx: &mut ViewContext, ) { let snapshot = self.snapshot(cx); // this is to try and catch a panic sooner - for range in &ranges { + for range in ranges { snapshot .buffer_snapshot .summary_for_anchor::(&range.start); @@ -9032,16 +9060,21 @@ impl Editor { } self.background_highlights - .insert(TypeId::of::(), (color_fetcher, ranges)); + .insert(TypeId::of::(), (color_fetcher, Arc::from(ranges))); + self.scrollbar_marker_state.dirty = true; cx.notify(); } pub fn clear_background_highlights( &mut self, - _cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option { - let text_highlights = self.background_highlights.remove(&TypeId::of::()); - text_highlights + let text_highlights = self.background_highlights.remove(&TypeId::of::())?; + if !text_highlights.1.is_empty() { + self.scrollbar_marker_state.dirty = true; + cx.notify(); + } + Some(text_highlights) } #[cfg(feature = "test-support")] @@ -9295,6 +9328,7 @@ impl Editor { multi_buffer::Event::Edited { singleton_buffer_edited, } => { + self.scrollbar_marker_state.dirty = true; self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); if self.has_active_inline_completion(cx) { @@ -9362,10 +9396,16 @@ impl Editor { multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => { cx.emit(EditorEvent::TitleChanged) } - multi_buffer::Event::DiffBaseChanged => cx.emit(EditorEvent::DiffBaseChanged), + multi_buffer::Event::DiffBaseChanged => { + self.scrollbar_marker_state.dirty = true; + cx.emit(EditorEvent::DiffBaseChanged); + cx.notify(); + } multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed), multi_buffer::Event::DiagnosticsUpdated => { self.refresh_active_diagnostics(cx); + self.scrollbar_marker_state.dirty = true; + cx.notify(); } _ => {} }; @@ -10526,7 +10566,7 @@ impl InvalidationRegion for SnippetState { pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> RenderBlock { let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic); - Arc::new(move |cx: &mut BlockContext| { + Box::new(move |cx: &mut BlockContext| { let group_id: SharedString = cx.block_id.to_string().into(); let mut text_style = cx.text_style().clone(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index b6abacb2a9..d9a2ff4d03 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3317,7 +3317,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { position: snapshot.anchor_after(Point::new(2, 0)), disposition: BlockDisposition::Below, height: 1, - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), }], Some(Autoscroll::fit()), cx, @@ -7263,7 +7263,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { |range: Range| buffer.anchor_after(range.start)..buffer.anchor_after(range.end); editor.highlight_background::( - vec![ + &[ anchor_range(Point::new(2, 1)..Point::new(2, 3)), anchor_range(Point::new(4, 2)..Point::new(4, 4)), anchor_range(Point::new(6, 3)..Point::new(6, 5)), @@ -7273,7 +7273,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { cx, ); editor.highlight_background::( - vec![ + &[ anchor_range(Point::new(3, 2)..Point::new(3, 5)), anchor_range(Point::new(5, 3)..Point::new(5, 6)), anchor_range(Point::new(7, 4)..Point::new(7, 7)), diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 9a1dacf44d..26ba2f0647 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -24,7 +24,7 @@ use gpui::{ transparent_black, Action, AnchorCorner, AnyElement, AnyView, AvailableSpace, Bounds, ClipboardItem, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementContext, ElementInputHandler, Entity, Hitbox, Hsla, InteractiveElement, IntoElement, - ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, ViewContext, WindowContext, @@ -2370,150 +2370,15 @@ impl EditorElement { }, cx.theme().colors().scrollbar_track_border, )); - let scrollbar_settings = EditorSettings::get_global(cx).scrollbar; - let is_singleton = self.editor.read(cx).is_singleton(cx); - let left = scrollbar_layout.hitbox.left(); - let right = scrollbar_layout.hitbox.right(); - let column_width = - px(((right - left - ScrollbarLayout::BORDER_WIDTH).0 / 3.0).floor()); - if is_singleton && scrollbar_settings.selections { - let start_anchor = Anchor::min(); - let end_anchor = Anchor::max(); - let background_ranges = self - .editor - .read(cx) - .background_highlight_row_ranges::( - start_anchor..end_anchor, - &layout.position_map.snapshot, - 50000, - ); - let left_x = left + ScrollbarLayout::BORDER_WIDTH + column_width; - let right_x = left_x + column_width; - for range in background_ranges { - let (start_y, end_y) = - scrollbar_layout.ys_for_marker(range.start().row(), range.end().row()); - let bounds = - Bounds::from_corners(point(left_x, start_y), point(right_x, end_y)); - cx.paint_quad(quad( - bounds, - Corners::default(), - cx.theme().status().info, - Edges::default(), - cx.theme().colors().scrollbar_thumb_border, - )); - } - } - if is_singleton && scrollbar_settings.symbols_selections { - let selection_ranges = self.editor.read(cx).background_highlights_in_range( - Anchor::min()..Anchor::max(), - &layout.position_map.snapshot, - cx.theme().colors(), - ); - let left_x = left + ScrollbarLayout::BORDER_WIDTH + column_width; - let right_x = left_x + column_width; - for hunk in selection_ranges { - let start_display = Point::new(hunk.0.start.row(), 0) - .to_display_point(&layout.position_map.snapshot.display_snapshot); - let end_display = Point::new(hunk.0.end.row(), 0) - .to_display_point(&layout.position_map.snapshot.display_snapshot); - let (start_y, end_y) = - scrollbar_layout.ys_for_marker(start_display.row(), end_display.row()); - let bounds = - Bounds::from_corners(point(left_x, start_y), point(right_x, end_y)); - cx.paint_quad(quad( - bounds, - Corners::default(), - cx.theme().status().info, - Edges::default(), - cx.theme().colors().scrollbar_thumb_border, - )); - } - } + // Refresh scrollbar markers in the background. Below, we paint whatever markers have already been computed. + self.refresh_scrollbar_markers(layout, scrollbar_layout, cx); - if is_singleton && scrollbar_settings.git_diff { - let left_x = left + ScrollbarLayout::BORDER_WIDTH; - let right_x = left_x + column_width; - for hunk in layout - .position_map - .snapshot - .buffer_snapshot - .git_diff_hunks_in_range(0..layout.max_row) - { - let start_display_row = Point::new(hunk.associated_range.start, 0) - .to_display_point(&layout.position_map.snapshot.display_snapshot) - .row(); - let mut end_display_row = Point::new(hunk.associated_range.end, 0) - .to_display_point(&layout.position_map.snapshot.display_snapshot) - .row(); - if end_display_row != start_display_row { - end_display_row -= 1; - } - let (start_y, end_y) = - scrollbar_layout.ys_for_marker(start_display_row, end_display_row); - let bounds = - Bounds::from_corners(point(left_x, start_y), point(right_x, end_y)); - let color = match hunk.status() { - DiffHunkStatus::Added => cx.theme().status().created, - DiffHunkStatus::Modified => cx.theme().status().modified, - DiffHunkStatus::Removed => cx.theme().status().deleted, - }; - cx.paint_quad(quad( - bounds, - Corners::default(), - color, - Edges::default(), - cx.theme().colors().scrollbar_thumb_border, - )); - } - } - - if is_singleton && scrollbar_settings.diagnostics { - let max_point = layout - .position_map - .snapshot - .display_snapshot - .buffer_snapshot - .max_point(); - - let diagnostics = layout - .position_map - .snapshot - .buffer_snapshot - .diagnostics_in_range::<_, Point>(Point::zero()..max_point, false) - // We want to sort by severity, in order to paint the most severe diagnostics last. - .sorted_by_key(|diagnostic| { - std::cmp::Reverse(diagnostic.diagnostic.severity) - }); - - let left_x = left + ScrollbarLayout::BORDER_WIDTH + 2.0 * column_width; - for diagnostic in diagnostics { - let start_display = diagnostic - .range - .start - .to_display_point(&layout.position_map.snapshot.display_snapshot); - let end_display = diagnostic - .range - .end - .to_display_point(&layout.position_map.snapshot.display_snapshot); - let (start_y, end_y) = - scrollbar_layout.ys_for_marker(start_display.row(), end_display.row()); - let bounds = - Bounds::from_corners(point(left_x, start_y), point(right, end_y)); - let color = match diagnostic.diagnostic.severity { - DiagnosticSeverity::ERROR => cx.theme().status().error, - DiagnosticSeverity::WARNING => cx.theme().status().warning, - DiagnosticSeverity::INFORMATION => cx.theme().status().info, - _ => cx.theme().status().hint, - }; - cx.paint_quad(quad( - bounds, - Corners::default(), - color, - Edges::default(), - cx.theme().colors().scrollbar_thumb_border, - )); - } + let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone(); + for marker in markers.iter() { + let mut marker = marker.clone(); + marker.bounds.origin += scrollbar_layout.hitbox.origin; + cx.paint_quad(marker); } cx.paint_quad(quad( @@ -2619,6 +2484,156 @@ impl EditorElement { } } + fn refresh_scrollbar_markers( + &self, + layout: &EditorLayout, + scrollbar_layout: &ScrollbarLayout, + cx: &mut ElementContext, + ) { + self.editor.update(cx, |editor, cx| { + if !editor.is_singleton(cx) + || !editor + .scrollbar_marker_state + .should_refresh(scrollbar_layout.hitbox.size) + { + return; + } + + let scrollbar_layout = scrollbar_layout.clone(); + let background_highlights = editor.background_highlights.clone(); + let snapshot = layout.position_map.snapshot.clone(); + let theme = cx.theme().clone(); + let scrollbar_settings = EditorSettings::get_global(cx).scrollbar; + let max_row = layout.max_row; + + editor.scrollbar_marker_state.dirty = false; + editor.scrollbar_marker_state.pending_refresh = + Some(cx.spawn(|editor, mut cx| async move { + let scrollbar_size = scrollbar_layout.hitbox.size; + let scrollbar_markers = cx + .background_executor() + .spawn(async move { + let mut marker_quads = Vec::new(); + + if scrollbar_settings.git_diff { + let marker_row_ranges = snapshot + .buffer_snapshot + .git_diff_hunks_in_range(0..max_row) + .map(|hunk| { + let start_display_row = + Point::new(hunk.associated_range.start, 0) + .to_display_point(&snapshot.display_snapshot) + .row(); + let mut end_display_row = + Point::new(hunk.associated_range.end, 0) + .to_display_point(&snapshot.display_snapshot) + .row(); + if end_display_row != start_display_row { + end_display_row -= 1; + } + let color = match hunk.status() { + DiffHunkStatus::Added => theme.status().created, + DiffHunkStatus::Modified => theme.status().modified, + DiffHunkStatus::Removed => theme.status().deleted, + }; + ColoredRange { + start: start_display_row, + end: end_display_row, + color, + } + }); + + marker_quads.extend( + scrollbar_layout.marker_quads_for_ranges(marker_row_ranges, 0), + ); + } + + for (background_highlight_id, (_, background_ranges)) in + background_highlights.iter() + { + if (*background_highlight_id + == TypeId::of::() + && scrollbar_settings.selections) + || scrollbar_settings.symbols_selections + { + let marker_row_ranges = + background_ranges.into_iter().map(|range| { + let display_start = range + .start + .to_display_point(&snapshot.display_snapshot); + let display_end = range + .end + .to_display_point(&snapshot.display_snapshot); + ColoredRange { + start: display_start.row(), + end: display_end.row(), + color: theme.status().info, + } + }); + marker_quads.extend( + scrollbar_layout + .marker_quads_for_ranges(marker_row_ranges, 1), + ); + } + } + + if scrollbar_settings.diagnostics { + let max_point = + snapshot.display_snapshot.buffer_snapshot.max_point(); + + let diagnostics = snapshot + .buffer_snapshot + .diagnostics_in_range::<_, Point>( + Point::zero()..max_point, + false, + ) + // We want to sort by severity, in order to paint the most severe diagnostics last. + .sorted_by_key(|diagnostic| { + std::cmp::Reverse(diagnostic.diagnostic.severity) + }); + + let marker_row_ranges = diagnostics.into_iter().map(|diagnostic| { + let start_display = diagnostic + .range + .start + .to_display_point(&snapshot.display_snapshot); + let end_display = diagnostic + .range + .end + .to_display_point(&snapshot.display_snapshot); + let color = match diagnostic.diagnostic.severity { + DiagnosticSeverity::ERROR => theme.status().error, + DiagnosticSeverity::WARNING => theme.status().warning, + DiagnosticSeverity::INFORMATION => theme.status().info, + _ => theme.status().hint, + }; + ColoredRange { + start: start_display.row(), + end: end_display.row(), + color, + } + }); + marker_quads.extend( + scrollbar_layout.marker_quads_for_ranges(marker_row_ranges, 2), + ); + } + + Arc::from(marker_quads) + }) + .await; + + editor.update(&mut cx, |editor, cx| { + editor.scrollbar_marker_state.markers = scrollbar_markers; + editor.scrollbar_marker_state.scrollbar_size = scrollbar_size; + editor.scrollbar_marker_state.pending_refresh = None; + cx.notify(); + })?; + + Ok(()) + })); + }); + } + #[allow(clippy::too_many_arguments)] fn paint_highlighted_range( &self, @@ -3811,6 +3826,13 @@ impl EditorLayout { } } +struct ColoredRange { + start: T, + end: T, + color: Hsla, +} + +#[derive(Clone)] struct ScrollbarLayout { hitbox: Hitbox, visible_row_range: Range, @@ -3838,13 +3860,60 @@ impl ScrollbarLayout { self.hitbox.top() + self.first_row_y_offset + row * self.row_height } - fn ys_for_marker(&self, start_row: u32, end_row: u32) -> (Pixels, Pixels) { - let start_y = self.y_for_row(start_row as f32); - let mut end_y = self.y_for_row((end_row + 1) as f32); - if end_y - start_y < Self::MIN_MARKER_HEIGHT { - end_y = start_y + Self::MIN_MARKER_HEIGHT; + fn marker_quads_for_ranges( + &self, + row_ranges: impl IntoIterator>, + column: usize, + ) -> Vec { + let column_width = + px(((self.hitbox.size.width - ScrollbarLayout::BORDER_WIDTH).0 / 3.0).floor()); + + let left_x = ScrollbarLayout::BORDER_WIDTH + (column as f32 * column_width); + let right_x = left_x + column_width; + + let mut background_pixel_ranges = row_ranges + .into_iter() + .map(|range| { + let start_y = self.first_row_y_offset + range.start as f32 * self.row_height; + let mut end_y = self.first_row_y_offset + (range.end + 1) as f32 * self.row_height; + if end_y - start_y < Self::MIN_MARKER_HEIGHT { + end_y = start_y + Self::MIN_MARKER_HEIGHT; + } + ColoredRange { + start: start_y, + end: end_y, + color: range.color, + } + }) + .peekable(); + + let mut quads = Vec::new(); + while let Some(mut pixel_range) = background_pixel_ranges.next() { + while let Some(next_pixel_range) = background_pixel_ranges.peek() { + if pixel_range.end >= next_pixel_range.start + && pixel_range.color == next_pixel_range.color + { + pixel_range.end = next_pixel_range.end; + background_pixel_ranges.next(); + } else { + break; + } + } + + let bounds = Bounds::from_corners( + point(left_x, pixel_range.start), + point(right_x, pixel_range.end), + ); + quads.push(quad( + bounds, + Corners::default(), + pixel_range.color, + Edges::default(), + Hsla::transparent_black(), + )); } - (start_y, end_y) + + quads } } @@ -4241,7 +4310,7 @@ mod tests { use gpui::TestAppContext; use language::language_settings; use log::info; - use std::{num::NonZeroU32, sync::Arc}; + use std::num::NonZeroU32; use util::test::sample_text; #[gpui::test] @@ -4473,7 +4542,7 @@ mod tests { disposition: BlockDisposition::Above, height: 3, position: Anchor::min(), - render: Arc::new(|_| div().into_any()), + render: Box::new(|_| div().into_any()), }], None, cx, diff --git a/crates/editor/src/highlight_matching_bracket.rs b/crates/editor/src/highlight_matching_bracket.rs index 1d0b304913..ca905fee02 100644 --- a/crates/editor/src/highlight_matching_bracket.rs +++ b/crates/editor/src/highlight_matching_bracket.rs @@ -20,7 +20,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon .innermost_enclosing_bracket_ranges(head..head, None) { editor.highlight_background::( - vec![ + &[ opening_range.to_anchors(&snapshot.buffer_snapshot), closing_range.to_anchors(&snapshot.buffer_snapshot), ], diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index fb9011e02b..6c27b0b3b0 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -342,7 +342,7 @@ fn show_hover( } else { // Highlight the selected symbol using a background highlight editor.highlight_background::( - hover_highlights, + &hover_highlights, |theme| theme.element_hover, // todo update theme cx, ); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 3f681445ef..ec55f512f0 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -976,7 +976,7 @@ impl SearchableItem for Editor { self.clear_background_highlights::(cx); } - fn update_matches(&mut self, matches: Vec>, cx: &mut ViewContext) { + fn update_matches(&mut self, matches: &[Range], cx: &mut ViewContext) { self.highlight_background::( matches, |theme| theme.search_match_background, @@ -1013,7 +1013,7 @@ impl SearchableItem for Editor { fn activate_match( &mut self, index: usize, - matches: Vec>, + matches: &[Range], cx: &mut ViewContext, ) { self.unfold_ranges([matches[index].clone()], false, true, cx); @@ -1023,10 +1023,10 @@ impl SearchableItem for Editor { }) } - fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { - self.unfold_ranges(matches.clone(), false, false, cx); + fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext) { + self.unfold_ranges(matches.to_vec(), false, false, cx); let mut ranges = Vec::new(); - for m in &matches { + for m in matches { ranges.push(self.range_for_match(&m)) } self.change_selections(None, cx, |s| s.select_ranges(ranges)); @@ -1055,7 +1055,7 @@ impl SearchableItem for Editor { } fn match_index_for_direction( &mut self, - matches: &Vec>, + matches: &[Range], current_index: usize, direction: Direction, count: usize, @@ -1147,11 +1147,11 @@ impl SearchableItem for Editor { fn active_match_index( &mut self, - matches: Vec>, + matches: &[Range], cx: &mut ViewContext, ) -> Option { active_match_index( - &matches, + matches, &self.selections.newest_anchor().head(), &self.buffer().read(cx).snapshot(cx), ) diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 0a0f621969..9611a8581c 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -345,7 +345,7 @@ impl EditorTestContext { .background_highlights .get(&TypeId::of::()) .map(|h| h.1.clone()) - .unwrap_or_default() + .unwrap_or_else(|| Arc::from([])) .into_iter() .map(|range| range.to_offset(&snapshot.buffer_snapshot)) .collect() diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 84cebeafca..a9a473b84f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2853,11 +2853,16 @@ impl From<(&'static str, u64)> for ElementId { /// Passed as an argument [`ElementContext::paint_quad`]. #[derive(Clone)] pub struct PaintQuad { - bounds: Bounds, - corner_radii: Corners, - background: Hsla, - border_widths: Edges, - border_color: Hsla, + /// The bounds of the quad within the window. + pub bounds: Bounds, + /// The radii of the quad's corners. + pub corner_radii: Corners, + /// The background color of the quad. + pub background: Hsla, + /// The widths of the quad's borders. + pub border_widths: Edges, + /// The color of the quad's borders. + pub border_color: Hsla, } impl PaintQuad { diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index c03f4dc90a..0a106a3f80 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -654,7 +654,7 @@ impl SearchableItem for LspLogView { self.editor.update(cx, |e, cx| e.clear_matches(cx)) } - fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext) { self.editor .update(cx, |e, cx| e.update_matches(matches, cx)) } @@ -666,14 +666,14 @@ impl SearchableItem for LspLogView { fn activate_match( &mut self, index: usize, - matches: Vec, + matches: &[Self::Match], cx: &mut ViewContext, ) { self.editor .update(cx, |e, cx| e.activate_match(index, matches, cx)) } - fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext) { self.editor .update(cx, |e, cx| e.select_matches(matches, cx)) } @@ -700,7 +700,7 @@ impl SearchableItem for LspLogView { } fn active_match_index( &mut self, - matches: Vec, + matches: &[Self::Match], cx: &mut ViewContext, ) -> Option { self.editor diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index c4d478b16e..1dd647fc82 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -337,7 +337,7 @@ impl Render for SyntaxTreeView { tree_view.update_editor_with_range_for_descendant_ix(descendant_ix, cx, |editor, range, cx| { editor.clear_background_highlights::(cx); editor.highlight_background::( - vec![range], + &[range], |theme| theme.editor_document_highlight_write_background, cx, ); diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 943bfea56b..0654027b47 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -14,6 +14,7 @@ doctest = false [dependencies] anyhow.workspace = true +any_vec.workspace = true bitflags.workspace = true collections.workspace = true editor.workspace = true diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index fc10cfd788..f0206aff9f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -7,6 +7,7 @@ use crate::{ ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord, }; +use any_vec::AnyVec; use collections::HashMap; use editor::{ actions::{Tab, TabPrev}, @@ -25,7 +26,7 @@ use project::{ }; use serde::Deserialize; use settings::Settings; -use std::{any::Any, sync::Arc}; +use std::sync::Arc; use theme::ThemeSettings; use ui::{h_flex, prelude::*, IconButton, IconName, ToggleButton, Tooltip}; @@ -70,8 +71,7 @@ pub struct BufferSearchBar { active_match_index: Option, active_searchable_item_subscription: Option, active_search: Option>, - searchable_items_with_matches: - HashMap, Vec>>, + searchable_items_with_matches: HashMap, AnyVec>, pending_search: Option>, search_options: SearchOptions, default_options: SearchOptions, @@ -191,7 +191,7 @@ impl Render for BufferSearchBar { let matches_count = self .searchable_items_with_matches .get(&searchable_item.downgrade()) - .map(Vec::len) + .map(AnyVec::len) .unwrap_or(0); if let Some(match_ix) = self.active_match_index { Some(format!("{}/{}", match_ix + 1, matches_count)) @@ -1067,7 +1067,7 @@ impl BufferSearchBar { .as_ref() .clone() .with_replacement(self.replacement(cx)); - searchable_item.replace(&matches[active_index], &query, cx); + searchable_item.replace(matches.at(active_index), &query, cx); self.select_next_match(&SelectNextMatch, cx); } should_propagate = false; diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 1a93669cee..6a06d7b8ab 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -585,43 +585,54 @@ impl ProjectSearchView { cx.notify(); } fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext) { - let model = self.model.read(cx); - if let Some(query) = model.active_query.as_ref() { - if model.match_ranges.is_empty() { - return; - } - if let Some(active_index) = self.active_match_index { - let query = query.clone().with_replacement(self.replacement(cx)); - self.results_editor.replace( - &(Box::new(model.match_ranges[active_index].clone()) as _), - &query, - cx, - ); - self.select_match(Direction::Next, cx) - } + if self.model.read(cx).match_ranges.is_empty() { + return; + } + let Some(active_index) = self.active_match_index else { + return; + }; + + let query = self.model.read(cx).active_query.clone(); + if let Some(query) = query { + let query = query.with_replacement(self.replacement(cx)); + + // TODO: Do we need the clone here? + let mat = self.model.read(cx).match_ranges[active_index].clone(); + self.results_editor.update(cx, |editor, cx| { + editor.replace(&mat, &query, cx); + }); + self.select_match(Direction::Next, cx) } } pub fn replacement(&self, cx: &AppContext) -> String { self.replacement_editor.read(cx).text(cx) } fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext) { - let model = self.model.read(cx); - if let Some(query) = model.active_query.as_ref() { - if model.match_ranges.is_empty() { - return; - } - if self.active_match_index.is_some() { - let query = query.clone().with_replacement(self.replacement(cx)); - let matches = model - .match_ranges - .iter() - .map(|item| Box::new(item.clone()) as _) - .collect::>(); - for item in matches { - self.results_editor.replace(&item, &query, cx); - } - } + if self.active_match_index.is_none() { + return; } + + let Some(query) = self.model.read(cx).active_query.as_ref() else { + return; + }; + let query = query.clone().with_replacement(self.replacement(cx)); + + let match_ranges = self + .model + .update(cx, |model, _| mem::take(&mut model.match_ranges)); + if match_ranges.is_empty() { + return; + } + + self.results_editor.update(cx, |editor, cx| { + for item in &match_ranges { + editor.replace(item, &query, cx); + } + }); + + self.model.update(cx, |model, _cx| { + model.match_ranges = match_ranges; + }); } fn new( @@ -1060,7 +1071,7 @@ impl ProjectSearchView { editor.scroll(Point::default(), Some(Axis::Vertical), cx); } editor.highlight_background::( - match_ranges, + &match_ranges, |theme| theme.search_match_background, cx, ); diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index b46150e3c3..a59f796fe0 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -5,7 +5,7 @@ use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary #[derive(Clone, PartialEq, Eq)] pub struct TreeMap(SumTree>) where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, V: Clone + Debug; #[derive(Clone, Debug, PartialEq, Eq)] @@ -14,18 +14,30 @@ pub struct MapEntry { value: V, } -#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] -pub struct MapKey(K); +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct MapKey(Option); -#[derive(Clone, Debug, Default)] +impl Default for MapKey { + fn default() -> Self { + Self(None) + } +} + +#[derive(Clone, Debug)] pub struct MapKeyRef<'a, K>(Option<&'a K>); +impl<'a, K> Default for MapKeyRef<'a, K> { + fn default() -> Self { + Self(None) + } +} + #[derive(Clone)] pub struct TreeSet(TreeMap) where - K: Clone + Debug + Default + Ord; + K: Clone + Debug + Ord; -impl TreeMap { +impl TreeMap { pub fn from_ordered_entries(entries: impl IntoIterator) -> Self { let tree = SumTree::from_iter( entries @@ -44,7 +56,7 @@ impl TreeMap { let mut cursor = self.0.cursor::>(); cursor.seek(&MapKeyRef(Some(key)), Bias::Left, &()); if let Some(item) = cursor.item() { - if *key == item.key().0 { + if Some(key) == item.key().0.as_ref() { Some(&item.value) } else { None @@ -162,7 +174,7 @@ impl TreeMap { impl Debug for TreeMap where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, V: Clone + Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -173,8 +185,8 @@ where #[derive(Debug)] struct MapSeekTargetAdaptor<'a, T>(&'a T); -impl<'a, K: Debug + Clone + Default + Ord, T: MapSeekTarget> - SeekTarget<'a, MapKey, MapKeyRef<'a, K>> for MapSeekTargetAdaptor<'_, T> +impl<'a, K: Debug + Clone + Ord, T: MapSeekTarget> SeekTarget<'a, MapKey, MapKeyRef<'a, K>> + for MapSeekTargetAdaptor<'_, T> { fn cmp(&self, cursor_location: &MapKeyRef, _: &()) -> Ordering { if let Some(key) = &cursor_location.0 { @@ -197,7 +209,7 @@ impl MapSeekTarget for K { impl Default for TreeMap where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, V: Clone + Debug, { fn default() -> Self { @@ -207,7 +219,7 @@ where impl Item for MapEntry where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, V: Clone, { type Summary = MapKey; @@ -219,19 +231,19 @@ where impl KeyedItem for MapEntry where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, V: Clone, { type Key = MapKey; fn key(&self) -> Self::Key { - MapKey(self.key.clone()) + MapKey(Some(self.key.clone())) } } impl Summary for MapKey where - K: Clone + Debug + Default, + K: Clone + Debug, { type Context = (); @@ -242,16 +254,16 @@ where impl<'a, K> Dimension<'a, MapKey> for MapKeyRef<'a, K> where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, { fn add_summary(&mut self, summary: &'a MapKey, _: &()) { - self.0 = Some(&summary.0) + self.0 = summary.0.as_ref(); } } impl<'a, K> SeekTarget<'a, MapKey, MapKeyRef<'a, K>> for MapKeyRef<'_, K> where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, { fn cmp(&self, cursor_location: &MapKeyRef, _: &()) -> Ordering { Ord::cmp(&self.0, &cursor_location.0) @@ -260,7 +272,7 @@ where impl Default for TreeSet where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, { fn default() -> Self { Self(Default::default()) @@ -269,7 +281,7 @@ where impl TreeSet where - K: Clone + Debug + Default + Ord, + K: Clone + Debug + Ord, { pub fn from_ordered_entries(entries: impl IntoIterator) -> Self { Self(TreeMap::from_ordered_entries( diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 174bf16a5f..82291c752f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -952,7 +952,7 @@ impl Terminal { } } - pub fn select_matches(&mut self, matches: Vec>) { + pub fn select_matches(&mut self, matches: &[RangeInclusive]) { let matches_to_select = self .matches .iter() diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index fa0a7d0ec9..fee4351d5e 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -943,8 +943,9 @@ impl SearchableItem for TerminalView { } /// Store matches returned from find_matches somewhere for rendering - fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { - self.terminal().update(cx, |term, _| term.matches = matches) + fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext) { + self.terminal() + .update(cx, |term, _| term.matches = matches.to_vec()) } /// Returns the selection content to pre-load into this search @@ -958,14 +959,14 @@ impl SearchableItem for TerminalView { } /// Focus match at given index into the Vec of matches - fn activate_match(&mut self, index: usize, _: Vec, cx: &mut ViewContext) { + fn activate_match(&mut self, index: usize, _: &[Self::Match], cx: &mut ViewContext) { self.terminal() .update(cx, |term, _| term.activate_match(index)); cx.notify(); } /// Add selections for all matches given. - fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext) { self.terminal() .update(cx, |term, _| term.select_matches(matches)); cx.notify(); @@ -1003,7 +1004,7 @@ impl SearchableItem for TerminalView { /// Reports back to the search toolbar what the active match should be (the selection) fn active_match_index( &mut self, - matches: Vec, + matches: &[Self::Match], cx: &mut ViewContext, ) -> Option { // Selection head might have a value if there's a selection that isn't diff --git a/crates/vim/src/utils.rs b/crates/vim/src/utils.rs index d0c099f64d..3af455a309 100644 --- a/crates/vim/src/utils.rs +++ b/crates/vim/src/utils.rs @@ -103,7 +103,7 @@ fn copy_selections_content_internal( } editor.highlight_background::( - ranges_to_highlight, + &ranges_to_highlight, |colors| colors.editor_document_highlight_read_background, cx, ); diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 56d52f3d43..608efb23c7 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -25,6 +25,7 @@ test-support = [ [dependencies] anyhow.workspace = true +any_vec.workspace = true async-recursion.workspace = true bincode = "1.2.1" call.workspace = true diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 8e1095b038..0d6b18ae2e 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -1,5 +1,6 @@ use std::{any::Any, sync::Arc}; +use any_vec::AnyVec; use gpui::{ AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView, WindowContext, @@ -45,19 +46,14 @@ pub trait SearchableItem: Item + EventEmitter { } fn clear_matches(&mut self, cx: &mut ViewContext); - fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext); + fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext); fn query_suggestion(&mut self, cx: &mut ViewContext) -> String; - fn activate_match( - &mut self, - index: usize, - matches: Vec, - cx: &mut ViewContext, - ); - fn select_matches(&mut self, matches: Vec, cx: &mut ViewContext); + fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext); + fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext); fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext); fn match_index_for_direction( &mut self, - matches: &Vec, + matches: &[Self::Match], current_index: usize, direction: Direction, count: usize, @@ -82,7 +78,7 @@ pub trait SearchableItem: Item + EventEmitter { ) -> Task>; fn active_match_index( &mut self, - matches: Vec, + matches: &[Self::Match], cx: &mut ViewContext, ) -> Option; } @@ -97,19 +93,19 @@ pub trait SearchableItemHandle: ItemHandle { handler: Box, ) -> Subscription; fn clear_matches(&self, cx: &mut WindowContext); - fn update_matches(&self, matches: &Vec>, cx: &mut WindowContext); + fn update_matches(&self, matches: &AnyVec, cx: &mut WindowContext); fn query_suggestion(&self, cx: &mut WindowContext) -> String; - fn activate_match( + fn activate_match(&self, index: usize, matches: &AnyVec, cx: &mut WindowContext); + fn select_matches(&self, matches: &AnyVec, cx: &mut WindowContext); + fn replace( &self, - index: usize, - matches: &Vec>, - cx: &mut WindowContext, + _: any_vec::element::ElementRef<'_, dyn Send>, + _: &SearchQuery, + _: &mut WindowContext, ); - fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext); - fn replace(&self, _: &Box, _: &SearchQuery, _: &mut WindowContext); fn match_index_for_direction( &self, - matches: &Vec>, + matches: &AnyVec, current_index: usize, direction: Direction, count: usize, @@ -119,10 +115,10 @@ pub trait SearchableItemHandle: ItemHandle { &self, query: Arc, cx: &mut WindowContext, - ) -> Task>>; + ) -> Task>; fn active_match_index( &self, - matches: &Vec>, + matches: &AnyVec, cx: &mut WindowContext, ) -> Option; } @@ -151,80 +147,78 @@ impl SearchableItemHandle for View { fn clear_matches(&self, cx: &mut WindowContext) { self.update(cx, |this, cx| this.clear_matches(cx)); } - fn update_matches(&self, matches: &Vec>, cx: &mut WindowContext) { - let matches = downcast_matches(matches); - self.update(cx, |this, cx| this.update_matches(matches, cx)); + fn update_matches(&self, matches: &AnyVec, cx: &mut WindowContext) { + let matches = matches.downcast_ref().unwrap(); + self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx)); } fn query_suggestion(&self, cx: &mut WindowContext) -> String { self.update(cx, |this, cx| this.query_suggestion(cx)) } - fn activate_match( - &self, - index: usize, - matches: &Vec>, - cx: &mut WindowContext, - ) { - let matches = downcast_matches(matches); - self.update(cx, |this, cx| this.activate_match(index, matches, cx)); + fn activate_match(&self, index: usize, matches: &AnyVec, cx: &mut WindowContext) { + let matches = matches.downcast_ref().unwrap(); + self.update(cx, |this, cx| { + this.activate_match(index, matches.as_slice(), cx) + }); } - fn select_matches(&self, matches: &Vec>, cx: &mut WindowContext) { - let matches = downcast_matches(matches); - self.update(cx, |this, cx| this.select_matches(matches, cx)); + fn select_matches(&self, matches: &AnyVec, cx: &mut WindowContext) { + let matches = matches.downcast_ref().unwrap(); + self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx)); } fn match_index_for_direction( &self, - matches: &Vec>, + matches: &AnyVec, current_index: usize, direction: Direction, count: usize, cx: &mut WindowContext, ) -> usize { - let matches = downcast_matches(matches); + let matches = matches.downcast_ref().unwrap(); self.update(cx, |this, cx| { - this.match_index_for_direction(&matches, current_index, direction, count, cx) + this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx) }) } fn find_matches( &self, query: Arc, cx: &mut WindowContext, - ) -> Task>> { + ) -> Task> { let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); cx.spawn(|_| async { let matches = matches.await; - matches - .into_iter() - .map::, _>(|range| Box::new(range)) - .collect() + let mut any_matches = AnyVec::with_capacity::(matches.len()); + { + let mut any_matches = any_matches.downcast_mut::().unwrap(); + for mat in matches { + any_matches.push(mat); + } + } + any_matches }) } fn active_match_index( &self, - matches: &Vec>, + matches: &AnyVec, cx: &mut WindowContext, ) -> Option { - let matches = downcast_matches(matches); - self.update(cx, |this, cx| this.active_match_index(matches, cx)) + let matches = matches.downcast_ref()?; + self.update(cx, |this, cx| { + this.active_match_index(matches.as_slice(), cx) + }) } - fn replace(&self, matches: &Box, query: &SearchQuery, cx: &mut WindowContext) { - let matches = matches.downcast_ref().unwrap(); - self.update(cx, |this, cx| this.replace(matches, query, cx)) + fn replace( + &self, + mat: any_vec::element::ElementRef<'_, dyn Send>, + query: &SearchQuery, + cx: &mut WindowContext, + ) { + let mat = mat.downcast_ref().unwrap(); + self.update(cx, |this, cx| this.replace(mat, query, cx)) } } -fn downcast_matches(matches: &Vec>) -> Vec { - matches - .iter() - .map(|range| range.downcast_ref::().cloned()) - .collect::>>() - .expect( - "SearchableItemHandle function called with vec of matches of a different type than expected", - ) -} - impl From> for AnyView { fn from(this: Box) -> Self { this.to_any().clone()