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 <me@as-cii.com>
Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
Thorsten Ball 2024-04-03 12:21:17 +02:00 committed by GitHub
parent 7dbcace839
commit 3a0d3cee87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 532 additions and 379 deletions

8
Cargo.lock generated
View file

@ -225,6 +225,12 @@ dependencies = [
"util", "util",
] ]
[[package]]
name = "any_vec"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78f17bacc1bc7b91fef7b1885c10772eb2b9e4e989356f6f0f6a972240f97cd"
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.75" version = "1.0.75"
@ -8296,6 +8302,7 @@ dependencies = [
name = "search" name = "search"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"any_vec",
"anyhow", "anyhow",
"bitflags 2.4.2", "bitflags 2.4.2",
"client", "client",
@ -12112,6 +12119,7 @@ dependencies = [
name = "workspace" name = "workspace"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"any_vec",
"anyhow", "anyhow",
"async-recursion 1.0.5", "async-recursion 1.0.5",
"bincode", "bincode",

View file

@ -220,6 +220,7 @@ zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" } zed_actions = { path = "crates/zed_actions" }
anyhow = "1.0.57" anyhow = "1.0.57"
any_vec = "0.13"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] } async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-fs = "1.6" async-fs = "1.6"
async-recursion = "1.0.0" async-recursion = "1.0.0"

View file

