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:
parent
7dbcace839
commit
3a0d3cee87
26 changed files with 532 additions and 379 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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<_>>();
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
],
|
],
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue