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 ba3d1e8140c864aa1db99c4ff0d765cbc35a0db0..a2e5ed0d7ee4213d08a8ff24ed688a4d6f247d9d 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 72079ff004ea35be1fad5a87e18eea683469638f..765a99ac3ea1ab31e253ea9ce14a69f0fd36a43f 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 61604d7ef628ad62730701cada4df0b22dda5449..435388cf817fbef20d7729434f660dcae3608af8 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 be5e4a7f8a3b61c49438ccffdc9c61053100acd1..7dec4be5cd9a7a77d257f16f9dfe6bf6c16b9105 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 03ff628b8fc87bbbf1be0082ad05230eb86d63e7..f27499185343b7617d99ee10fb62daff18af15ff 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 2d7280114f581861570b0bf54ca4b3c86eb3a260..6de21b0d08079e86ba1a66e72477946f12d7c967 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 a64200709e13007ad5991bb3de7a31c1bee2d6ad..387885b406efe0ba58ba1ab4e5996ef35f7f470a 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 6363202c910b7eced11c70245e61b5a83435290a..f331bf5769bc2265913a3c4c29f955c0819b9088 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 b6abacb2a94e38e11d63d4368af2eb174ff98ac3..d9a2ff4d034bf1c0ab3ff194e0159c16d2591a10 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 9a1dacf44d58be712659caa576f5a62a1b94ae81..26ba2f06477422d3c724a3c86cdbeb0f725b6cbb 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 1d0b304913d90bba38311e2dc169c2e5120a1e84..ca905fee026f61d648be35f8c25ff0a0f33af463 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 fb9011e02b34d1f577f2cb252cbdc1f9bf581965..6c27b0b3b05e095f81fecf958362e5eae124d54e 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 3f681445ef19b3d41f4b084fb1fa89f509e008e8..ec55f512f0e9f3be415b12f9b2e87be012fd8486 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 0a0f6219695ce08f97b872a3340ed9e736f633be..9611a8581c065f6118cdad2dd1c4b710bf17ab81 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 84cebeafca6038953d5edb9a96bf2d15e00ea058..a9a473b84f531ad7cbd68f63aaa0f6d654b12e64 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 c03f4dc90a3301718479f41e3559e19fc0329423..0a106a3f80e5919353821c67a5d86dbf9e7b7444 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 c4d478b16edc419f8d493d914f3ac602c1dd019e..1dd647fc82d63494f6afdc9617ecf88541c830d1 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 943bfea56b9fb2de301909c54b47577b5786f408..0654027b475639e1eece87087ec7c99b059952be 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 fc10cfd788f4b2d901e2d21ac6fb7538f52139cb..f0206aff9fd2f975a975c29dc60340ed12f76d7c 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 1a93669cee32f4dddb05c2e46a73a9b806086a5e..6a06d7b8ab7ed8378df57603f84f077cc5acb167 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 b46150e3c376fcaae3414b9ecca74eb7003b8a0c..a59f796fe0d1d424336d1756876fe1ffaf46356e 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 174bf16a5f7f81cbad9cb290081e6f7adfd39d61..82291c752f891248bc252f8401846a0772bbd5e2 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 fa0a7d0ec95520419d748f928451c834d011e11e..fee4351d5e7eea3185761b79ba39adfbaaf20946 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 d0c099f64d4bfb718b6ac34cd6ebd6a5679af9b4..3af455a3090eac8d575541178fc27694510cd82a 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 56d52f3d436497f04984c7e28c148f3976bfb2cd..608efb23c711aba91b7f856f7aab27a76812113b 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 8e1095b038af89149cb180632f8d36bf532234ff..0d6b18ae2eec53af6d6f8f20f6a0d205d2b89573 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()