@ -345,7 +345,7 @@ impl AssistantPanel {
style: BlockStyle::Flex, style: BlockStyle::Flex,
position: snapshot.anchor_before(point_selection.head()), position: snapshot.anchor_before(point_selection.head()),
height: 2, height: 2,
render: Arc::new({ render: Box::new({
let inline_assistant = inline_assistant.clone(); let inline_assistant = inline_assistant.clone();
move |cx: &mut BlockContext| { move |cx: &mut BlockContext| {
*measurements.lock() = BlockMeasurements { *measurements.lock() = BlockMeasurements {
@ -695,7 +695,7 @@ impl AssistantPanel {
editor.clear_background_highlights::<PendingInlineAssist>(cx); editor.clear_background_highlights::<PendingInlineAssist>(cx);
} else { } else {
editor.highlight_background::<PendingInlineAssist>( editor.highlight_background::<PendingInlineAssist>(
background_ranges, &background_ranges,
|theme| theme.editor_active_line_background, // todo!("use the appropriate color") |theme| theme.editor_active_line_background, // todo!("use the appropriate color")
cx, cx,
); );
@ -2266,7 +2266,7 @@ impl ConversationEditor {
.unwrap(), .unwrap(),
height: 2, height: 2,
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
render: Arc::new({ render: Box::new({
let conversation = self.conversation.clone(); let conversation = self.conversation.clone();
move |_cx| { move |_cx| {
let message_id = message.id; let message_id = message.id;

View file

@ -32,7 +32,6 @@ use std::{
mem, mem,
ops::Range, ops::Range,
path::PathBuf, path::PathBuf,
sync::Arc,
}; };
use theme::ActiveTheme; use theme::ActiveTheme;
pub use toolbar_controls::ToolbarControls; pub use toolbar_controls::ToolbarControls;
@ -805,7 +804,7 @@ impl Item for ProjectDiagnosticsEditor {
fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
let (message, code_ranges) = highlight_diagnostic_message(&diagnostic); let (message, code_ranges) = highlight_diagnostic_message(&diagnostic);
let message: SharedString = message; let message: SharedString = message;
Arc::new(move |cx| { Box::new(move |cx| {
let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into(); let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into();
h_flex() h_flex()
.id("diagnostic header") .id("diagnostic header")

View file

@ -26,7 +26,7 @@ mod wrap_map;
use crate::EditorStyle; use crate::EditorStyle;
use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId}; use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
pub use block_map::{BlockMap, BlockPoint}; pub use block_map::{BlockMap, BlockPoint};
use collections::{BTreeMap, HashMap, HashSet}; use collections::{HashMap, HashSet};
use fold_map::FoldMap; use fold_map::FoldMap;
use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle}; use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle};
use inlay_map::InlayMap; use inlay_map::InlayMap;
@ -63,7 +63,7 @@ pub trait ToDisplayPoint {
} }
type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>; type TextHighlights = TreeMap<Option<TypeId>, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>;
type InlayHighlights = BTreeMap<TypeId, HashMap<InlayId, (HighlightStyle, InlayHighlight)>>; type InlayHighlights = TreeMap<TypeId, TreeMap<InlayId, (HighlightStyle, InlayHighlight)>>;
/// Decides how text in a [`MultiBuffer`] should be displayed in a buffer, handling inlay hints, /// 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. /// folding, hard tabs, soft wrapping, custom blocks (like diagnostics), and highlighting.
@ -257,10 +257,15 @@ impl DisplayMap {
style: HighlightStyle, style: HighlightStyle,
) { ) {
for highlight in highlights { for highlight in highlights {
self.inlay_highlights let update = self.inlay_highlights.update(&type_id, |highlights| {
.entry(type_id) highlights.insert(highlight.inlay, (style, highlight.clone()))
.or_default() });
.insert(highlight.inlay, (style, highlight)); 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, pub is_tab: bool,
} }
#[derive(Clone)]
pub struct DisplaySnapshot { pub struct DisplaySnapshot {
pub buffer_snapshot: MultiBufferSnapshot, pub buffer_snapshot: MultiBufferSnapshot,
pub fold_snapshot: fold_map::FoldSnapshot, pub fold_snapshot: fold_map::FoldSnapshot,
@ -872,7 +878,7 @@ impl DisplaySnapshot {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>( pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
&self, &self,
) -> Option<&HashMap<InlayId, (HighlightStyle, InlayHighlight)>> { ) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
let type_id = TypeId::of::<Tag>(); let type_id = TypeId::of::<Tag>();
self.inlay_highlights.get(&type_id) self.inlay_highlights.get(&type_id)
} }
@ -1093,7 +1099,7 @@ pub mod tests {
position, position,
height, height,
disposition, disposition,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -37,6 +37,7 @@ pub struct BlockMap {
pub struct BlockMapWriter<'a>(&'a mut BlockMap); pub struct BlockMapWriter<'a>(&'a mut BlockMap);
#[derive(Clone)]
pub struct BlockSnapshot { pub struct BlockSnapshot {
wrap_snapshot: WrapSnapshot, wrap_snapshot: WrapSnapshot,
transforms: SumTree<Transform>, transforms: SumTree<Transform>,
@ -54,7 +55,7 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
struct WrapRow(u32); struct WrapRow(u32);
pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> AnyElement>; pub type RenderBlock = Box<dyn Send + Fn(&mut BlockContext) -> AnyElement>;
pub struct Block { pub struct Block {
id: BlockId, id: BlockId,
@ -65,15 +66,11 @@ pub struct Block {
disposition: BlockDisposition, disposition: BlockDisposition,
} }
#[derive(Clone)] pub struct BlockProperties<P> {
pub struct BlockProperties<P>
where
P: Clone,
{
pub position: P, pub position: P,
pub height: u8, pub height: u8,
pub style: BlockStyle, pub style: BlockStyle,
pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>, pub render: Box<dyn Send + Fn(&mut BlockContext) -> AnyElement>,
pub disposition: BlockDisposition, pub disposition: BlockDisposition,
} }
@ -1041,21 +1038,21 @@ mod tests {
position: buffer_snapshot.anchor_after(Point::new(1, 0)), position: buffer_snapshot.anchor_after(Point::new(1, 0)),
height: 1, height: 1,
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 2)), position: buffer_snapshot.anchor_after(Point::new(1, 2)),
height: 2, height: 2,
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(3, 3)), position: buffer_snapshot.anchor_after(Point::new(3, 3)),
height: 3, height: 3,
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
}, },
]); ]);
@ -1209,14 +1206,14 @@ mod tests {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 12)), position: buffer_snapshot.anchor_after(Point::new(1, 12)),
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
height: 1, height: 1,
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 1)), position: buffer_snapshot.anchor_after(Point::new(1, 1)),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
height: 1, height: 1,
}, },
]); ]);
@ -1311,7 +1308,7 @@ mod tests {
position, position,
height, height,
disposition, disposition,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -1325,7 +1322,14 @@ mod tests {
wrap_map.sync(tab_snapshot, tab_edits, cx) wrap_map.sync(tab_snapshot, tab_edits, cx)
}); });
let mut block_map = block_map.write(wraps_snapshot, wrap_edits); 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) { for (block_id, props) in block_ids.into_iter().zip(block_properties) {
custom_blocks.push((block_id, props)); custom_blocks.push((block_id, props));
} }

View file

@ -1695,38 +1695,39 @@ mod tests {
while inlay_indices.len() < inlay_highlight_count { while inlay_indices.len() < inlay_highlight_count {
inlay_indices.insert(rng.gen_range(0..inlays.len())); inlay_indices.insert(rng.gen_range(0..inlays.len()));
} }
let new_highlights = inlay_indices let new_highlights = TreeMap::from_ordered_entries(
.into_iter() inlay_indices
.filter_map(|i| { .into_iter()
let (_, inlay) = &inlays[i]; .filter_map(|i| {
let inlay_text_len = inlay.text.len(); let (_, inlay) = &inlays[i];
match inlay_text_len { let inlay_text_len = inlay.text.len();
0 => None, match inlay_text_len {
1 => Some(InlayHighlight { 0 => None,
inlay: inlay.id, 1 => Some(InlayHighlight {
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 {
inlay: inlay.id, inlay: inlay.id,
inlay_position: inlay.position, 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))),
.map(|highlight| (highlight.inlay, (HighlightStyle::default(), highlight))) );
.collect();
log::info!("highlighting inlay ranges {new_highlights:?}"); log::info!("highlighting inlay ranges {new_highlights:?}");
inlay_highlights.insert(TypeId::of::<()>(), new_highlights); inlay_highlights.insert(TypeId::of::<()>(), new_highlights);
} }

View file

@ -64,9 +64,9 @@ use gpui::{
AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView,
FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, FontId, FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model,
MouseButton, ParentElement, Pixels, Render, SharedString, StrikethroughStyle, Styled, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, StrikethroughStyle,
StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle, View, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle, UniformListScrollHandle,
ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext, View, ViewContext, ViewInputHandler, VisualContext, WeakView, WhiteSpace, WindowContext,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
@ -116,6 +116,7 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
pub use sum_tree::Bias; pub use sum_tree::Bias;
use sum_tree::TreeMap;
use text::{BufferId, OffsetUtf16, Rope}; use text::{BufferId, OffsetUtf16, Rope};
use theme::{ use theme::{
observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, 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 GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>; // type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Vec<Range<Anchor>>); type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
struct ScrollbarMarkerState {
scrollbar_size: Size<Pixels>,
dirty: bool,
markers: Arc<[PaintQuad]>,
pending_refresh: Option<Task<Result<()>>>,
}
impl ScrollbarMarkerState {
fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> 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`] /// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
/// ///
@ -394,7 +419,8 @@ pub struct Editor {
placeholder_text: Option<Arc<str>>, placeholder_text: Option<Arc<str>>,
highlight_order: usize, highlight_order: usize,
highlighted_rows: HashMap<TypeId, Vec<(usize, Range<Anchor>, Hsla)>>, highlighted_rows: HashMap<TypeId, Vec<(usize, Range<Anchor>, Hsla)>>,
background_highlights: BTreeMap<TypeId, BackgroundHighlight>, background_highlights: TreeMap<TypeId, BackgroundHighlight>,
scrollbar_marker_state: ScrollbarMarkerState,
nav_history: Option<ItemNavHistory>, nav_history: Option<ItemNavHistory>,
context_menu: RwLock<Option<ContextMenu>>, context_menu: RwLock<Option<ContextMenu>>,
mouse_context_menu: Option<MouseContextMenu>, mouse_context_menu: Option<MouseContextMenu>,
@ -444,6 +470,7 @@ pub struct Editor {
>, >,
} }
#[derive(Clone)]
pub struct EditorSnapshot { pub struct EditorSnapshot {
pub mode: EditorMode, pub mode: EditorMode,
show_gutter: bool, show_gutter: bool,
@ -1440,6 +1467,7 @@ impl Editor {
highlight_order: 0, highlight_order: 0,
highlighted_rows: HashMap::default(), highlighted_rows: HashMap::default(),
background_highlights: Default::default(), background_highlights: Default::default(),
scrollbar_marker_state: ScrollbarMarkerState::default(),
nav_history: None, nav_history: None,
context_menu: RwLock::new(None), context_menu: RwLock::new(None),
mouse_context_menu: None, mouse_context_menu: None,
@ -3730,7 +3758,7 @@ impl Editor {
workspace.add_item_to_active_pane(Box::new(editor.clone()), cx); workspace.add_item_to_active_pane(Box::new(editor.clone()), cx);
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>( editor.highlight_background::<Self>(
ranges_to_highlight, &ranges_to_highlight,
|theme| theme.editor_highlighted_line_background, |theme| theme.editor_highlighted_line_background,
cx, cx,
); );
@ -3860,12 +3888,12 @@ impl Editor {
} }
this.highlight_background::<DocumentHighlightRead>( this.highlight_background::<DocumentHighlightRead>(
read_ranges, &read_ranges,
|theme| theme.editor_document_highlight_read_background, |theme| theme.editor_document_highlight_read_background,
cx, cx,
); );
this.highlight_background::<DocumentHighlightWrite>( this.highlight_background::<DocumentHighlightWrite>(
write_ranges, &write_ranges,
|theme| theme.editor_document_highlight_write_background, |theme| theme.editor_document_highlight_write_background,
cx, cx,
); );
@ -7967,7 +7995,7 @@ impl Editor {
}); });
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>( editor.highlight_background::<Self>(
ranges_to_highlight, &ranges_to_highlight,
|theme| theme.editor_highlighted_line_background, |theme| theme.editor_highlighted_line_background,
cx, cx,
); );
@ -8058,15 +8086,15 @@ impl Editor {
editor editor
}); });
let ranges = this let write_highlights =
.clear_background_highlights::<DocumentHighlightWrite>(cx) this.clear_background_highlights::<DocumentHighlightWrite>(cx);
.into_iter() let read_highlights =
.flat_map(|(_, ranges)| ranges.into_iter()) this.clear_background_highlights::<DocumentHighlightRead>(cx);
.chain( let ranges = write_highlights
this.clear_background_highlights::<DocumentHighlightRead>(cx) .iter()
.into_iter() .flat_map(|(_, ranges)| ranges.iter())
.flat_map(|(_, ranges)| ranges.into_iter()), .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
) .cloned()
.collect(); .collect();
this.highlight_text::<Rename>( this.highlight_text::<Rename>(
@ -8084,7 +8112,7 @@ impl Editor {
style: BlockStyle::Flex, style: BlockStyle::Flex,
position: range.start, position: range.start,
height: 1, height: 1,
render: Arc::new({ render: Box::new({
let rename_editor = rename_editor.clone(); let rename_editor = rename_editor.clone();
move |cx: &mut BlockContext| { move |cx: &mut BlockContext| {
let mut text_style = cx.editor_style.text.clone(); let mut text_style = cx.editor_style.text.clone();
@ -9016,13 +9044,13 @@ impl Editor {
pub fn highlight_background<T: 'static>( pub fn highlight_background<T: 'static>(
&mut self, &mut self,
ranges: Vec<Range<Anchor>>, ranges: &[Range<Anchor>],
color_fetcher: fn(&ThemeColors) -> Hsla, color_fetcher: fn(&ThemeColors) -> Hsla,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let snapshot = self.snapshot(cx); let snapshot = self.snapshot(cx);
// this is to try and catch a panic sooner // this is to try and catch a panic sooner
for range in &ranges { for range in ranges {
snapshot snapshot
.buffer_snapshot .buffer_snapshot
.summary_for_anchor::<usize>(&range.start); .summary_for_anchor::<usize>(&range.start);
@ -9032,16 +9060,21 @@ impl Editor {
} }
self.background_highlights self.background_highlights
.insert(TypeId::of::<T>(), (color_fetcher, ranges)); .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
self.scrollbar_marker_state.dirty = true;
cx.notify(); cx.notify();
} }
pub fn clear_background_highlights<T: 'static>( pub fn clear_background_highlights<T: 'static>(
&mut self, &mut self,
_cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<BackgroundHighlight> { ) -> Option<BackgroundHighlight> {
let text_highlights = self.background_highlights.remove(&TypeId::of::<T>()); let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
text_highlights if !text_highlights.1.is_empty() {
self.scrollbar_marker_state.dirty = true;
cx.notify();
}
Some(text_highlights)
} }
#[cfg(feature = "test-support")] #[cfg(feature = "test-support")]
@ -9295,6 +9328,7 @@ impl Editor {
multi_buffer::Event::Edited { multi_buffer::Event::Edited {
singleton_buffer_edited, singleton_buffer_edited,
} => { } => {
self.scrollbar_marker_state.dirty = true;
self.refresh_active_diagnostics(cx); self.refresh_active_diagnostics(cx);
self.refresh_code_actions(cx); self.refresh_code_actions(cx);
if self.has_active_inline_completion(cx) { if self.has_active_inline_completion(cx) {
@ -9362,10 +9396,16 @@ impl Editor {
multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => { multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
cx.emit(EditorEvent::TitleChanged) 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::Closed => cx.emit(EditorEvent::Closed),
multi_buffer::Event::DiagnosticsUpdated => { multi_buffer::Event::DiagnosticsUpdated => {
self.refresh_active_diagnostics(cx); 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 { pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> RenderBlock {
let (text_without_backticks, code_ranges) = highlight_diagnostic_message(&diagnostic); 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 group_id: SharedString = cx.block_id.to_string().into();
let mut text_style = cx.text_style().clone(); let mut text_style = cx.text_style().clone();

View file

@ -3317,7 +3317,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
position: snapshot.anchor_after(Point::new(2, 0)), position: snapshot.anchor_after(Point::new(2, 0)),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
height: 1, height: 1,
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
}], }],
Some(Autoscroll::fit()), Some(Autoscroll::fit()),
cx, cx,
@ -7263,7 +7263,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
|range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end); |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
editor.highlight_background::<Type1>( editor.highlight_background::<Type1>(
vec![ &[
anchor_range(Point::new(2, 1)..Point::new(2, 3)), anchor_range(Point::new(2, 1)..Point::new(2, 3)),
anchor_range(Point::new(4, 2)..Point::new(4, 4)), anchor_range(Point::new(4, 2)..Point::new(4, 4)),
anchor_range(Point::new(6, 3)..Point::new(6, 5)), anchor_range(Point::new(6, 3)..Point::new(6, 5)),
@ -7273,7 +7273,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
cx, cx,
); );
editor.highlight_background::<Type2>( editor.highlight_background::<Type2>(
vec![ &[
anchor_range(Point::new(3, 2)..Point::new(3, 5)), anchor_range(Point::new(3, 2)..Point::new(3, 5)),
anchor_range(Point::new(5, 3)..Point::new(5, 6)), anchor_range(Point::new(5, 3)..Point::new(5, 6)),
anchor_range(Point::new(7, 4)..Point::new(7, 7)), anchor_range(Point::new(7, 4)..Point::new(7, 7)),

View file

@ -24,7 +24,7 @@ use gpui::{
transparent_black, Action, AnchorCorner, AnyElement, AnyView, AvailableSpace, Bounds, transparent_black, Action, AnchorCorner, AnyElement, AnyView, AvailableSpace, Bounds,
ClipboardItem, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ClipboardItem, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element,
ElementContext, ElementInputHandler, Entity, Hitbox, Hsla, InteractiveElement, IntoElement, 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, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
ViewContext, WindowContext, ViewContext, WindowContext,
@ -2370,150 +2370,15 @@ impl EditorElement {
}, },
cx.theme().colors().scrollbar_track_border, 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::<BufferSearchHighlights>(
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 { // Refresh scrollbar markers in the background. Below, we paint whatever markers have already been computed.
let selection_ranges = self.editor.read(cx).background_highlights_in_range( self.refresh_scrollbar_markers(layout, scrollbar_layout, cx);
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,
));
}
}
if is_singleton && scrollbar_settings.git_diff { let markers = self.editor.read(cx).scrollbar_marker_state.markers.clone();
let left_x = left + ScrollbarLayout::BORDER_WIDTH; for marker in markers.iter() {
let right_x = left_x + column_width; let mut marker = marker.clone();
for hunk in layout marker.bounds.origin += scrollbar_layout.hitbox.origin;
.position_map cx.paint_quad(marker);
.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,
));
}
} }
cx.paint_quad(quad( 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::<BufferSearchHighlights>()
&& 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)] #[allow(clippy::too_many_arguments)]
fn paint_highlighted_range( fn paint_highlighted_range(
&self, &self,
@ -3811,6 +3826,13 @@ impl EditorLayout {
} }
} }
struct ColoredRange<T> {
start: T,
end: T,
color: Hsla,
}
#[derive(Clone)]
struct ScrollbarLayout { struct ScrollbarLayout {
hitbox: Hitbox, hitbox: Hitbox,
visible_row_range: Range<f32>, visible_row_range: Range<f32>,
@ -3838,13 +3860,60 @@ impl ScrollbarLayout {
self.hitbox.top() + self.first_row_y_offset + row * self.row_height 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) { fn marker_quads_for_ranges(
let start_y = self.y_for_row(start_row as f32); &self,
let mut end_y = self.y_for_row((end_row + 1) as f32); row_ranges: impl IntoIterator<Item = ColoredRange<u32>>,
if end_y - start_y < Self::MIN_MARKER_HEIGHT { column: usize,
end_y = start_y + Self::MIN_MARKER_HEIGHT; ) -> Vec<PaintQuad> {
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 gpui::TestAppContext;
use language::language_settings; use language::language_settings;
use log::info; use log::info;
use std::{num::NonZeroU32, sync::Arc}; use std::num::NonZeroU32;
use util::test::sample_text; use util::test::sample_text;
#[gpui::test] #[gpui::test]
@ -4473,7 +4542,7 @@ mod tests {
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
height: 3, height: 3,
position: Anchor::min(), position: Anchor::min(),
render: Arc::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
}], }],
None, None,
cx, cx,

View file

@ -20,7 +20,7 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
.innermost_enclosing_bracket_ranges(head..head, None) .innermost_enclosing_bracket_ranges(head..head, None)
{ {
editor.highlight_background::<MatchingBracketHighlight>( editor.highlight_background::<MatchingBracketHighlight>(
vec![ &[
opening_range.to_anchors(&snapshot.buffer_snapshot), opening_range.to_anchors(&snapshot.buffer_snapshot),
closing_range.to_anchors(&snapshot.buffer_snapshot), closing_range.to_anchors(&snapshot.buffer_snapshot),
], ],

View file

@ -342,7 +342,7 @@ fn show_hover(
} else { } else {
// Highlight the selected symbol using a background highlight // Highlight the selected symbol using a background highlight
editor.highlight_background::<HoverState>( editor.highlight_background::<HoverState>(
hover_highlights, &hover_highlights,
|theme| theme.element_hover, // todo update theme |theme| theme.element_hover, // todo update theme
cx, cx,
); );

View file

@ -976,7 +976,7 @@ impl SearchableItem for Editor {
self.clear_background_highlights::<BufferSearchHighlights>(cx); self.clear_background_highlights::<BufferSearchHighlights>(cx);
} }
fn update_matches(&mut self, matches: Vec<Range<Anchor>>, cx: &mut ViewContext<Self>) { fn update_matches(&mut self, matches: &[Range<Anchor>], cx: &mut ViewContext<Self>) {
self.highlight_background::<BufferSearchHighlights>( self.highlight_background::<BufferSearchHighlights>(
matches, matches,
|theme| theme.search_match_background, |theme| theme.search_match_background,
@ -1013,7 +1013,7 @@ impl SearchableItem for Editor {
fn activate_match( fn activate_match(
&mut self, &mut self,
index: usize, index: usize,
matches: Vec<Range<Anchor>>, matches: &[Range<Anchor>],
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.unfold_ranges([matches[index].clone()], false, true, cx); self.unfold_ranges([matches[index].clone()], false, true, cx);
@ -1023,10 +1023,10 @@ impl SearchableItem for Editor {
}) })
} }
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
self.unfold_ranges(matches.clone(), false, false, cx); self.unfold_ranges(matches.to_vec(), false, false, cx);
let mut ranges = Vec::new(); let mut ranges = Vec::new();
for m in &matches { for m in matches {
ranges.push(self.range_for_match(&m)) ranges.push(self.range_for_match(&m))
} }
self.change_selections(None, cx, |s| s.select_ranges(ranges)); self.change_selections(None, cx, |s| s.select_ranges(ranges));
@ -1055,7 +1055,7 @@ impl SearchableItem for Editor {
} }
fn match_index_for_direction( fn match_index_for_direction(
&mut self, &mut self,
matches: &Vec<Range<Anchor>>, matches: &[Range<Anchor>],
current_index: usize, current_index: usize,
direction: Direction, direction: Direction,
count: usize, count: usize,
@ -1147,11 +1147,11 @@ impl SearchableItem for Editor {
fn active_match_index( fn active_match_index(
&mut self, &mut self,
matches: Vec<Range<Anchor>>, matches: &[Range<Anchor>],
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<usize> { ) -> Option<usize> {
active_match_index( active_match_index(
&matches, matches,
&self.selections.newest_anchor().head(), &self.selections.newest_anchor().head(),
&self.buffer().read(cx).snapshot(cx), &self.buffer().read(cx).snapshot(cx),
) )

View file

@ -345,7 +345,7 @@ impl EditorTestContext {
.background_highlights .background_highlights
.get(&TypeId::of::<Tag>()) .get(&TypeId::of::<Tag>())
.map(|h| h.1.clone()) .map(|h| h.1.clone())
.unwrap_or_default() .unwrap_or_else(|| Arc::from([]))
.into_iter() .into_iter()
.map(|range| range.to_offset(&snapshot.buffer_snapshot)) .map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect() .collect()

View file

@ -2853,11 +2853,16 @@ impl From<(&'static str, u64)> for ElementId {
/// Passed as an argument [`ElementContext::paint_quad`]. /// Passed as an argument [`ElementContext::paint_quad`].
#[derive(Clone)] #[derive(Clone)]
pub struct PaintQuad { pub struct PaintQuad {
bounds: Bounds<Pixels>, /// The bounds of the quad within the window.
corner_radii: Corners<Pixels>, pub bounds: Bounds<Pixels>,
background: Hsla, /// The radii of the quad's corners.
border_widths: Edges<Pixels>, pub corner_radii: Corners<Pixels>,
border_color: Hsla, /// The background color of the quad.
pub background: Hsla,
/// The widths of the quad's borders.
pub border_widths: Edges<Pixels>,
/// The color of the quad's borders.
pub border_color: Hsla,
} }
impl PaintQuad { impl PaintQuad {

View file

@ -654,7 +654,7 @@ impl SearchableItem for LspLogView {
self.editor.update(cx, |e, cx| e.clear_matches(cx)) self.editor.update(cx, |e, cx| e.clear_matches(cx))
} }
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
self.editor self.editor
.update(cx, |e, cx| e.update_matches(matches, cx)) .update(cx, |e, cx| e.update_matches(matches, cx))
} }
@ -666,14 +666,14 @@ impl SearchableItem for LspLogView {
fn activate_match( fn activate_match(
&mut self, &mut self,
index: usize, index: usize,
matches: Vec<Self::Match>, matches: &[Self::Match],
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.editor self.editor
.update(cx, |e, cx| e.activate_match(index, matches, cx)) .update(cx, |e, cx| e.activate_match(index, matches, cx))
} }
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
self.editor self.editor
.update(cx, |e, cx| e.select_matches(matches, cx)) .update(cx, |e, cx| e.select_matches(matches, cx))
} }
@ -700,7 +700,7 @@ impl SearchableItem for LspLogView {
} }
fn active_match_index( fn active_match_index(
&mut self, &mut self,
matches: Vec<Self::Match>, matches: &[Self::Match],
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<usize> { ) -> Option<usize> {
self.editor self.editor

View file

@ -337,7 +337,7 @@ impl Render for SyntaxTreeView {
tree_view.update_editor_with_range_for_descendant_ix(descendant_ix, cx, |editor, range, cx| { tree_view.update_editor_with_range_for_descendant_ix(descendant_ix, cx, |editor, range, cx| {
editor.clear_background_highlights::<Self>(cx); editor.clear_background_highlights::<Self>(cx);
editor.highlight_background::<Self>( editor.highlight_background::<Self>(
vec![range], &[range],
|theme| theme.editor_document_highlight_write_background, |theme| theme.editor_document_highlight_write_background,
cx, cx,
); );

View file

@ -14,6 +14,7 @@ doctest = false
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
any_vec.workspace = true
bitflags.workspace = true bitflags.workspace = true
collections.workspace = true collections.workspace = true
editor.workspace = true editor.workspace = true

View file

@ -7,6 +7,7 @@ use crate::{
ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch,
ToggleCaseSensitive, ToggleReplace, ToggleWholeWord, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
}; };
use any_vec::AnyVec;
use collections::HashMap; use collections::HashMap;
use editor::{ use editor::{
actions::{Tab, TabPrev}, actions::{Tab, TabPrev},
@ -25,7 +26,7 @@ use project::{
}; };
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::{any::Any, sync::Arc}; use std::sync::Arc;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{h_flex, prelude::*, IconButton, IconName, ToggleButton, Tooltip}; use ui::{h_flex, prelude::*, IconButton, IconName, ToggleButton, Tooltip};
@ -70,8 +71,7 @@ pub struct BufferSearchBar {
active_match_index: Option<usize>, active_match_index: Option<usize>,
active_searchable_item_subscription: Option<Subscription>, active_searchable_item_subscription: Option<Subscription>,
active_search: Option<Arc<SearchQuery>>, active_search: Option<Arc<SearchQuery>>,
searchable_items_with_matches: searchable_items_with_matches: HashMap<Box<dyn WeakSearchableItemHandle>, AnyVec<dyn Send>>,
HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
pending_search: Option<Task<()>>, pending_search: Option<Task<()>>,
search_options: SearchOptions, search_options: SearchOptions,
default_options: SearchOptions, default_options: SearchOptions,
@ -191,7 +191,7 @@ impl Render for BufferSearchBar {
let matches_count = self let matches_count = self
.searchable_items_with_matches .searchable_items_with_matches
.get(&searchable_item.downgrade()) .get(&searchable_item.downgrade())
.map(Vec::len) .map(AnyVec::len)
.unwrap_or(0); .unwrap_or(0);
if let Some(match_ix) = self.active_match_index { if let Some(match_ix) = self.active_match_index {
Some(format!("{}/{}", match_ix + 1, matches_count)) Some(format!("{}/{}", match_ix + 1, matches_count))
@ -1067,7 +1067,7 @@ impl BufferSearchBar {
.as_ref() .as_ref()
.clone() .clone()
.with_replacement(self.replacement(cx)); .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); self.select_next_match(&SelectNextMatch, cx);
} }
should_propagate = false; should_propagate = false;

View file

@ -585,43 +585,54 @@ impl ProjectSearchView {
cx.notify(); cx.notify();
} }
fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) { fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
let model = self.model.read(cx); if self.model.read(cx).match_ranges.is_empty() {
if let Some(query) = model.active_query.as_ref() { return;
if model.match_ranges.is_empty() { }
return; let Some(active_index) = self.active_match_index else {
} return;
if let Some(active_index) = self.active_match_index { };
let query = query.clone().with_replacement(self.replacement(cx));
self.results_editor.replace( let query = self.model.read(cx).active_query.clone();
&(Box::new(model.match_ranges[active_index].clone()) as _), if let Some(query) = query {
&query, let query = query.with_replacement(self.replacement(cx));
cx,
); // TODO: Do we need the clone here?
self.select_match(Direction::Next, cx) 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 { pub fn replacement(&self, cx: &AppContext) -> String {
self.replacement_editor.read(cx).text(cx) self.replacement_editor.read(cx).text(cx)
} }
fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) { fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
let model = self.model.read(cx); if self.active_match_index.is_none() {
if let Some(query) = model.active_query.as_ref() { return;
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::<Vec<_>>();
for item in matches {
self.results_editor.replace(&item, &query, cx);
}
}
} }
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( fn new(
@ -1060,7 +1071,7 @@ impl ProjectSearchView {
editor.scroll(Point::default(), Some(Axis::Vertical), cx); editor.scroll(Point::default(), Some(Axis::Vertical), cx);
} }
editor.highlight_background::<Self>( editor.highlight_background::<Self>(
match_ranges, &match_ranges,
|theme| theme.search_match_background, |theme| theme.search_match_background,
cx, cx,
); );

View file

@ -5,7 +5,7 @@ use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct TreeMap<K, V>(SumTree<MapEntry<K, V>>) pub struct TreeMap<K, V>(SumTree<MapEntry<K, V>>)
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
V: Clone + Debug; V: Clone + Debug;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -14,18 +14,30 @@ pub struct MapEntry<K, V> {
value: V, value: V,
} }
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct MapKey<K>(K); pub struct MapKey<K>(Option<K>);
#[derive(Clone, Debug, Default)] impl<K> Default for MapKey<K> {
fn default() -> Self {
Self(None)
}
}
#[derive(Clone, Debug)]
pub struct MapKeyRef<'a, K>(Option<&'a K>); pub struct MapKeyRef<'a, K>(Option<&'a K>);
impl<'a, K> Default for MapKeyRef<'a, K> {
fn default() -> Self {
Self(None)
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct TreeSet<K>(TreeMap<K, ()>) pub struct TreeSet<K>(TreeMap<K, ()>)
where where
K: Clone + Debug + Default + Ord; K: Clone + Debug + Ord;
impl<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> { impl<K: Clone + Debug + Ord, V: Clone + Debug> TreeMap<K, V> {
pub fn from_ordered_entries(entries: impl IntoIterator<Item = (K, V)>) -> Self { pub fn from_ordered_entries(entries: impl IntoIterator<Item = (K, V)>) -> Self {
let tree = SumTree::from_iter( let tree = SumTree::from_iter(
entries entries
@ -44,7 +56,7 @@ impl<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> {
let mut cursor = self.0.cursor::<MapKeyRef<'_, K>>(); let mut cursor = self.0.cursor::<MapKeyRef<'_, K>>();
cursor.seek(&MapKeyRef(Some(key)), Bias::Left, &()); cursor.seek(&MapKeyRef(Some(key)), Bias::Left, &());
if let Some(item) = cursor.item() { if let Some(item) = cursor.item() {
if *key == item.key().0 { if Some(key) == item.key().0.as_ref() {
Some(&item.value) Some(&item.value)
} else { } else {
None None
@ -162,7 +174,7 @@ impl<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> {
impl<K: Debug, V: Debug> Debug for TreeMap<K, V> impl<K: Debug, V: Debug> Debug for TreeMap<K, V>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
V: Clone + Debug, V: Clone + Debug,
{ {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -173,8 +185,8 @@ where
#[derive(Debug)] #[derive(Debug)]
struct MapSeekTargetAdaptor<'a, T>(&'a T); struct MapSeekTargetAdaptor<'a, T>(&'a T);
impl<'a, K: Debug + Clone + Default + Ord, T: MapSeekTarget<K>> impl<'a, K: Debug + Clone + Ord, T: MapSeekTarget<K>> SeekTarget<'a, MapKey<K>, MapKeyRef<'a, K>>
SeekTarget<'a, MapKey<K>, MapKeyRef<'a, K>> for MapSeekTargetAdaptor<'_, T> for MapSeekTargetAdaptor<'_, T>
{ {
fn cmp(&self, cursor_location: &MapKeyRef<K>, _: &()) -> Ordering { fn cmp(&self, cursor_location: &MapKeyRef<K>, _: &()) -> Ordering {
if let Some(key) = &cursor_location.0 { if let Some(key) = &cursor_location.0 {
@ -197,7 +209,7 @@ impl<K: Debug + Ord> MapSeekTarget<K> for K {
impl<K, V> Default for TreeMap<K, V> impl<K, V> Default for TreeMap<K, V>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
V: Clone + Debug, V: Clone + Debug,
{ {
fn default() -> Self { fn default() -> Self {
@ -207,7 +219,7 @@ where
impl<K, V> Item for MapEntry<K, V> impl<K, V> Item for MapEntry<K, V>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
V: Clone, V: Clone,
{ {
type Summary = MapKey<K>; type Summary = MapKey<K>;
@ -219,19 +231,19 @@ where
impl<K, V> KeyedItem for MapEntry<K, V> impl<K, V> KeyedItem for MapEntry<K, V>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
V: Clone, V: Clone,
{ {
type Key = MapKey<K>; type Key = MapKey<K>;
fn key(&self) -> Self::Key { fn key(&self) -> Self::Key {
MapKey(self.key.clone()) MapKey(Some(self.key.clone()))
} }
} }
impl<K> Summary for MapKey<K> impl<K> Summary for MapKey<K>
where where
K: Clone + Debug + Default, K: Clone + Debug,
{ {
type Context = (); type Context = ();
@ -242,16 +254,16 @@ where
impl<'a, K> Dimension<'a, MapKey<K>> for MapKeyRef<'a, K> impl<'a, K> Dimension<'a, MapKey<K>> for MapKeyRef<'a, K>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
{ {
fn add_summary(&mut self, summary: &'a MapKey<K>, _: &()) { fn add_summary(&mut self, summary: &'a MapKey<K>, _: &()) {
self.0 = Some(&summary.0) self.0 = summary.0.as_ref();
} }
} }
impl<'a, K> SeekTarget<'a, MapKey<K>, MapKeyRef<'a, K>> for MapKeyRef<'_, K> impl<'a, K> SeekTarget<'a, MapKey<K>, MapKeyRef<'a, K>> for MapKeyRef<'_, K>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
{ {
fn cmp(&self, cursor_location: &MapKeyRef<K>, _: &()) -> Ordering { fn cmp(&self, cursor_location: &MapKeyRef<K>, _: &()) -> Ordering {
Ord::cmp(&self.0, &cursor_location.0) Ord::cmp(&self.0, &cursor_location.0)
@ -260,7 +272,7 @@ where
impl<K> Default for TreeSet<K> impl<K> Default for TreeSet<K>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
{ {
fn default() -> Self { fn default() -> Self {
Self(Default::default()) Self(Default::default())
@ -269,7 +281,7 @@ where
impl<K> TreeSet<K> impl<K> TreeSet<K>
where where
K: Clone + Debug + Default + Ord, K: Clone + Debug + Ord,
{ {
pub fn from_ordered_entries(entries: impl IntoIterator<Item = K>) -> Self { pub fn from_ordered_entries(entries: impl IntoIterator<Item = K>) -> Self {
Self(TreeMap::from_ordered_entries( Self(TreeMap::from_ordered_entries(

View file

@ -952,7 +952,7 @@ impl Terminal {
} }
} }
pub fn select_matches(&mut self, matches: Vec<RangeInclusive<AlacPoint>>) { pub fn select_matches(&mut self, matches: &[RangeInclusive<AlacPoint>]) {
let matches_to_select = self let matches_to_select = self
.matches .matches
.iter() .iter()

View file

@ -943,8 +943,9 @@ impl SearchableItem for TerminalView {
} }
/// Store matches returned from find_matches somewhere for rendering /// Store matches returned from find_matches somewhere for rendering
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
self.terminal().update(cx, |term, _| term.matches = matches) self.terminal()
.update(cx, |term, _| term.matches = matches.to_vec())
} }
/// Returns the selection content to pre-load into this search /// 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 /// Focus match at given index into the Vec of matches
fn activate_match(&mut self, index: usize, _: Vec<Self::Match>, cx: &mut ViewContext<Self>) { fn activate_match(&mut self, index: usize, _: &[Self::Match], cx: &mut ViewContext<Self>) {
self.terminal() self.terminal()
.update(cx, |term, _| term.activate_match(index)); .update(cx, |term, _| term.activate_match(index));
cx.notify(); cx.notify();
} }
/// Add selections for all matches given. /// Add selections for all matches given.
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) { fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
self.terminal() self.terminal()
.update(cx, |term, _| term.select_matches(matches)); .update(cx, |term, _| term.select_matches(matches));
cx.notify(); cx.notify();
@ -1003,7 +1004,7 @@ impl SearchableItem for TerminalView {
/// Reports back to the search toolbar what the active match should be (the selection) /// Reports back to the search toolbar what the active match should be (the selection)
fn active_match_index( fn active_match_index(
&mut self, &mut self,
matches: Vec<Self::Match>, matches: &[Self::Match],
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<usize> { ) -> Option<usize> {
// Selection head might have a value if there's a selection that isn't // Selection head might have a value if there's a selection that isn't

View file

@ -103,7 +103,7 @@ fn copy_selections_content_internal(
} }
editor.highlight_background::<HighlightOnYank>( editor.highlight_background::<HighlightOnYank>(
ranges_to_highlight, &ranges_to_highlight,
|colors| colors.editor_document_highlight_read_background, |colors| colors.editor_document_highlight_read_background,
cx, cx,
); );

View file

@ -25,6 +25,7 @@ test-support = [
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
any_vec.workspace = true
async-recursion.workspace = true async-recursion.workspace = true
bincode = "1.2.1" bincode = "1.2.1"
call.workspace = true call.workspace = true

View file

@ -1,5 +1,6 @@
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
use any_vec::AnyVec;
use gpui::{ use gpui::{
AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext,
WeakView, WindowContext, WeakView, WindowContext,
@ -45,19 +46,14 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
} }
fn clear_matches(&mut self, cx: &mut ViewContext<Self>); fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>); fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String; fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
fn activate_match( fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
&mut self, fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
index: usize,
matches: Vec<Self::Match>,
cx: &mut ViewContext<Self>,
);
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>); fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
fn match_index_for_direction( fn match_index_for_direction(
&mut self, &mut self,
matches: &Vec<Self::Match>, matches: &[Self::Match],
current_index: usize, current_index: usize,
direction: Direction, direction: Direction,
count: usize, count: usize,
@ -82,7 +78,7 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
) -> Task<Vec<Self::Match>>; ) -> Task<Vec<Self::Match>>;
fn active_match_index( fn active_match_index(
&mut self, &mut self,
matches: Vec<Self::Match>, matches: &[Self::Match],
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<usize>; ) -> Option<usize>;
} }
@ -97,19 +93,19 @@ pub trait SearchableItemHandle: ItemHandle {
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>, handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
) -> Subscription; ) -> Subscription;
fn clear_matches(&self, cx: &mut WindowContext); fn clear_matches(&self, cx: &mut WindowContext);
fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext); fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
fn query_suggestion(&self, cx: &mut WindowContext) -> String; fn query_suggestion(&self, cx: &mut WindowContext) -> String;
fn activate_match( fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
fn replace(
&self, &self,
index: usize, _: any_vec::element::ElementRef<'_, dyn Send>,
matches: &Vec<Box<dyn Any + Send>>, _: &SearchQuery,
cx: &mut WindowContext, _: &mut WindowContext,
); );
fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
fn replace(&self, _: &Box<dyn Any + Send>, _: &SearchQuery, _: &mut WindowContext);
fn match_index_for_direction( fn match_index_for_direction(
&self, &self,
matches: &Vec<Box<dyn Any + Send>>, matches: &AnyVec<dyn Send>,
current_index: usize, current_index: usize,
direction: Direction, direction: Direction,
count: usize, count: usize,
@ -119,10 +115,10 @@ pub trait SearchableItemHandle: ItemHandle {
&self, &self,
query: Arc<SearchQuery>, query: Arc<SearchQuery>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Task<Vec<Box<dyn Any + Send>>>; ) -> Task<AnyVec<dyn Send>>;
fn active_match_index( fn active_match_index(
&self, &self,
matches: &Vec<Box<dyn Any + Send>>, matches: &AnyVec<dyn Send>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Option<usize>; ) -> Option<usize>;
} }
@ -151,80 +147,78 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
fn clear_matches(&self, cx: &mut WindowContext) { fn clear_matches(&self, cx: &mut WindowContext) {
self.update(cx, |this, cx| this.clear_matches(cx)); self.update(cx, |this, cx| this.clear_matches(cx));
} }
fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) { fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
let matches = downcast_matches(matches); let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| this.update_matches(matches, cx)); self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
} }
fn query_suggestion(&self, cx: &mut WindowContext) -> String { fn query_suggestion(&self, cx: &mut WindowContext) -> String {
self.update(cx, |this, cx| this.query_suggestion(cx)) self.update(cx, |this, cx| this.query_suggestion(cx))
} }
fn activate_match( fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
&self, let matches = matches.downcast_ref().unwrap();
index: usize, self.update(cx, |this, cx| {
matches: &Vec<Box<dyn Any + Send>>, this.activate_match(index, matches.as_slice(), cx)
cx: &mut WindowContext, });
) {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.activate_match(index, matches, cx));
} }
fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) { fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
let matches = downcast_matches(matches); let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| this.select_matches(matches, cx)); self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
} }
fn match_index_for_direction( fn match_index_for_direction(
&self, &self,
matches: &Vec<Box<dyn Any + Send>>, matches: &AnyVec<dyn Send>,
current_index: usize, current_index: usize,
direction: Direction, direction: Direction,
count: usize, count: usize,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> usize { ) -> usize {
let matches = downcast_matches(matches); let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| { 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( fn find_matches(
&self, &self,
query: Arc<SearchQuery>, query: Arc<SearchQuery>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Task<Vec<Box<dyn Any + Send>>> { ) -> Task<AnyVec<dyn Send>> {
let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
cx.spawn(|_| async { cx.spawn(|_| async {
let matches = matches.await; let matches = matches.await;
matches let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
.into_iter() {
.map::<Box<dyn Any + Send>, _>(|range| Box::new(range)) let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
.collect() for mat in matches {
any_matches.push(mat);
}
}
any_matches
}) })
} }
fn active_match_index( fn active_match_index(
&self, &self,
matches: &Vec<Box<dyn Any + Send>>, matches: &AnyVec<dyn Send>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> Option<usize> { ) -> Option<usize> {
let matches = downcast_matches(matches); let matches = matches.downcast_ref()?;
self.update(cx, |this, cx| this.active_match_index(matches, cx)) self.update(cx, |this, cx| {
this.active_match_index(matches.as_slice(), cx)
})
} }
fn replace(&self, matches: &Box<dyn Any + Send>, query: &SearchQuery, cx: &mut WindowContext) { fn replace(
let matches = matches.downcast_ref().unwrap(); &self,
self.update(cx, |this, cx| this.replace(matches, query, cx)) 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<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
matches
.iter()
.map(|range| range.downcast_ref::<T>().cloned())
.collect::<Option<Vec<_>>>()
.expect(
"SearchableItemHandle function called with vec of matches of a different type than expected",
)
}
impl From<Box<dyn SearchableItemHandle>> for AnyView { impl From<Box<dyn SearchableItemHandle>> for AnyView {
fn from(this: Box<dyn SearchableItemHandle>) -> Self { fn from(this: Box<dyn SearchableItemHandle>) -> Self {
this.to_any().clone() this.to_any().clone()