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()