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",
]
[[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",

View File

@ -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"

View File

@ -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::<PendingInlineAssist>(cx);
} else {
editor.highlight_background::<PendingInlineAssist>(
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;

View File

@ -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")

View File

@ -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<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,
/// 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<Tag: ?Sized + 'static>(
&self,
) -> Option<&HashMap<InlayId, (HighlightStyle, InlayHighlight)>> {
) -> Option<&TreeMap<InlayId, (HighlightStyle, InlayHighlight)>> {
let type_id = TypeId::of::<Tag>();
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::<Vec<_>>();

View File

@ -37,6 +37,7 @@ pub struct BlockMap {
pub struct BlockMapWriter<'a>(&'a mut BlockMap);
#[derive(Clone)]
pub struct BlockSnapshot {
wrap_snapshot: WrapSnapshot,
transforms: SumTree<Transform>,
@ -54,7 +55,7 @@ struct BlockRow(u32);
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
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 {
id: BlockId,
@ -65,15 +66,11 @@ pub struct Block {
disposition: BlockDisposition,
}
#[derive(Clone)]
pub struct BlockProperties<P>
where
P: Clone,
{
pub struct BlockProperties<P> {
pub position: P,
pub height: u8,
pub style: BlockStyle,
pub render: Arc<dyn Fn(&mut BlockContext) -> AnyElement>,
pub render: Box<dyn Send + Fn(&mut BlockContext) -> 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::<Vec<_>>();
@ -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));
}

View File

@ -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);
}

View File

@ -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<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`]
///
@ -394,7 +419,8 @@ pub struct Editor {
placeholder_text: Option<Arc<str>>,
highlight_order: usize,
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>,
context_menu: RwLock<Option<ContextMenu>>,
mouse_context_menu: Option<MouseContextMenu>,
@ -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::<Self>(
ranges_to_highlight,
&ranges_to_highlight,
|theme| theme.editor_highlighted_line_background,
cx,
);
@ -3860,12 +3888,12 @@ impl Editor {
}
this.highlight_background::<DocumentHighlightRead>(
read_ranges,
&read_ranges,
|theme| theme.editor_document_highlight_read_background,
cx,
);
this.highlight_background::<DocumentHighlightWrite>(
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::<Self>(
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::<DocumentHighlightWrite>(cx)
.into_iter()
.flat_map(|(_, ranges)| ranges.into_iter())
.chain(
this.clear_background_highlights::<DocumentHighlightRead>(cx)
.into_iter()
.flat_map(|(_, ranges)| ranges.into_iter()),
)
let write_highlights =
this.clear_background_highlights::<DocumentHighlightWrite>(cx);
let read_highlights =
this.clear_background_highlights::<DocumentHighlightRead>(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::<Rename>(
@ -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<T: 'static>(
&mut self,
ranges: Vec<Range<Anchor>>,
ranges: &[Range<Anchor>],
color_fetcher: fn(&ThemeColors) -> Hsla,
cx: &mut ViewContext<Self>,
) {
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::<usize>(&range.start);
@ -9032,16 +9060,21 @@ impl Editor {
}
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();
}
pub fn clear_background_highlights<T: 'static>(
&mut self,
_cx: &mut ViewContext<Self>,
cx: &mut ViewContext<Self>,
) -> Option<BackgroundHighlight> {
let text_highlights = self.background_highlights.remove(&TypeId::of::<T>());
text_highlights
let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
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();

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)),
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<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
editor.highlight_background::<Type1>(
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::<Type2>(
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)),

View File

@ -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::<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 {
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::<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)]
fn paint_highlighted_range(
&self,
@ -3811,6 +3826,13 @@ impl EditorLayout {
}
}
struct ColoredRange<T> {
start: T,
end: T,
color: Hsla,
}
#[derive(Clone)]
struct ScrollbarLayout {
hitbox: Hitbox,
visible_row_range: Range<f32>,
@ -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<Item = ColoredRange<u32>>,
column: usize,
) -> 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 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,

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)
{
editor.highlight_background::<MatchingBracketHighlight>(
vec![
&[
opening_range.to_anchors(&snapshot.buffer_snapshot),
closing_range.to_anchors(&snapshot.buffer_snapshot),
],

View File

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

View File

@ -976,7 +976,7 @@ impl SearchableItem for Editor {
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>(
matches,
|theme| theme.search_match_background,
@ -1013,7 +1013,7 @@ impl SearchableItem for Editor {
fn activate_match(
&mut self,
index: usize,
matches: Vec<Range<Anchor>>,
matches: &[Range<Anchor>],
cx: &mut ViewContext<Self>,
) {
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>) {
self.unfold_ranges(matches.clone(), false, false, cx);
fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
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<Range<Anchor>>,
matches: &[Range<Anchor>],
current_index: usize,
direction: Direction,
count: usize,
@ -1147,11 +1147,11 @@ impl SearchableItem for Editor {
fn active_match_index(
&mut self,
matches: Vec<Range<Anchor>>,
matches: &[Range<Anchor>],
cx: &mut ViewContext<Self>,
) -> Option<usize> {
active_match_index(
&matches,
matches,
&self.selections.newest_anchor().head(),
&self.buffer().read(cx).snapshot(cx),
)

View File

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

View File

@ -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<Pixels>,
corner_radii: Corners<Pixels>,
background: Hsla,
border_widths: Edges<Pixels>,
border_color: Hsla,
/// The bounds of the quad within the window.
pub bounds: Bounds<Pixels>,
/// The radii of the quad's corners.
pub corner_radii: Corners<Pixels>,
/// 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 {

View File

@ -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<Self::Match>, cx: &mut ViewContext<Self>) {
fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
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<Self::Match>,
matches: &[Self::Match],
cx: &mut ViewContext<Self>,
) {
self.editor
.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
.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<Self::Match>,
matches: &[Self::Match],
cx: &mut ViewContext<Self>,
) -> Option<usize> {
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| {
editor.clear_background_highlights::<Self>(cx);
editor.highlight_background::<Self>(
vec![range],
&[range],
|theme| theme.editor_document_highlight_write_background,
cx,
);

View File

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

View File

@ -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<usize>,
active_searchable_item_subscription: Option<Subscription>,
active_search: Option<Arc<SearchQuery>>,
searchable_items_with_matches:
HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
searchable_items_with_matches: HashMap<Box<dyn WeakSearchableItemHandle>, AnyVec<dyn Send>>,
pending_search: Option<Task<()>>,
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;

View File

@ -585,43 +585,54 @@ impl ProjectSearchView {
cx.notify();
}
fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
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<Self>) {
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::<Vec<_>>();
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::<Self>(
match_ranges,
&match_ranges,
|theme| theme.search_match_background,
cx,
);

View File

@ -5,7 +5,7 @@ use crate::{Bias, Dimension, Edit, Item, KeyedItem, SeekTarget, SumTree, Summary
#[derive(Clone, PartialEq, Eq)]
pub struct TreeMap<K, V>(SumTree<MapEntry<K, V>>)
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<K, V> {
value: V,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct MapKey<K>(K);
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
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>);
impl<'a, K> Default for MapKeyRef<'a, K> {
fn default() -> Self {
Self(None)
}
}
#[derive(Clone)]
pub struct TreeSet<K>(TreeMap<K, ()>)
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 {
let tree = SumTree::from_iter(
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>>();
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<K: Clone + Debug + Default + Ord, V: Clone + Debug> TreeMap<K, V> {
impl<K: Debug, V: Debug> Debug for TreeMap<K, V>
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<K>>
SeekTarget<'a, MapKey<K>, MapKeyRef<'a, K>> for MapSeekTargetAdaptor<'_, T>
impl<'a, K: Debug + Clone + Ord, T: MapSeekTarget<K>> SeekTarget<'a, MapKey<K>, MapKeyRef<'a, K>>
for MapSeekTargetAdaptor<'_, T>
{
fn cmp(&self, cursor_location: &MapKeyRef<K>, _: &()) -> Ordering {
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>
where
K: Clone + Debug + Default + Ord,
K: Clone + Debug + Ord,
V: Clone + Debug,
{
fn default() -> Self {
@ -207,7 +219,7 @@ where
impl<K, V> Item for MapEntry<K, V>
where
K: Clone + Debug + Default + Ord,
K: Clone + Debug + Ord,
V: Clone,
{
type Summary = MapKey<K>;
@ -219,19 +231,19 @@ where
impl<K, V> KeyedItem for MapEntry<K, V>
where
K: Clone + Debug + Default + Ord,
K: Clone + Debug + Ord,
V: Clone,
{
type Key = MapKey<K>;
fn key(&self) -> Self::Key {
MapKey(self.key.clone())
MapKey(Some(self.key.clone()))
}
}
impl<K> Summary for MapKey<K>
where
K: Clone + Debug + Default,
K: Clone + Debug,
{
type Context = ();
@ -242,16 +254,16 @@ where
impl<'a, K> Dimension<'a, MapKey<K>> for MapKeyRef<'a, K>
where
K: Clone + Debug + Default + Ord,
K: Clone + Debug + Ord,
{
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>
where
K: Clone + Debug + Default + Ord,
K: Clone + Debug + Ord,
{
fn cmp(&self, cursor_location: &MapKeyRef<K>, _: &()) -> Ordering {
Ord::cmp(&self.0, &cursor_location.0)
@ -260,7 +272,7 @@ where
impl<K> Default for TreeSet<K>
where
K: Clone + Debug + Default + Ord,
K: Clone + Debug + Ord,
{
fn default() -> Self {
Self(Default::default())
@ -269,7 +281,7 @@ where
impl<K> TreeSet<K>
where
K: Clone + Debug + Default + Ord,
K: Clone + Debug + Ord,
{
pub fn from_ordered_entries(entries: impl IntoIterator<Item = K>) -> Self {
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
.matches
.iter()

View File

@ -943,8 +943,9 @@ impl SearchableItem for TerminalView {
}
/// Store matches returned from find_matches somewhere for rendering
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
self.terminal().update(cx, |term, _| term.matches = matches)
fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
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<Self::Match>, cx: &mut ViewContext<Self>) {
fn activate_match(&mut self, index: usize, _: &[Self::Match], cx: &mut ViewContext<Self>) {
self.terminal()
.update(cx, |term, _| term.activate_match(index));
cx.notify();
}
/// 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()
.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<Self::Match>,
matches: &[Self::Match],
cx: &mut ViewContext<Self>,
) -> Option<usize> {
// 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>(
ranges_to_highlight,
&ranges_to_highlight,
|colors| colors.editor_document_highlight_read_background,
cx,
);

View File

@ -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

View File

@ -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<SearchEvent> {
}
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 activate_match(
&mut 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 activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
fn match_index_for_direction(
&mut self,
matches: &Vec<Self::Match>,
matches: &[Self::Match],
current_index: usize,
direction: Direction,
count: usize,
@ -82,7 +78,7 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
) -> Task<Vec<Self::Match>>;
fn active_match_index(
&mut self,
matches: Vec<Self::Match>,
matches: &[Self::Match],
cx: &mut ViewContext<Self>,
) -> Option<usize>;
}
@ -97,19 +93,19 @@ pub trait SearchableItemHandle: ItemHandle {
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
) -> Subscription;
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 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,
index: usize,
matches: &Vec<Box<dyn Any + Send>>,
cx: &mut WindowContext,
_: any_vec::element::ElementRef<'_, dyn Send>,
_: &SearchQuery,
_: &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(
&self,
matches: &Vec<Box<dyn Any + Send>>,
matches: &AnyVec<dyn Send>,
current_index: usize,
direction: Direction,
count: usize,
@ -119,10 +115,10 @@ pub trait SearchableItemHandle: ItemHandle {
&self,
query: Arc<SearchQuery>,
cx: &mut WindowContext,
) -> Task<Vec<Box<dyn Any + Send>>>;
) -> Task<AnyVec<dyn Send>>;
fn active_match_index(
&self,
matches: &Vec<Box<dyn Any + Send>>,
matches: &AnyVec<dyn Send>,
cx: &mut WindowContext,
) -> Option<usize>;
}
@ -151,80 +147,78 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
fn clear_matches(&self, cx: &mut WindowContext) {
self.update(cx, |this, cx| this.clear_matches(cx));
}
fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.update_matches(matches, cx));
fn update_matches(&self, matches: &AnyVec<dyn Send>, 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<Box<dyn Any + Send>>,
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<dyn Send>, 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<Box<dyn Any + Send>>, cx: &mut WindowContext) {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.select_matches(matches, cx));
fn select_matches(&self, matches: &AnyVec<dyn Send>, 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<Box<dyn Any + Send>>,
matches: &AnyVec<dyn Send>,
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<SearchQuery>,
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));
cx.spawn(|_| async {
let matches = matches.await;
matches
.into_iter()
.map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
.collect()
let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
{
let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
for mat in matches {
any_matches.push(mat);
}
}
any_matches
})
}
fn active_match_index(
&self,
matches: &Vec<Box<dyn Any + Send>>,
matches: &AnyVec<dyn Send>,
cx: &mut WindowContext,
) -> Option<usize> {
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<dyn Any + Send>, 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<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 {
fn from(this: Box<dyn SearchableItemHandle>) -> Self {
this.to_any().clone()