Introduce Editor::insert_flaps
and Editor::remove_flaps
(#12096)
This pull request introduces the ability to add flaps, custom foldable regions whose first foldable line can be associated with: - A toggle in the gutter - A trailer showed at the end of the line, before the inline blame information https://github.com/zed-industries/zed/assets/482957/c53a9148-f31a-4743-af64-18afa73c404c To achieve this, we changed `FoldMap::fold` to accept a piece of text to display when the range is folded. We use this capability in flaps to avoid displaying the ellipsis character. We want to use this new API in the assistant to fold context while still giving visual cues as to what that context is. Release Notes: - N/A --------- Co-authored-by: Nathan Sobo <nathan@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
b89f360199
commit
f3710877f1
11 changed files with 868 additions and 239 deletions
|
@ -19,6 +19,7 @@ test-support = [
|
||||||
"gpui/test-support",
|
"gpui/test-support",
|
||||||
"multi_buffer/test-support",
|
"multi_buffer/test-support",
|
||||||
"project/test-support",
|
"project/test-support",
|
||||||
|
"theme/test-support",
|
||||||
"util/test-support",
|
"util/test-support",
|
||||||
"workspace/test-support",
|
"workspace/test-support",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
|
@ -86,6 +87,7 @@ release_channel.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
settings = { workspace = true, features = ["test-support"] }
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
text = { workspace = true, features = ["test-support"] }
|
text = { workspace = true, features = ["test-support"] }
|
||||||
|
theme = { workspace = true, features = ["test-support"] }
|
||||||
tree-sitter-html.workspace = true
|
tree-sitter-html.workspace = true
|
||||||
tree-sitter-rust.workspace = true
|
tree-sitter-rust.workspace = true
|
||||||
tree-sitter-typescript.workspace = true
|
tree-sitter-typescript.workspace = true
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
//! [EditorElement]: crate::element::EditorElement
|
//! [EditorElement]: crate::element::EditorElement
|
||||||
|
|
||||||
mod block_map;
|
mod block_map;
|
||||||
|
mod flap_map;
|
||||||
mod fold_map;
|
mod fold_map;
|
||||||
mod inlay_map;
|
mod inlay_map;
|
||||||
mod tab_map;
|
mod tab_map;
|
||||||
|
@ -28,7 +29,9 @@ use crate::{EditorStyle, RowExt};
|
||||||
pub use block_map::{BlockMap, BlockPoint};
|
pub use block_map::{BlockMap, BlockPoint};
|
||||||
use collections::{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::{
|
||||||
|
AnyElement, Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
|
||||||
|
};
|
||||||
use inlay_map::InlayMap;
|
use inlay_map::InlayMap;
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
|
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
|
||||||
|
@ -42,6 +45,7 @@ use serde::Deserialize;
|
||||||
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||||
use sum_tree::{Bias, TreeMap};
|
use sum_tree::{Bias, TreeMap};
|
||||||
use tab_map::TabMap;
|
use tab_map::TabMap;
|
||||||
|
use ui::WindowContext;
|
||||||
|
|
||||||
use wrap_map::WrapMap;
|
use wrap_map::WrapMap;
|
||||||
|
|
||||||
|
@ -49,10 +53,15 @@ pub use block_map::{
|
||||||
BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
|
BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
|
||||||
BlockProperties, BlockStyle, RenderBlock, TransformBlock,
|
BlockProperties, BlockStyle, RenderBlock, TransformBlock,
|
||||||
};
|
};
|
||||||
|
pub use flap_map::*;
|
||||||
|
|
||||||
use self::block_map::BlockRow;
|
use self::block_map::{BlockRow, BlockSnapshot};
|
||||||
|
use self::fold_map::FoldSnapshot;
|
||||||
pub use self::fold_map::{Fold, FoldId, FoldPoint};
|
pub use self::fold_map::{Fold, FoldId, FoldPoint};
|
||||||
|
use self::inlay_map::InlaySnapshot;
|
||||||
pub use self::inlay_map::{InlayOffset, InlayPoint};
|
pub use self::inlay_map::{InlayOffset, InlayPoint};
|
||||||
|
use self::tab_map::TabSnapshot;
|
||||||
|
use self::wrap_map::WrapSnapshot;
|
||||||
pub(crate) use inlay_map::Inlay;
|
pub(crate) use inlay_map::Inlay;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -61,6 +70,8 @@ pub enum FoldStatus {
|
||||||
Foldable,
|
Foldable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type RenderFoldToggle = Arc<dyn Fn(FoldStatus, &mut WindowContext) -> AnyElement>;
|
||||||
|
|
||||||
const UNNECESSARY_CODE_FADE: f32 = 0.3;
|
const UNNECESSARY_CODE_FADE: f32 = 0.3;
|
||||||
|
|
||||||
pub trait ToDisplayPoint {
|
pub trait ToDisplayPoint {
|
||||||
|
@ -92,6 +103,8 @@ pub struct DisplayMap {
|
||||||
text_highlights: TextHighlights,
|
text_highlights: TextHighlights,
|
||||||
/// Regions of inlays that should be highlighted.
|
/// Regions of inlays that should be highlighted.
|
||||||
inlay_highlights: InlayHighlights,
|
inlay_highlights: InlayHighlights,
|
||||||
|
/// A container for explicitly foldable ranges, which supersede indentation based fold range suggestions.
|
||||||
|
flap_map: FlapMap,
|
||||||
pub clip_at_line_ends: bool,
|
pub clip_at_line_ends: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +126,9 @@ impl DisplayMap {
|
||||||
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
|
||||||
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
|
||||||
let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
|
let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height);
|
||||||
|
let flap_map = FlapMap::default();
|
||||||
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
DisplayMap {
|
DisplayMap {
|
||||||
buffer,
|
buffer,
|
||||||
buffer_subscription,
|
buffer_subscription,
|
||||||
|
@ -122,6 +137,7 @@ impl DisplayMap {
|
||||||
tab_map,
|
tab_map,
|
||||||
wrap_map,
|
wrap_map,
|
||||||
block_map,
|
block_map,
|
||||||
|
flap_map,
|
||||||
text_highlights: Default::default(),
|
text_highlights: Default::default(),
|
||||||
inlay_highlights: Default::default(),
|
inlay_highlights: Default::default(),
|
||||||
clip_at_line_ends: false,
|
clip_at_line_ends: false,
|
||||||
|
@ -147,6 +163,7 @@ impl DisplayMap {
|
||||||
tab_snapshot,
|
tab_snapshot,
|
||||||
wrap_snapshot,
|
wrap_snapshot,
|
||||||
block_snapshot,
|
block_snapshot,
|
||||||
|
flap_snapshot: self.flap_map.snapshot(),
|
||||||
text_highlights: self.text_highlights.clone(),
|
text_highlights: self.text_highlights.clone(),
|
||||||
inlay_highlights: self.inlay_highlights.clone(),
|
inlay_highlights: self.inlay_highlights.clone(),
|
||||||
clip_at_line_ends: self.clip_at_line_ends,
|
clip_at_line_ends: self.clip_at_line_ends,
|
||||||
|
@ -157,14 +174,14 @@ impl DisplayMap {
|
||||||
self.fold(
|
self.fold(
|
||||||
other
|
other
|
||||||
.folds_in_range(0..other.buffer_snapshot.len())
|
.folds_in_range(0..other.buffer_snapshot.len())
|
||||||
.map(|fold| fold.range.to_offset(&other.buffer_snapshot)),
|
.map(|fold| (fold.range.to_offset(&other.buffer_snapshot), fold.text)),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fold<T: ToOffset>(
|
pub fn fold<T: ToOffset>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ranges: impl IntoIterator<Item = Range<T>>,
|
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
|
@ -209,6 +226,24 @@ impl DisplayMap {
|
||||||
self.block_map.read(snapshot, edits);
|
self.block_map.read(snapshot, edits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_flaps(
|
||||||
|
&mut self,
|
||||||
|
flaps: impl IntoIterator<Item = Flap>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Vec<FlapId> {
|
||||||
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
|
self.flap_map.insert(flaps, &snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_flaps(
|
||||||
|
&mut self,
|
||||||
|
flap_ids: impl IntoIterator<Item = FlapId>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
|
self.flap_map.remove(flap_ids, &snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn insert_blocks(
|
pub fn insert_blocks(
|
||||||
&mut self,
|
&mut self,
|
||||||
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
||||||
|
@ -367,11 +402,12 @@ pub struct HighlightedChunk<'a> {
|
||||||
#[derive(Clone)]
|
#[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: FoldSnapshot,
|
||||||
inlay_snapshot: inlay_map::InlaySnapshot,
|
pub flap_snapshot: FlapSnapshot,
|
||||||
tab_snapshot: tab_map::TabSnapshot,
|
inlay_snapshot: InlaySnapshot,
|
||||||
wrap_snapshot: wrap_map::WrapSnapshot,
|
tab_snapshot: TabSnapshot,
|
||||||
block_snapshot: block_map::BlockSnapshot,
|
wrap_snapshot: WrapSnapshot,
|
||||||
|
block_snapshot: BlockSnapshot,
|
||||||
text_highlights: TextHighlights,
|
text_highlights: TextHighlights,
|
||||||
inlay_highlights: InlayHighlights,
|
inlay_highlights: InlayHighlights,
|
||||||
clip_at_line_ends: bool,
|
clip_at_line_ends: bool,
|
||||||
|
@ -833,17 +869,7 @@ impl DisplaySnapshot {
|
||||||
DisplayRow(self.block_snapshot.longest_row())
|
DisplayRow(self.block_snapshot.longest_row())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fold_for_line(&self, buffer_row: MultiBufferRow) -> Option<FoldStatus> {
|
pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
|
||||||
if self.is_line_folded(buffer_row) {
|
|
||||||
Some(FoldStatus::Folded)
|
|
||||||
} else if self.is_foldable(buffer_row) {
|
|
||||||
Some(FoldStatus::Foldable)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_foldable(&self, buffer_row: MultiBufferRow) -> bool {
|
|
||||||
let max_row = self.buffer_snapshot.max_buffer_row();
|
let max_row = self.buffer_snapshot.max_buffer_row();
|
||||||
if buffer_row >= max_row {
|
if buffer_row >= max_row {
|
||||||
return false;
|
return false;
|
||||||
|
@ -867,9 +893,17 @@ impl DisplaySnapshot {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn foldable_range(&self, buffer_row: MultiBufferRow) -> Option<Range<Point>> {
|
pub fn foldable_range(
|
||||||
|
&self,
|
||||||
|
buffer_row: MultiBufferRow,
|
||||||
|
) -> Option<(Range<Point>, &'static str)> {
|
||||||
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
|
||||||
if self.is_foldable(MultiBufferRow(start.row))
|
if let Some(flap) = self
|
||||||
|
.flap_snapshot
|
||||||
|
.query_row(buffer_row, &self.buffer_snapshot)
|
||||||
|
{
|
||||||
|
Some((flap.range.to_point(&self.buffer_snapshot), ""))
|
||||||
|
} else if self.starts_indent(MultiBufferRow(start.row))
|
||||||
&& !self.is_line_folded(MultiBufferRow(start.row))
|
&& !self.is_line_folded(MultiBufferRow(start.row))
|
||||||
{
|
{
|
||||||
let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row);
|
let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row);
|
||||||
|
@ -888,7 +922,7 @@ impl DisplaySnapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let end = end.unwrap_or(max_point);
|
let end = end.unwrap_or(max_point);
|
||||||
Some(start..end)
|
Some((start..end, "⋯"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1165,7 +1199,7 @@ pub mod tests {
|
||||||
} else {
|
} else {
|
||||||
log::info!("folding ranges: {:?}", ranges);
|
log::info!("folding ranges: {:?}", ranges);
|
||||||
map.update(cx, |map, cx| {
|
map.update(cx, |map, cx| {
|
||||||
map.fold(ranges, cx);
|
map.fold(ranges.into_iter().map(|range| (range, "⋯")), cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1513,7 +1547,10 @@ pub mod tests {
|
||||||
|
|
||||||
map.update(cx, |map, cx| {
|
map.update(cx, |map, cx| {
|
||||||
map.fold(
|
map.fold(
|
||||||
vec![MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2)],
|
vec![(
|
||||||
|
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
||||||
|
"⋯",
|
||||||
|
)],
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -1595,7 +1632,10 @@ pub mod tests {
|
||||||
|
|
||||||
map.update(cx, |map, cx| {
|
map.update(cx, |map, cx| {
|
||||||
map.fold(
|
map.fold(
|
||||||
vec![MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2)],
|
vec![(
|
||||||
|
MultiBufferPoint::new(0, 6)..MultiBufferPoint::new(3, 2),
|
||||||
|
"⋯",
|
||||||
|
)],
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -1754,6 +1794,33 @@ pub mod tests {
|
||||||
assert("aˇαˇ", cx);
|
assert("aˇαˇ", cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_flaps(cx: &mut gpui::AppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let text = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll";
|
||||||
|
let buffer = MultiBuffer::build_simple(text, cx);
|
||||||
|
let font_size = px(14.0);
|
||||||
|
cx.new_model(|cx| {
|
||||||
|
let mut map =
|
||||||
|
DisplayMap::new(buffer.clone(), font("Helvetica"), font_size, None, 1, 1, cx);
|
||||||
|
let snapshot = map.buffer.read(cx).snapshot(cx);
|
||||||
|
let range =
|
||||||
|
snapshot.anchor_before(Point::new(2, 0))..snapshot.anchor_after(Point::new(3, 3));
|
||||||
|
|
||||||
|
map.flap_map.insert(
|
||||||
|
[Flap::new(
|
||||||
|
range,
|
||||||
|
|_row, _status, _toggle, _cx| div(),
|
||||||
|
|_row, _status, _cx| div(),
|
||||||
|
)],
|
||||||
|
&map.buffer.read(cx).snapshot(cx),
|
||||||
|
);
|
||||||
|
|
||||||
|
map
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
|
fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
292
crates/editor/src/display_map/flap_map.rs
Normal file
292
crates/editor/src/display_map/flap_map.rs
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{AnyElement, IntoElement};
|
||||||
|
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
|
||||||
|
use std::{cmp::Ordering, ops::Range, sync::Arc};
|
||||||
|
use sum_tree::{Bias, SeekTarget, SumTree};
|
||||||
|
use text::Point;
|
||||||
|
use ui::WindowContext;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct FlapId(usize);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FlapMap {
|
||||||
|
snapshot: FlapSnapshot,
|
||||||
|
next_id: FlapId,
|
||||||
|
id_to_range: HashMap<FlapId, Range<Anchor>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct FlapSnapshot {
|
||||||
|
flaps: SumTree<FlapItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlapSnapshot {
|
||||||
|
/// Returns the first Flap starting on the specified buffer row.
|
||||||
|
pub fn query_row<'a>(
|
||||||
|
&'a self,
|
||||||
|
row: MultiBufferRow,
|
||||||
|
snapshot: &'a MultiBufferSnapshot,
|
||||||
|
) -> Option<&'a Flap> {
|
||||||
|
let start = snapshot.anchor_before(Point::new(row.0, 0));
|
||||||
|
let mut cursor = self.flaps.cursor::<ItemSummary>();
|
||||||
|
cursor.seek(&start, Bias::Left, snapshot);
|
||||||
|
while let Some(item) = cursor.item() {
|
||||||
|
match Ord::cmp(&item.flap.range.start.to_point(snapshot).row, &row.0) {
|
||||||
|
Ordering::Less => cursor.next(snapshot),
|
||||||
|
Ordering::Equal => return Some(&item.flap),
|
||||||
|
Ordering::Greater => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flap_items_with_offsets(
|
||||||
|
&self,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Vec<(FlapId, Range<Point>)> {
|
||||||
|
let mut cursor = self.flaps.cursor::<ItemSummary>();
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
cursor.next(snapshot);
|
||||||
|
while let Some(item) = cursor.item() {
|
||||||
|
let start_point = item.flap.range.start.to_point(snapshot);
|
||||||
|
let end_point = item.flap.range.end.to_point(snapshot);
|
||||||
|
results.push((item.id, start_point..end_point));
|
||||||
|
cursor.next(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderToggleFn = Arc<
|
||||||
|
dyn Send
|
||||||
|
+ Sync
|
||||||
|
+ Fn(
|
||||||
|
MultiBufferRow,
|
||||||
|
bool,
|
||||||
|
Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
|
||||||
|
&mut WindowContext,
|
||||||
|
) -> AnyElement,
|
||||||
|
>;
|
||||||
|
type RenderTrailerFn =
|
||||||
|
Arc<dyn Send + Sync + Fn(MultiBufferRow, bool, &mut WindowContext) -> AnyElement>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Flap {
|
||||||
|
pub range: Range<Anchor>,
|
||||||
|
pub render_toggle: RenderToggleFn,
|
||||||
|
pub render_trailer: RenderTrailerFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Flap {
|
||||||
|
pub fn new<RenderToggle, ToggleElement, RenderTrailer, TrailerElement>(
|
||||||
|
range: Range<Anchor>,
|
||||||
|
render_toggle: RenderToggle,
|
||||||
|
render_trailer: RenderTrailer,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
RenderToggle: 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ Fn(
|
||||||
|
MultiBufferRow,
|
||||||
|
bool,
|
||||||
|
Arc<dyn Send + Sync + Fn(bool, &mut WindowContext)>,
|
||||||
|
&mut WindowContext,
|
||||||
|
) -> ToggleElement
|
||||||
|
+ 'static,
|
||||||
|
ToggleElement: IntoElement,
|
||||||
|
RenderTrailer: 'static
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ Fn(MultiBufferRow, bool, &mut WindowContext) -> TrailerElement
|
||||||
|
+ 'static,
|
||||||
|
TrailerElement: IntoElement,
|
||||||
|
{
|
||||||
|
Flap {
|
||||||
|
range,
|
||||||
|
render_toggle: Arc::new(move |row, folded, toggle, cx| {
|
||||||
|
render_toggle(row, folded, toggle, cx).into_any_element()
|
||||||
|
}),
|
||||||
|
render_trailer: Arc::new(move |row, folded, cx| {
|
||||||
|
render_trailer(row, folded, cx).into_any_element()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Flap {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Flap").field("range", &self.range).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct FlapItem {
|
||||||
|
id: FlapId,
|
||||||
|
flap: Flap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlapMap {
|
||||||
|
pub fn snapshot(&self) -> FlapSnapshot {
|
||||||
|
self.snapshot.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
flaps: impl IntoIterator<Item = Flap>,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Vec<FlapId> {
|
||||||
|
let mut new_ids = Vec::new();
|
||||||
|
self.snapshot.flaps = {
|
||||||
|
let mut new_flaps = SumTree::new();
|
||||||
|
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
|
||||||
|
for flap in flaps {
|
||||||
|
new_flaps.append(cursor.slice(&flap.range, Bias::Left, snapshot), snapshot);
|
||||||
|
|
||||||
|
let id = self.next_id;
|
||||||
|
self.next_id.0 += 1;
|
||||||
|
self.id_to_range.insert(id, flap.range.clone());
|
||||||
|
new_flaps.push(FlapItem { flap, id }, snapshot);
|
||||||
|
new_ids.push(id);
|
||||||
|
}
|
||||||
|
new_flaps.append(cursor.suffix(snapshot), snapshot);
|
||||||
|
new_flaps
|
||||||
|
};
|
||||||
|
new_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(
|
||||||
|
&mut self,
|
||||||
|
ids: impl IntoIterator<Item = FlapId>,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
) {
|
||||||
|
let mut removals = Vec::new();
|
||||||
|
for id in ids {
|
||||||
|
if let Some(range) = self.id_to_range.remove(&id) {
|
||||||
|
removals.push((id, range.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removals.sort_unstable_by(|(a_id, a_range), (b_id, b_range)| {
|
||||||
|
AnchorRangeExt::cmp(a_range, b_range, snapshot).then(b_id.cmp(&a_id))
|
||||||
|
});
|
||||||
|
|
||||||
|
self.snapshot.flaps = {
|
||||||
|
let mut new_flaps = SumTree::new();
|
||||||
|
let mut cursor = self.snapshot.flaps.cursor::<ItemSummary>();
|
||||||
|
|
||||||
|
for (id, range) in removals {
|
||||||
|
new_flaps.append(cursor.slice(&range, Bias::Left, snapshot), snapshot);
|
||||||
|
while let Some(item) = cursor.item() {
|
||||||
|
cursor.next(snapshot);
|
||||||
|
if item.id == id {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
new_flaps.push(item.clone(), snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_flaps.append(cursor.suffix(snapshot), snapshot);
|
||||||
|
new_flaps
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ItemSummary {
|
||||||
|
range: Range<Anchor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ItemSummary {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
range: Anchor::min()..Anchor::min(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Summary for ItemSummary {
|
||||||
|
type Context = MultiBufferSnapshot;
|
||||||
|
|
||||||
|
fn add_summary(&mut self, other: &Self, _snapshot: &MultiBufferSnapshot) {
|
||||||
|
self.range = other.range.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sum_tree::Item for FlapItem {
|
||||||
|
type Summary = ItemSummary;
|
||||||
|
|
||||||
|
fn summary(&self) -> Self::Summary {
|
||||||
|
ItemSummary {
|
||||||
|
range: self.flap.range.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements `SeekTarget` for `Range<Anchor>` to enable seeking within a `SumTree` of `FlapItem`s.
|
||||||
|
impl SeekTarget<'_, ItemSummary, ItemSummary> for Range<Anchor> {
|
||||||
|
fn cmp(&self, cursor_location: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
|
||||||
|
AnchorRangeExt::cmp(self, &cursor_location.range, snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeekTarget<'_, ItemSummary, ItemSummary> for Anchor {
|
||||||
|
fn cmp(&self, other: &ItemSummary, snapshot: &MultiBufferSnapshot) -> Ordering {
|
||||||
|
self.cmp(&other.range.start, snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use gpui::{div, AppContext};
|
||||||
|
use multi_buffer::MultiBuffer;
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_insert_and_remove_flaps(cx: &mut AppContext) {
|
||||||
|
let text = "line1\nline2\nline3\nline4\nline5";
|
||||||
|
let buffer = MultiBuffer::build_simple(text, cx);
|
||||||
|
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||||
|
let mut flap_map = FlapMap::default();
|
||||||
|
|
||||||
|
// Insert flaps
|
||||||
|
let flaps = [
|
||||||
|
Flap::new(
|
||||||
|
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
||||||
|
|_row, _folded, _toggle, _cx| div(),
|
||||||
|
|_row, _folded, _cx| div(),
|
||||||
|
),
|
||||||
|
Flap::new(
|
||||||
|
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
||||||
|
|_row, _folded, _toggle, _cx| div(),
|
||||||
|
|_row, _folded, _cx| div(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let flap_ids = flap_map.insert(flaps, &snapshot);
|
||||||
|
assert_eq!(flap_ids.len(), 2);
|
||||||
|
|
||||||
|
// Verify flaps are inserted
|
||||||
|
let flap_snapshot = flap_map.snapshot();
|
||||||
|
assert!(flap_snapshot
|
||||||
|
.query_row(MultiBufferRow(1), &snapshot)
|
||||||
|
.is_some());
|
||||||
|
assert!(flap_snapshot
|
||||||
|
.query_row(MultiBufferRow(3), &snapshot)
|
||||||
|
.is_some());
|
||||||
|
|
||||||
|
// Remove flaps
|
||||||
|
flap_map.remove(flap_ids, &snapshot);
|
||||||
|
|
||||||
|
// Verify flaps are removed
|
||||||
|
let flap_snapshot = flap_map.snapshot();
|
||||||
|
assert!(flap_snapshot
|
||||||
|
.query_row(MultiBufferRow(1), &snapshot)
|
||||||
|
.is_none());
|
||||||
|
assert!(flap_snapshot
|
||||||
|
.query_row(MultiBufferRow(3), &snapshot)
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,12 +75,12 @@ pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
|
||||||
impl<'a> FoldMapWriter<'a> {
|
impl<'a> FoldMapWriter<'a> {
|
||||||
pub(crate) fn fold<T: ToOffset>(
|
pub(crate) fn fold<T: ToOffset>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ranges: impl IntoIterator<Item = Range<T>>,
|
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
|
||||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
let mut folds = Vec::new();
|
let mut folds = Vec::new();
|
||||||
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
||||||
for range in ranges.into_iter() {
|
for (range, fold_text) in ranges.into_iter() {
|
||||||
let buffer = &snapshot.buffer;
|
let buffer = &snapshot.buffer;
|
||||||
let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer);
|
let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer);
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ impl<'a> FoldMapWriter<'a> {
|
||||||
folds.push(Fold {
|
folds.push(Fold {
|
||||||
id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
|
id: FoldId(post_inc(&mut self.0.next_fold_id.0)),
|
||||||
range: fold_range,
|
range: fold_range,
|
||||||
|
text: fold_text,
|
||||||
});
|
});
|
||||||
|
|
||||||
let inlay_range =
|
let inlay_range =
|
||||||
|
@ -324,11 +325,14 @@ impl FoldMap {
|
||||||
let mut folds = iter::from_fn({
|
let mut folds = iter::from_fn({
|
||||||
let inlay_snapshot = &inlay_snapshot;
|
let inlay_snapshot = &inlay_snapshot;
|
||||||
move || {
|
move || {
|
||||||
let item = folds_cursor.item().map(|f| {
|
let item = folds_cursor.item().map(|fold| {
|
||||||
let buffer_start = f.range.start.to_offset(&inlay_snapshot.buffer);
|
let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer);
|
||||||
let buffer_end = f.range.end.to_offset(&inlay_snapshot.buffer);
|
let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer);
|
||||||
inlay_snapshot.to_inlay_offset(buffer_start)
|
(
|
||||||
..inlay_snapshot.to_inlay_offset(buffer_end)
|
inlay_snapshot.to_inlay_offset(buffer_start)
|
||||||
|
..inlay_snapshot.to_inlay_offset(buffer_end),
|
||||||
|
fold.text,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
folds_cursor.next(&inlay_snapshot.buffer);
|
folds_cursor.next(&inlay_snapshot.buffer);
|
||||||
item
|
item
|
||||||
|
@ -336,25 +340,27 @@ impl FoldMap {
|
||||||
})
|
})
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
while folds.peek().map_or(false, |fold| fold.start < edit.new.end) {
|
while folds
|
||||||
let mut fold = folds.next().unwrap();
|
.peek()
|
||||||
|
.map_or(false, |(fold_range, _)| fold_range.start < edit.new.end)
|
||||||
|
{
|
||||||
|
let (mut fold_range, fold_text) = folds.next().unwrap();
|
||||||
let sum = new_transforms.summary();
|
let sum = new_transforms.summary();
|
||||||
|
|
||||||
assert!(fold.start.0 >= sum.input.len);
|
assert!(fold_range.start.0 >= sum.input.len);
|
||||||
|
|
||||||
while folds
|
while folds.peek().map_or(false, |(next_fold_range, _)| {
|
||||||
.peek()
|
next_fold_range.start <= fold_range.end
|
||||||
.map_or(false, |next_fold| next_fold.start <= fold.end)
|
}) {
|
||||||
{
|
let (next_fold_range, _) = folds.next().unwrap();
|
||||||
let next_fold = folds.next().unwrap();
|
if next_fold_range.end > fold_range.end {
|
||||||
if next_fold.end > fold.end {
|
fold_range.end = next_fold_range.end;
|
||||||
fold.end = next_fold.end;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fold.start.0 > sum.input.len {
|
if fold_range.start.0 > sum.input.len {
|
||||||
let text_summary = inlay_snapshot
|
let text_summary = inlay_snapshot
|
||||||
.text_summary_for_range(InlayOffset(sum.input.len)..fold.start);
|
.text_summary_for_range(InlayOffset(sum.input.len)..fold_range.start);
|
||||||
new_transforms.push(
|
new_transforms.push(
|
||||||
Transform {
|
Transform {
|
||||||
summary: TransformSummary {
|
summary: TransformSummary {
|
||||||
|
@ -367,16 +373,15 @@ impl FoldMap {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if fold.end > fold.start {
|
if fold_range.end > fold_range.start {
|
||||||
let output_text = "⋯";
|
|
||||||
new_transforms.push(
|
new_transforms.push(
|
||||||
Transform {
|
Transform {
|
||||||
summary: TransformSummary {
|
summary: TransformSummary {
|
||||||
output: TextSummary::from(output_text),
|
output: TextSummary::from(fold_text),
|
||||||
input: inlay_snapshot
|
input: inlay_snapshot
|
||||||
.text_summary_for_range(fold.start..fold.end),
|
.text_summary_for_range(fold_range.start..fold_range.end),
|
||||||
},
|
},
|
||||||
output_text: Some(output_text),
|
output_text: Some(fold_text),
|
||||||
},
|
},
|
||||||
&(),
|
&(),
|
||||||
);
|
);
|
||||||
|
@ -853,6 +858,7 @@ impl Into<ElementId> for FoldId {
|
||||||
pub struct Fold {
|
pub struct Fold {
|
||||||
pub id: FoldId,
|
pub id: FoldId,
|
||||||
pub range: FoldRange,
|
pub range: FoldRange,
|
||||||
|
pub text: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -948,7 +954,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
|
||||||
|
|
||||||
impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
|
impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
|
||||||
fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
|
fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
|
||||||
self.0.cmp(&other.0, buffer)
|
AnchorRangeExt::cmp(&self.0, &other.0, buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1159,8 +1165,8 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot, vec![]);
|
||||||
let (snapshot2, edits) = writer.fold(vec![
|
let (snapshot2, edits) = writer.fold(vec![
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
||||||
Point::new(2, 4)..Point::new(4, 1),
|
(Point::new(2, 4)..Point::new(4, 1), "⋯"),
|
||||||
]);
|
]);
|
||||||
assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
|
assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1239,19 +1245,19 @@ mod tests {
|
||||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![5..8]);
|
writer.fold(vec![(5..8, "⋯")]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "abcde⋯ijkl");
|
assert_eq!(snapshot.text(), "abcde⋯ijkl");
|
||||||
|
|
||||||
// Create an fold adjacent to the start of the first fold.
|
// Create an fold adjacent to the start of the first fold.
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![0..1, 2..5]);
|
writer.fold(vec![(0..1, "⋯"), (2..5, "⋯")]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "⋯b⋯ijkl");
|
assert_eq!(snapshot.text(), "⋯b⋯ijkl");
|
||||||
|
|
||||||
// Create an fold adjacent to the end of the first fold.
|
// Create an fold adjacent to the end of the first fold.
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![11..11, 8..10]);
|
writer.fold(vec![(11..11, "⋯"), (8..10, "⋯")]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "⋯b⋯kl");
|
assert_eq!(snapshot.text(), "⋯b⋯kl");
|
||||||
}
|
}
|
||||||
|
@ -1261,7 +1267,7 @@ mod tests {
|
||||||
|
|
||||||
// Create two adjacent folds.
|
// Create two adjacent folds.
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![0..2, 2..5]);
|
writer.fold(vec![(0..2, "⋯"), (2..5, "⋯")]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||||
assert_eq!(snapshot.text(), "⋯fghijkl");
|
assert_eq!(snapshot.text(), "⋯fghijkl");
|
||||||
|
|
||||||
|
@ -1285,10 +1291,10 @@ mod tests {
|
||||||
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
let mut map = FoldMap::new(inlay_snapshot.clone()).0;
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
||||||
Point::new(0, 4)..Point::new(1, 0),
|
(Point::new(0, 4)..Point::new(1, 0), "⋯"),
|
||||||
Point::new(1, 2)..Point::new(3, 2),
|
(Point::new(1, 2)..Point::new(3, 2), "⋯"),
|
||||||
Point::new(3, 1)..Point::new(4, 1),
|
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
||||||
]);
|
]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||||
assert_eq!(snapshot.text(), "aa⋯eeeee");
|
assert_eq!(snapshot.text(), "aa⋯eeeee");
|
||||||
|
@ -1305,8 +1311,8 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
||||||
Point::new(3, 1)..Point::new(4, 1),
|
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
||||||
]);
|
]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
|
assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee");
|
||||||
|
@ -1330,10 +1336,10 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
||||||
Point::new(0, 4)..Point::new(1, 0),
|
(Point::new(0, 4)..Point::new(1, 0), "⋯"),
|
||||||
Point::new(1, 2)..Point::new(3, 2),
|
(Point::new(1, 2)..Point::new(3, 2), "⋯"),
|
||||||
Point::new(3, 1)..Point::new(4, 1),
|
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
||||||
]);
|
]);
|
||||||
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||||
let fold_ranges = snapshot
|
let fold_ranges = snapshot
|
||||||
|
@ -1408,10 +1414,10 @@ mod tests {
|
||||||
snapshot_edits.push((snapshot.clone(), edits));
|
snapshot_edits.push((snapshot.clone(), edits));
|
||||||
|
|
||||||
let mut expected_text: String = inlay_snapshot.text().to_string();
|
let mut expected_text: String = inlay_snapshot.text().to_string();
|
||||||
for fold_range in map.merged_fold_ranges().into_iter().rev() {
|
for (fold_range, fold_text) in map.merged_folds().into_iter().rev() {
|
||||||
let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
|
let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start);
|
||||||
let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
|
let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end);
|
||||||
expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯");
|
expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, fold_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(snapshot.text(), expected_text);
|
assert_eq!(snapshot.text(), expected_text);
|
||||||
|
@ -1423,7 +1429,7 @@ mod tests {
|
||||||
|
|
||||||
let mut prev_row = 0;
|
let mut prev_row = 0;
|
||||||
let mut expected_buffer_rows = Vec::new();
|
let mut expected_buffer_rows = Vec::new();
|
||||||
for fold_range in map.merged_fold_ranges().into_iter() {
|
for (fold_range, _fold_text) in map.merged_folds().into_iter() {
|
||||||
let fold_start = inlay_snapshot
|
let fold_start = inlay_snapshot
|
||||||
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
|
.to_point(inlay_snapshot.to_inlay_offset(fold_range.start))
|
||||||
.row();
|
.row();
|
||||||
|
@ -1535,11 +1541,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let folded_buffer_rows = map
|
let folded_buffer_rows = map
|
||||||
.merged_fold_ranges()
|
.merged_folds()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|range| {
|
.flat_map(|(fold_range, _)| {
|
||||||
let start_row = range.start.to_point(&buffer_snapshot).row;
|
let start_row = fold_range.start.to_point(&buffer_snapshot).row;
|
||||||
let end = range.end.to_point(&buffer_snapshot);
|
let end = fold_range.end.to_point(&buffer_snapshot);
|
||||||
if end.column == 0 {
|
if end.column == 0 {
|
||||||
start_row..end.row
|
start_row..end.row
|
||||||
} else {
|
} else {
|
||||||
|
@ -1634,8 +1640,8 @@ mod tests {
|
||||||
|
|
||||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||||
writer.fold(vec![
|
writer.fold(vec![
|
||||||
Point::new(0, 2)..Point::new(2, 2),
|
(Point::new(0, 2)..Point::new(2, 2), "⋯"),
|
||||||
Point::new(3, 1)..Point::new(4, 1),
|
(Point::new(3, 1)..Point::new(4, 1), "⋯"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
let (snapshot, _) = map.read(inlay_snapshot, vec![]);
|
||||||
|
@ -1653,34 +1659,39 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FoldMap {
|
impl FoldMap {
|
||||||
fn merged_fold_ranges(&self) -> Vec<Range<usize>> {
|
fn merged_folds(&self) -> Vec<(Range<usize>, &'static str)> {
|
||||||
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
let inlay_snapshot = self.snapshot.inlay_snapshot.clone();
|
||||||
let buffer = &inlay_snapshot.buffer;
|
let buffer = &inlay_snapshot.buffer;
|
||||||
let mut folds = self.snapshot.folds.items(buffer);
|
let mut folds = self.snapshot.folds.items(buffer);
|
||||||
// Ensure sorting doesn't change how folds get merged and displayed.
|
// Ensure sorting doesn't change how folds get merged and displayed.
|
||||||
folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
folds.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
let mut fold_ranges = folds
|
let mut folds = folds
|
||||||
.iter()
|
.iter()
|
||||||
.map(|fold| fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer))
|
.map(|fold| {
|
||||||
|
(
|
||||||
|
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer),
|
||||||
|
fold.text,
|
||||||
|
)
|
||||||
|
})
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
let mut merged_ranges = Vec::new();
|
let mut merged_folds = Vec::new();
|
||||||
while let Some(mut fold_range) = fold_ranges.next() {
|
while let Some((mut fold_range, fold_text)) = folds.next() {
|
||||||
while let Some(next_range) = fold_ranges.peek() {
|
while let Some((next_range, _)) = folds.peek() {
|
||||||
if fold_range.end >= next_range.start {
|
if fold_range.end >= next_range.start {
|
||||||
if next_range.end > fold_range.end {
|
if next_range.end > fold_range.end {
|
||||||
fold_range.end = next_range.end;
|
fold_range.end = next_range.end;
|
||||||
}
|
}
|
||||||
fold_ranges.next();
|
folds.next();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fold_range.end > fold_range.start {
|
if fold_range.end > fold_range.start {
|
||||||
merged_ranges.push(fold_range);
|
merged_folds.push((fold_range, fold_text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
merged_ranges
|
merged_folds
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn randomly_mutate(
|
pub fn randomly_mutate(
|
||||||
|
@ -1698,10 +1709,11 @@ mod tests {
|
||||||
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
||||||
to_unfold.push(start..end);
|
to_unfold.push(start..end);
|
||||||
}
|
}
|
||||||
log::info!("unfolding {:?}", to_unfold);
|
let inclusive = rng.gen();
|
||||||
|
log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
|
||||||
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
||||||
snapshot_edits.push((snapshot, edits));
|
snapshot_edits.push((snapshot, edits));
|
||||||
let (snapshot, edits) = writer.fold(to_unfold);
|
let (snapshot, edits) = writer.unfold(to_unfold, inclusive);
|
||||||
snapshot_edits.push((snapshot, edits));
|
snapshot_edits.push((snapshot, edits));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -1711,7 +1723,8 @@ mod tests {
|
||||||
for _ in 0..rng.gen_range(1..=2) {
|
for _ in 0..rng.gen_range(1..=2) {
|
||||||
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right);
|
||||||
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
let start = buffer.clip_offset(rng.gen_range(0..=end), Left);
|
||||||
to_fold.push(start..end);
|
let text = if rng.gen() { "⋯" } else { "" };
|
||||||
|
to_fold.push((start..end, text));
|
||||||
}
|
}
|
||||||
log::info!("folding {:?}", to_fold);
|
log::info!("folding {:?}", to_fold);
|
||||||
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
||||||
|
|
|
@ -523,6 +523,7 @@ pub struct EditorSnapshot {
|
||||||
scroll_anchor: ScrollAnchor,
|
scroll_anchor: ScrollAnchor,
|
||||||
ongoing_scroll: OngoingScroll,
|
ongoing_scroll: OngoingScroll,
|
||||||
current_line_highlight: CurrentLineHighlight,
|
current_line_highlight: CurrentLineHighlight,
|
||||||
|
gutter_hovered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
|
const GIT_BLAME_GUTTER_WIDTH_CHARS: f32 = 53.;
|
||||||
|
@ -1886,6 +1887,7 @@ impl Editor {
|
||||||
placeholder_text: self.placeholder_text.clone(),
|
placeholder_text: self.placeholder_text.clone(),
|
||||||
is_focused: self.focus_handle.is_focused(cx),
|
is_focused: self.focus_handle.is_focused(cx),
|
||||||
current_line_highlight: self.current_line_highlight,
|
current_line_highlight: self.current_line_highlight,
|
||||||
|
gutter_hovered: self.gutter_hovered,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4639,44 +4641,6 @@ impl Editor {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_fold_indicators(
|
|
||||||
&mut self,
|
|
||||||
fold_data: Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
|
|
||||||
_style: &EditorStyle,
|
|
||||||
gutter_hovered: bool,
|
|
||||||
_line_height: Pixels,
|
|
||||||
_gutter_margin: Pixels,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Vec<Option<AnyElement>> {
|
|
||||||
fold_data
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(ix, fold_data)| {
|
|
||||||
fold_data
|
|
||||||
.map(|(fold_status, buffer_row, active)| {
|
|
||||||
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
|
|
||||||
IconButton::new(ix, ui::IconName::ChevronDown)
|
|
||||||
.on_click(cx.listener(move |this, _e, cx| match fold_status {
|
|
||||||
FoldStatus::Folded => {
|
|
||||||
this.unfold_at(&UnfoldAt { buffer_row }, cx);
|
|
||||||
}
|
|
||||||
FoldStatus::Foldable => {
|
|
||||||
this.fold_at(&FoldAt { buffer_row }, cx);
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.icon_color(ui::Color::Muted)
|
|
||||||
.icon_size(ui::IconSize::Small)
|
|
||||||
.selected(fold_status == FoldStatus::Folded)
|
|
||||||
.selected_icon(ui::IconName::ChevronRight)
|
|
||||||
.size(ui::ButtonSize::None)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn context_menu_visible(&self) -> bool {
|
pub fn context_menu_visible(&self) -> bool {
|
||||||
self.context_menu
|
self.context_menu
|
||||||
.read()
|
.read()
|
||||||
|
@ -5830,7 +5794,7 @@ impl Editor {
|
||||||
let mut end = fold.range.end.to_point(&buffer);
|
let mut end = fold.range.end.to_point(&buffer);
|
||||||
start.row -= row_delta;
|
start.row -= row_delta;
|
||||||
end.row -= row_delta;
|
end.row -= row_delta;
|
||||||
refold_ranges.push(start..end);
|
refold_ranges.push((start..end, fold.text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5924,7 +5888,7 @@ impl Editor {
|
||||||
let mut end = fold.range.end.to_point(&buffer);
|
let mut end = fold.range.end.to_point(&buffer);
|
||||||
start.row += row_delta;
|
start.row += row_delta;
|
||||||
end.row += row_delta;
|
end.row += row_delta;
|
||||||
refold_ranges.push(start..end);
|
refold_ranges.push((start..end, fold.text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9282,11 +9246,11 @@ impl Editor {
|
||||||
let buffer_start_row = range.start.row;
|
let buffer_start_row = range.start.row;
|
||||||
|
|
||||||
for row in (0..=range.end.row).rev() {
|
for row in (0..=range.end.row).rev() {
|
||||||
let fold_range = display_map.foldable_range(MultiBufferRow(row));
|
if let Some((foldable_range, fold_text)) =
|
||||||
|
display_map.foldable_range(MultiBufferRow(row))
|
||||||
if let Some(fold_range) = fold_range {
|
{
|
||||||
if fold_range.end.row >= buffer_start_row {
|
if foldable_range.end.row >= buffer_start_row {
|
||||||
fold_ranges.push(fold_range);
|
fold_ranges.push((foldable_range, fold_text));
|
||||||
if row <= range.start.row {
|
if row <= range.start.row {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -9302,14 +9266,14 @@ impl Editor {
|
||||||
let buffer_row = fold_at.buffer_row;
|
let buffer_row = fold_at.buffer_row;
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
|
||||||
if let Some(fold_range) = display_map.foldable_range(buffer_row) {
|
if let Some((fold_range, fold_text)) = display_map.foldable_range(buffer_row) {
|
||||||
let autoscroll = self
|
let autoscroll = self
|
||||||
.selections
|
.selections
|
||||||
.all::<Point>(cx)
|
.all::<Point>(cx)
|
||||||
.iter()
|
.iter()
|
||||||
.any(|selection| fold_range.overlaps(&selection.range()));
|
.any(|selection| fold_range.overlaps(&selection.range()));
|
||||||
|
|
||||||
self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
|
self.fold_ranges([(fold_range, fold_text)], autoscroll, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9363,9 +9327,9 @@ impl Editor {
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.line_len(MultiBufferRow(s.end.row)),
|
.line_len(MultiBufferRow(s.end.row)),
|
||||||
);
|
);
|
||||||
start..end
|
(start..end, "⋯")
|
||||||
} else {
|
} else {
|
||||||
s.start..s.end
|
(s.start..s.end, "⋯")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.fold_ranges(ranges, true, cx);
|
self.fold_ranges(ranges, true, cx);
|
||||||
|
@ -9373,18 +9337,20 @@ impl Editor {
|
||||||
|
|
||||||
pub fn fold_ranges<T: ToOffset + Clone>(
|
pub fn fold_ranges<T: ToOffset + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
ranges: impl IntoIterator<Item = Range<T>>,
|
ranges: impl IntoIterator<Item = (Range<T>, &'static str)>,
|
||||||
auto_scroll: bool,
|
auto_scroll: bool,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
let mut fold_ranges = Vec::new();
|
let mut fold_ranges = Vec::new();
|
||||||
let mut buffers_affected = HashMap::default();
|
let mut buffers_affected = HashMap::default();
|
||||||
let multi_buffer = self.buffer().read(cx);
|
let multi_buffer = self.buffer().read(cx);
|
||||||
for range in ranges {
|
for (fold_range, fold_text) in ranges {
|
||||||
if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
|
if let Some((_, buffer, _)) =
|
||||||
|
multi_buffer.excerpt_containing(fold_range.start.clone(), cx)
|
||||||
|
{
|
||||||
buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
|
buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
|
||||||
};
|
};
|
||||||
fold_ranges.push(range);
|
fold_ranges.push((fold_range, fold_text));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ranges = fold_ranges.into_iter().peekable();
|
let mut ranges = fold_ranges.into_iter().peekable();
|
||||||
|
@ -9500,6 +9466,24 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_flaps(
|
||||||
|
&mut self,
|
||||||
|
flaps: impl IntoIterator<Item = Flap>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Vec<FlapId> {
|
||||||
|
self.display_map
|
||||||
|
.update(cx, |map, cx| map.insert_flaps(flaps, cx))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_flaps(
|
||||||
|
&mut self,
|
||||||
|
ids: impl IntoIterator<Item = FlapId>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.display_map
|
||||||
|
.update(cx, |map, cx| map.remove_flaps(ids, cx));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
|
pub fn longest_row(&self, cx: &mut AppContext) -> DisplayRow {
|
||||||
self.display_map
|
self.display_map
|
||||||
.update(cx, |map, cx| map.snapshot(cx))
|
.update(cx, |map, cx| map.snapshot(cx))
|
||||||
|
@ -11098,6 +11082,76 @@ impl EditorSnapshot {
|
||||||
git_blame_entries_width,
|
git_blame_entries_width,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_fold_toggle(
|
||||||
|
&self,
|
||||||
|
buffer_row: MultiBufferRow,
|
||||||
|
row_contains_cursor: bool,
|
||||||
|
editor: View<Editor>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Option<AnyElement> {
|
||||||
|
let folded = self.is_line_folded(buffer_row);
|
||||||
|
|
||||||
|
if let Some(flap) = self
|
||||||
|
.flap_snapshot
|
||||||
|
.query_row(buffer_row, &self.buffer_snapshot)
|
||||||
|
{
|
||||||
|
let toggle_callback = Arc::new(move |folded, cx: &mut WindowContext| {
|
||||||
|
if folded {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.fold_at(&crate::FoldAt { buffer_row }, cx)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.unfold_at(&crate::UnfoldAt { buffer_row }, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Some((flap.render_toggle)(
|
||||||
|
buffer_row,
|
||||||
|
folded,
|
||||||
|
toggle_callback,
|
||||||
|
cx,
|
||||||
|
))
|
||||||
|
} else if folded
|
||||||
|
|| (self.starts_indent(buffer_row) && (row_contains_cursor || self.gutter_hovered))
|
||||||
|
{
|
||||||
|
Some(
|
||||||
|
IconButton::new(
|
||||||
|
("indent-fold-indicator", buffer_row.0),
|
||||||
|
ui::IconName::ChevronDown,
|
||||||
|
)
|
||||||
|
.on_click(cx.listener_for(&editor, move |this, _e, cx| {
|
||||||
|
if folded {
|
||||||
|
this.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||||
|
} else {
|
||||||
|
this.fold_at(&FoldAt { buffer_row }, cx);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.icon_color(ui::Color::Muted)
|
||||||
|
.icon_size(ui::IconSize::Small)
|
||||||
|
.selected(folded)
|
||||||
|
.selected_icon(ui::IconName::ChevronRight)
|
||||||
|
.size(ui::ButtonSize::None)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_flap_trailer(
|
||||||
|
&self,
|
||||||
|
buffer_row: MultiBufferRow,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Option<AnyElement> {
|
||||||
|
let folded = self.is_line_folded(buffer_row);
|
||||||
|
let flap = self
|
||||||
|
.flap_snapshot
|
||||||
|
.query_row(buffer_row, &self.buffer_snapshot)?;
|
||||||
|
Some((flap.render_trailer)(buffer_row, folded, cx))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for EditorSnapshot {
|
impl Deref for EditorSnapshot {
|
||||||
|
|
|
@ -494,8 +494,8 @@ fn test_clone(cx: &mut TestAppContext) {
|
||||||
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
|
editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
|
||||||
editor.fold_ranges(
|
editor.fold_ranges(
|
||||||
[
|
[
|
||||||
Point::new(1, 0)..Point::new(2, 0),
|
(Point::new(1, 0)..Point::new(2, 0), "⋯"),
|
||||||
Point::new(3, 0)..Point::new(4, 0),
|
(Point::new(3, 0)..Point::new(4, 0), "⋯"),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -903,9 +903,9 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 6)..Point::new(0, 12),
|
(Point::new(0, 6)..Point::new(0, 12), "⋯"),
|
||||||
Point::new(1, 2)..Point::new(1, 4),
|
(Point::new(1, 2)..Point::new(1, 4), "⋯"),
|
||||||
Point::new(2, 4)..Point::new(2, 8),
|
(Point::new(2, 4)..Point::new(2, 8), "⋯"),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -3407,9 +3407,9 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 2)..Point::new(1, 2),
|
(Point::new(0, 2)..Point::new(1, 2), "⋯"),
|
||||||
Point::new(2, 3)..Point::new(4, 1),
|
(Point::new(2, 3)..Point::new(4, 1), "⋯"),
|
||||||
Point::new(7, 0)..Point::new(8, 4),
|
(Point::new(7, 0)..Point::new(8, 4), "⋯"),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -3891,9 +3891,9 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 2)..Point::new(1, 2),
|
(Point::new(0, 2)..Point::new(1, 2), "⋯"),
|
||||||
Point::new(2, 3)..Point::new(4, 1),
|
(Point::new(2, 3)..Point::new(4, 1), "⋯"),
|
||||||
Point::new(7, 0)..Point::new(8, 4),
|
(Point::new(7, 0)..Point::new(8, 4), "⋯"),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -4548,8 +4548,8 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||||
_ = view.update(cx, |view, cx| {
|
_ = view.update(cx, |view, cx| {
|
||||||
view.fold_ranges(
|
view.fold_ranges(
|
||||||
vec![
|
vec![
|
||||||
Point::new(0, 21)..Point::new(0, 24),
|
(Point::new(0, 21)..Point::new(0, 24), "⋯"),
|
||||||
Point::new(3, 20)..Point::new(3, 22),
|
(Point::new(3, 20)..Point::new(3, 22), "⋯"),
|
||||||
],
|
],
|
||||||
true,
|
true,
|
||||||
cx,
|
cx,
|
||||||
|
@ -11448,6 +11448,67 @@ async fn test_multiple_expanded_hunks_merge(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_flap_insertion_and_rendering(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
|
let editor = cx.add_window(|cx| {
|
||||||
|
let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
|
||||||
|
build_editor(buffer, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let render_args = Arc::new(Mutex::new(None));
|
||||||
|
let snapshot = editor
|
||||||
|
.update(cx, |editor, cx| {
|
||||||
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
let range =
|
||||||
|
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
|
||||||
|
|
||||||
|
struct RenderArgs {
|
||||||
|
row: MultiBufferRow,
|
||||||
|
folded: bool,
|
||||||
|
callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let flap = Flap::new(
|
||||||
|
range,
|
||||||
|
{
|
||||||
|
let toggle_callback = render_args.clone();
|
||||||
|
move |row, folded, callback, _cx| {
|
||||||
|
*toggle_callback.lock() = Some(RenderArgs {
|
||||||
|
row,
|
||||||
|
folded,
|
||||||
|
callback,
|
||||||
|
});
|
||||||
|
div()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|_row, _folded, _cx| div(),
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.insert_flaps(Some(flap), cx);
|
||||||
|
let snapshot = editor.snapshot(cx);
|
||||||
|
let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
|
||||||
|
snapshot
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let render_args = render_args.lock().take().unwrap();
|
||||||
|
assert_eq!(render_args.row, MultiBufferRow(1));
|
||||||
|
assert_eq!(render_args.folded, false);
|
||||||
|
assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
|
||||||
|
|
||||||
|
cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
|
||||||
|
.unwrap();
|
||||||
|
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
|
||||||
|
assert!(snapshot.is_line_folded(MultiBufferRow(1)));
|
||||||
|
|
||||||
|
cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
|
||||||
|
.unwrap();
|
||||||
|
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
|
||||||
|
assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
|
||||||
|
}
|
||||||
|
|
||||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||||
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
|
||||||
point..point
|
point..point
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
||||||
display_map::{
|
display_map::{
|
||||||
BlockContext, BlockStyle, DisplaySnapshot, FoldStatus, HighlightedChunk, ToDisplayPoint,
|
BlockContext, BlockStyle, DisplaySnapshot, HighlightedChunk, ToDisplayPoint, TransformBlock,
|
||||||
TransformBlock,
|
|
||||||
},
|
},
|
||||||
editor_settings::{
|
editor_settings::{
|
||||||
CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ShowScrollbar,
|
CurrentLineHighlight, DoubleClickInMultibuffer, MultiCursorModifier, ShowScrollbar,
|
||||||
|
@ -51,7 +50,7 @@ use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{self, max, Ordering},
|
cmp::{self, Ordering},
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
iter, mem,
|
iter, mem,
|
||||||
ops::{Deref, Range},
|
ops::{Deref, Range},
|
||||||
|
@ -869,6 +868,11 @@ impl EditorElement {
|
||||||
snapshot
|
snapshot
|
||||||
.folds_in_range(visible_anchor_range.clone())
|
.folds_in_range(visible_anchor_range.clone())
|
||||||
.filter_map(|fold| {
|
.filter_map(|fold| {
|
||||||
|
// Skip folds that have no text.
|
||||||
|
if fold.text.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let fold_range = fold.range.clone();
|
let fold_range = fold.range.clone();
|
||||||
let display_range = fold.range.start.to_display_point(&snapshot)
|
let display_range = fold.range.start.to_display_point(&snapshot)
|
||||||
..fold.range.end.to_display_point(&snapshot);
|
..fold.range.end.to_display_point(&snapshot);
|
||||||
|
@ -1163,28 +1167,17 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn layout_gutter_fold_indicators(
|
fn prepaint_gutter_fold_toggles(
|
||||||
&self,
|
&self,
|
||||||
fold_statuses: Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
|
toggles: &mut [Option<AnyElement>],
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
gutter_dimensions: &GutterDimensions,
|
gutter_dimensions: &GutterDimensions,
|
||||||
gutter_settings: crate::editor_settings::Gutter,
|
gutter_settings: crate::editor_settings::Gutter,
|
||||||
scroll_pixel_position: gpui::Point<Pixels>,
|
scroll_pixel_position: gpui::Point<Pixels>,
|
||||||
gutter_hitbox: &Hitbox,
|
gutter_hitbox: &Hitbox,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Vec<Option<AnyElement>> {
|
) {
|
||||||
let mut indicators = self.editor.update(cx, |editor, cx| {
|
for (ix, fold_indicator) in toggles.iter_mut().enumerate() {
|
||||||
editor.render_fold_indicators(
|
|
||||||
fold_statuses,
|
|
||||||
&self.style,
|
|
||||||
editor.gutter_hovered,
|
|
||||||
line_height,
|
|
||||||
gutter_dimensions.margin,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
for (ix, fold_indicator) in indicators.iter_mut().enumerate() {
|
|
||||||
if let Some(fold_indicator) = fold_indicator {
|
if let Some(fold_indicator) = fold_indicator {
|
||||||
debug_assert!(gutter_settings.folds);
|
debug_assert!(gutter_settings.folds);
|
||||||
let available_space = size(
|
let available_space = size(
|
||||||
|
@ -1207,8 +1200,49 @@ impl EditorElement {
|
||||||
fold_indicator.prepaint_as_root(origin, available_space, cx);
|
fold_indicator.prepaint_as_root(origin, available_space, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
indicators
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn prepaint_flap_trailers(
|
||||||
|
&self,
|
||||||
|
trailers: Vec<Option<AnyElement>>,
|
||||||
|
lines: &[LineWithInvisibles],
|
||||||
|
line_height: Pixels,
|
||||||
|
content_origin: gpui::Point<Pixels>,
|
||||||
|
scroll_pixel_position: gpui::Point<Pixels>,
|
||||||
|
em_width: Pixels,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Vec<Option<FlapTrailerLayout>> {
|
||||||
|
trailers
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ix, element)| {
|
||||||
|
let mut element = element?;
|
||||||
|
let available_space = size(
|
||||||
|
AvailableSpace::MinContent,
|
||||||
|
AvailableSpace::Definite(line_height),
|
||||||
|
);
|
||||||
|
let size = element.layout_as_root(available_space, cx);
|
||||||
|
|
||||||
|
let line = &lines[ix].line;
|
||||||
|
let padding = if line.width == Pixels::ZERO {
|
||||||
|
Pixels::ZERO
|
||||||
|
} else {
|
||||||
|
4. * em_width
|
||||||
|
};
|
||||||
|
let position = point(
|
||||||
|
scroll_pixel_position.x + line.width + padding,
|
||||||
|
ix as f32 * line_height - (scroll_pixel_position.y % line_height),
|
||||||
|
);
|
||||||
|
let centering_offset = point(px(0.), (line_height - size.height) / 2.);
|
||||||
|
let origin = content_origin + position + centering_offset;
|
||||||
|
element.prepaint_as_root(origin, available_space, cx);
|
||||||
|
Some(FlapTrailerLayout {
|
||||||
|
element,
|
||||||
|
bounds: Bounds::new(origin, size),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Folds contained in a hunk are ignored apart from shrinking visual size
|
// Folds contained in a hunk are ignored apart from shrinking visual size
|
||||||
|
@ -1292,6 +1326,7 @@ impl EditorElement {
|
||||||
display_row: DisplayRow,
|
display_row: DisplayRow,
|
||||||
display_snapshot: &DisplaySnapshot,
|
display_snapshot: &DisplaySnapshot,
|
||||||
line_layout: &LineWithInvisibles,
|
line_layout: &LineWithInvisibles,
|
||||||
|
flap_trailer: Option<&FlapTrailerLayout>,
|
||||||
em_width: Pixels,
|
em_width: Pixels,
|
||||||
content_origin: gpui::Point<Pixels>,
|
content_origin: gpui::Point<Pixels>,
|
||||||
scroll_pixel_position: gpui::Point<Pixels>,
|
scroll_pixel_position: gpui::Point<Pixels>,
|
||||||
|
@ -1331,17 +1366,22 @@ impl EditorElement {
|
||||||
let start_x = {
|
let start_x = {
|
||||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.;
|
||||||
|
|
||||||
let padded_line_width =
|
let line_end = if let Some(flap_trailer) = flap_trailer {
|
||||||
line_layout.line.width + (em_width * INLINE_BLAME_PADDING_EM_WIDTHS);
|
flap_trailer.bounds.right()
|
||||||
|
} else {
|
||||||
|
content_origin.x - scroll_pixel_position.x + line_layout.line.width
|
||||||
|
};
|
||||||
|
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
|
||||||
|
|
||||||
let min_column = ProjectSettings::get_global(cx)
|
let min_column_in_pixels = ProjectSettings::get_global(cx)
|
||||||
.git
|
.git
|
||||||
.inline_blame
|
.inline_blame
|
||||||
.and_then(|settings| settings.min_column)
|
.and_then(|settings| settings.min_column)
|
||||||
.map(|col| self.column_pixels(col as usize, cx))
|
.map(|col| self.column_pixels(col as usize, cx))
|
||||||
.unwrap_or(px(0.));
|
.unwrap_or(px(0.));
|
||||||
|
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
|
||||||
|
|
||||||
(content_origin.x - scroll_pixel_position.x) + max(padded_line_width, min_column)
|
cmp::max(padded_line_end, min_start)
|
||||||
};
|
};
|
||||||
|
|
||||||
let absolute_offset = point(start_x, start_y);
|
let absolute_offset = point(start_x, start_y);
|
||||||
|
@ -1580,13 +1620,9 @@ impl EditorElement {
|
||||||
active_rows: &BTreeMap<DisplayRow, bool>,
|
active_rows: &BTreeMap<DisplayRow, bool>,
|
||||||
newest_selection_head: Option<DisplayPoint>,
|
newest_selection_head: Option<DisplayPoint>,
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
cx: &WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (
|
) -> Vec<Option<ShapedLine>> {
|
||||||
Vec<Option<ShapedLine>>,
|
|
||||||
Vec<Option<(FoldStatus, MultiBufferRow, bool)>>,
|
|
||||||
) {
|
|
||||||
let editor = self.editor.read(cx);
|
let editor = self.editor.read(cx);
|
||||||
let is_singleton = editor.is_singleton(cx);
|
|
||||||
let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
|
let newest_selection_head = newest_selection_head.unwrap_or_else(|| {
|
||||||
let newest = editor.selections.newest::<Point>(cx);
|
let newest = editor.selections.newest::<Point>(cx);
|
||||||
SelectionLayout::new(
|
SelectionLayout::new(
|
||||||
|
@ -1603,10 +1639,7 @@ impl EditorElement {
|
||||||
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
|
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
|
||||||
let include_line_numbers =
|
let include_line_numbers =
|
||||||
EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
|
EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full;
|
||||||
let include_fold_statuses =
|
|
||||||
EditorSettings::get_global(cx).gutter.folds && snapshot.mode == EditorMode::Full;
|
|
||||||
let mut shaped_line_numbers = Vec::with_capacity(rows.len());
|
let mut shaped_line_numbers = Vec::with_capacity(rows.len());
|
||||||
let mut fold_statuses = Vec::with_capacity(rows.len());
|
|
||||||
let mut line_number = String::new();
|
let mut line_number = String::new();
|
||||||
let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
|
let is_relative = EditorSettings::get_global(cx).relative_line_numbers;
|
||||||
let relative_to = if is_relative {
|
let relative_to = if is_relative {
|
||||||
|
@ -1619,10 +1652,10 @@ impl EditorElement {
|
||||||
|
|
||||||
for (ix, row) in buffer_rows.into_iter().enumerate() {
|
for (ix, row) in buffer_rows.into_iter().enumerate() {
|
||||||
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||||
let (active, color) = if active_rows.contains_key(&display_row) {
|
let color = if active_rows.contains_key(&display_row) {
|
||||||
(true, cx.theme().colors().editor_active_line_number)
|
cx.theme().colors().editor_active_line_number
|
||||||
} else {
|
} else {
|
||||||
(false, cx.theme().colors().editor_line_number)
|
cx.theme().colors().editor_line_number
|
||||||
};
|
};
|
||||||
if let Some(multibuffer_row) = row {
|
if let Some(multibuffer_row) = row {
|
||||||
if include_line_numbers {
|
if include_line_numbers {
|
||||||
|
@ -1646,24 +1679,65 @@ impl EditorElement {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
shaped_line_numbers.push(Some(shaped_line));
|
shaped_line_numbers.push(Some(shaped_line));
|
||||||
}
|
}
|
||||||
if include_fold_statuses {
|
|
||||||
fold_statuses.push(
|
|
||||||
is_singleton
|
|
||||||
.then(|| {
|
|
||||||
snapshot
|
|
||||||
.fold_for_line(multibuffer_row)
|
|
||||||
.map(|fold_status| (fold_status, multibuffer_row, active))
|
|
||||||
})
|
|
||||||
.flatten(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
fold_statuses.push(None);
|
|
||||||
shaped_line_numbers.push(None);
|
shaped_line_numbers.push(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(shaped_line_numbers, fold_statuses)
|
shaped_line_numbers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout_gutter_fold_toggles(
|
||||||
|
&self,
|
||||||
|
rows: Range<DisplayRow>,
|
||||||
|
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
||||||
|
active_rows: &BTreeMap<DisplayRow, bool>,
|
||||||
|
snapshot: &EditorSnapshot,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Vec<Option<AnyElement>> {
|
||||||
|
let include_fold_statuses = EditorSettings::get_global(cx).gutter.folds
|
||||||
|
&& snapshot.mode == EditorMode::Full
|
||||||
|
&& self.editor.read(cx).is_singleton(cx);
|
||||||
|
if include_fold_statuses {
|
||||||
|
buffer_rows
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ix, row)| {
|
||||||
|
if let Some(multibuffer_row) = row {
|
||||||
|
let display_row = DisplayRow(rows.start.0 + ix as u32);
|
||||||
|
let active = active_rows.contains_key(&display_row);
|
||||||
|
snapshot.render_fold_toggle(
|
||||||
|
multibuffer_row,
|
||||||
|
active,
|
||||||
|
self.editor.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout_flap_trailers(
|
||||||
|
&self,
|
||||||
|
buffer_rows: impl IntoIterator<Item = Option<MultiBufferRow>>,
|
||||||
|
snapshot: &EditorSnapshot,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Vec<Option<AnyElement>> {
|
||||||
|
buffer_rows
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| {
|
||||||
|
if let Some(multibuffer_row) = row {
|
||||||
|
snapshot.render_flap_trailer(multibuffer_row, cx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_lines(
|
fn layout_lines(
|
||||||
|
@ -2465,8 +2539,8 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
|
||||||
cx.with_element_namespace("gutter_fold_indicators", |cx| {
|
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||||
for fold_indicator in layout.fold_indicators.iter_mut().flatten() {
|
for fold_indicator in layout.gutter_fold_toggles.iter_mut().flatten() {
|
||||||
fold_indicator.paint(cx);
|
fold_indicator.paint(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2646,6 +2720,11 @@ impl EditorElement {
|
||||||
self.paint_redactions(layout, cx);
|
self.paint_redactions(layout, cx);
|
||||||
self.paint_cursors(layout, cx);
|
self.paint_cursors(layout, cx);
|
||||||
self.paint_inline_blame(layout, cx);
|
self.paint_inline_blame(layout, cx);
|
||||||
|
cx.with_element_namespace("flap_trailers", |cx| {
|
||||||
|
for trailer in layout.flap_trailers.iter_mut().flatten() {
|
||||||
|
trailer.element.paint(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3992,15 +4071,29 @@ impl Element for EditorElement {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (line_numbers, fold_statuses) = self.layout_line_numbers(
|
let line_numbers = self.layout_line_numbers(
|
||||||
start_row..end_row,
|
start_row..end_row,
|
||||||
buffer_rows.clone().into_iter(),
|
buffer_rows.iter().copied(),
|
||||||
&active_rows,
|
&active_rows,
|
||||||
newest_selection_head,
|
newest_selection_head,
|
||||||
&snapshot,
|
&snapshot,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut gutter_fold_toggles =
|
||||||
|
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||||
|
self.layout_gutter_fold_toggles(
|
||||||
|
start_row..end_row,
|
||||||
|
buffer_rows.iter().copied(),
|
||||||
|
&active_rows,
|
||||||
|
&snapshot,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
|
||||||
|
self.layout_flap_trailers(buffer_rows.iter().copied(), &snapshot, cx)
|
||||||
|
});
|
||||||
|
|
||||||
let display_hunks = self.layout_git_gutters(
|
let display_hunks = self.layout_git_gutters(
|
||||||
line_height,
|
line_height,
|
||||||
&gutter_hitbox,
|
&gutter_hitbox,
|
||||||
|
@ -4046,15 +4139,30 @@ impl Element for EditorElement {
|
||||||
scroll_position.y * line_height,
|
scroll_position.y * line_height,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let flap_trailers = cx.with_element_namespace("flap_trailers", |cx| {
|
||||||
|
self.prepaint_flap_trailers(
|
||||||
|
flap_trailers,
|
||||||
|
&line_layouts,
|
||||||
|
line_height,
|
||||||
|
content_origin,
|
||||||
|
scroll_pixel_position,
|
||||||
|
em_width,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let mut inline_blame = None;
|
let mut inline_blame = None;
|
||||||
if let Some(newest_selection_head) = newest_selection_head {
|
if let Some(newest_selection_head) = newest_selection_head {
|
||||||
let display_row = newest_selection_head.row();
|
let display_row = newest_selection_head.row();
|
||||||
if (start_row..end_row).contains(&display_row) {
|
if (start_row..end_row).contains(&display_row) {
|
||||||
let line_layout = &line_layouts[display_row.minus(start_row) as usize];
|
let line_ix = display_row.minus(start_row) as usize;
|
||||||
|
let line_layout = &line_layouts[line_ix];
|
||||||
|
let flap_trailer_layout = flap_trailers[line_ix].as_ref();
|
||||||
inline_blame = self.layout_inline_blame(
|
inline_blame = self.layout_inline_blame(
|
||||||
display_row,
|
display_row,
|
||||||
&snapshot.display_snapshot,
|
&snapshot.display_snapshot,
|
||||||
line_layout,
|
line_layout,
|
||||||
|
flap_trailer_layout,
|
||||||
em_width,
|
em_width,
|
||||||
content_origin,
|
content_origin,
|
||||||
scroll_pixel_position,
|
scroll_pixel_position,
|
||||||
|
@ -4226,21 +4334,17 @@ impl Element for EditorElement {
|
||||||
|
|
||||||
let mouse_context_menu = self.layout_mouse_context_menu(cx);
|
let mouse_context_menu = self.layout_mouse_context_menu(cx);
|
||||||
|
|
||||||
let fold_indicators = if gutter_settings.folds {
|
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||||
cx.with_element_namespace("gutter_fold_indicators", |cx| {
|
self.prepaint_gutter_fold_toggles(
|
||||||
self.layout_gutter_fold_indicators(
|
&mut gutter_fold_toggles,
|
||||||
fold_statuses,
|
line_height,
|
||||||
line_height,
|
&gutter_dimensions,
|
||||||
&gutter_dimensions,
|
gutter_settings,
|
||||||
gutter_settings,
|
scroll_pixel_position,
|
||||||
scroll_pixel_position,
|
&gutter_hitbox,
|
||||||
&gutter_hitbox,
|
cx,
|
||||||
cx,
|
)
|
||||||
)
|
});
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let invisible_symbol_font_size = font_size / 2.;
|
let invisible_symbol_font_size = font_size / 2.;
|
||||||
let tab_invisible = cx
|
let tab_invisible = cx
|
||||||
|
@ -4310,7 +4414,8 @@ impl Element for EditorElement {
|
||||||
mouse_context_menu,
|
mouse_context_menu,
|
||||||
test_indicators,
|
test_indicators,
|
||||||
code_actions_indicator,
|
code_actions_indicator,
|
||||||
fold_indicators,
|
gutter_fold_toggles,
|
||||||
|
flap_trailers,
|
||||||
tab_invisible,
|
tab_invisible,
|
||||||
space_invisible,
|
space_invisible,
|
||||||
}
|
}
|
||||||
|
@ -4430,7 +4535,8 @@ pub struct EditorLayout {
|
||||||
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
||||||
code_actions_indicator: Option<AnyElement>,
|
code_actions_indicator: Option<AnyElement>,
|
||||||
test_indicators: Vec<AnyElement>,
|
test_indicators: Vec<AnyElement>,
|
||||||
fold_indicators: Vec<Option<AnyElement>>,
|
gutter_fold_toggles: Vec<Option<AnyElement>>,
|
||||||
|
flap_trailers: Vec<Option<FlapTrailerLayout>>,
|
||||||
mouse_context_menu: Option<AnyElement>,
|
mouse_context_menu: Option<AnyElement>,
|
||||||
tab_invisible: ShapedLine,
|
tab_invisible: ShapedLine,
|
||||||
space_invisible: ShapedLine,
|
space_invisible: ShapedLine,
|
||||||
|
@ -4554,6 +4660,11 @@ impl ScrollbarLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FlapTrailerLayout {
|
||||||
|
element: AnyElement,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
}
|
||||||
|
|
||||||
struct FoldLayout {
|
struct FoldLayout {
|
||||||
display_range: Range<DisplayPoint>,
|
display_range: Range<DisplayPoint>,
|
||||||
hover_element: AnyElement,
|
hover_element: AnyElement,
|
||||||
|
@ -4972,16 +5083,14 @@ mod tests {
|
||||||
|
|
||||||
let layouts = cx
|
let layouts = cx
|
||||||
.update_window(*window, |_, cx| {
|
.update_window(*window, |_, cx| {
|
||||||
element
|
element.layout_line_numbers(
|
||||||
.layout_line_numbers(
|
DisplayRow(0)..DisplayRow(6),
|
||||||
DisplayRow(0)..DisplayRow(6),
|
(0..6).map(MultiBufferRow).map(Some),
|
||||||
(0..6).map(MultiBufferRow).map(Some),
|
&Default::default(),
|
||||||
&Default::default(),
|
Some(DisplayPoint::new(DisplayRow(0), 0)),
|
||||||
Some(DisplayPoint::new(DisplayRow(0), 0)),
|
&snapshot,
|
||||||
&snapshot,
|
cx,
|
||||||
cx,
|
)
|
||||||
)
|
|
||||||
.0
|
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(layouts.len(), 6);
|
assert_eq!(layouts.len(), 6);
|
||||||
|
|
|
@ -280,6 +280,14 @@ impl<V: Render> From<View<V>> for AnyView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for AnyView {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.model == other.model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for AnyView {}
|
||||||
|
|
||||||
impl Element for AnyView {
|
impl Element for AnyView {
|
||||||
type RequestLayoutState = Option<AnyElement>;
|
type RequestLayoutState = Option<AnyElement>;
|
||||||
type PrepaintState = Option<AnyElement>;
|
type PrepaintState = Option<AnyElement>;
|
||||||
|
|
|
@ -4591,6 +4591,12 @@ impl From<(&'static str, u64)> for ElementId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(&'static str, u32)> for ElementId {
|
||||||
|
fn from((name, id): (&'static str, u32)) -> Self {
|
||||||
|
ElementId::NamedInteger(name.into(), id as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A rectangle to be rendered in the window at the given position and size.
|
/// A rectangle to be rendered in the window at the given position and size.
|
||||||
/// Passed as an argument [`WindowContext::paint_quad`].
|
/// Passed as an argument [`WindowContext::paint_quad`].
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -49,6 +49,7 @@ where
|
||||||
&self.position
|
&self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn end(&self, cx: &<T::Summary as Summary>::Context) -> D {
|
pub fn end(&self, cx: &<T::Summary as Summary>::Context) -> D {
|
||||||
if let Some(item_summary) = self.item_summary() {
|
if let Some(item_summary) = self.item_summary() {
|
||||||
let mut end = self.start().clone();
|
let mut end = self.start().clone();
|
||||||
|
@ -59,6 +60,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn item(&self) -> Option<&'a T> {
|
pub fn item(&self) -> Option<&'a T> {
|
||||||
self.assert_did_seek();
|
self.assert_did_seek();
|
||||||
if let Some(entry) = self.stack.last() {
|
if let Some(entry) = self.stack.last() {
|
||||||
|
@ -77,6 +79,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn item_summary(&self) -> Option<&'a T::Summary> {
|
pub fn item_summary(&self) -> Option<&'a T::Summary> {
|
||||||
self.assert_did_seek();
|
self.assert_did_seek();
|
||||||
if let Some(entry) = self.stack.last() {
|
if let Some(entry) = self.stack.last() {
|
||||||
|
@ -97,6 +100,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn next_item(&self) -> Option<&'a T> {
|
pub fn next_item(&self) -> Option<&'a T> {
|
||||||
self.assert_did_seek();
|
self.assert_did_seek();
|
||||||
if let Some(entry) = self.stack.last() {
|
if let Some(entry) = self.stack.last() {
|
||||||
|
@ -119,6 +123,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn next_leaf(&self) -> Option<&'a SumTree<T>> {
|
fn next_leaf(&self) -> Option<&'a SumTree<T>> {
|
||||||
for entry in self.stack.iter().rev().skip(1) {
|
for entry in self.stack.iter().rev().skip(1) {
|
||||||
if entry.index < entry.tree.0.child_trees().len() - 1 {
|
if entry.index < entry.tree.0.child_trees().len() - 1 {
|
||||||
|
@ -133,6 +138,7 @@ where
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn prev_item(&self) -> Option<&'a T> {
|
pub fn prev_item(&self) -> Option<&'a T> {
|
||||||
self.assert_did_seek();
|
self.assert_did_seek();
|
||||||
if let Some(entry) = self.stack.last() {
|
if let Some(entry) = self.stack.last() {
|
||||||
|
@ -155,6 +161,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn prev_leaf(&self) -> Option<&'a SumTree<T>> {
|
fn prev_leaf(&self) -> Option<&'a SumTree<T>> {
|
||||||
for entry in self.stack.iter().rev().skip(1) {
|
for entry in self.stack.iter().rev().skip(1) {
|
||||||
if entry.index != 0 {
|
if entry.index != 0 {
|
||||||
|
@ -169,10 +176,12 @@ where
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn prev(&mut self, cx: &<T::Summary as Summary>::Context) {
|
pub fn prev(&mut self, cx: &<T::Summary as Summary>::Context) {
|
||||||
self.prev_internal(|_| true, cx)
|
self.prev_internal(|_| true, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn prev_internal<F>(&mut self, mut filter_node: F, cx: &<T::Summary as Summary>::Context)
|
fn prev_internal<F>(&mut self, mut filter_node: F, cx: &<T::Summary as Summary>::Context)
|
||||||
where
|
where
|
||||||
F: FnMut(&T::Summary) -> bool,
|
F: FnMut(&T::Summary) -> bool,
|
||||||
|
@ -238,10 +247,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn next(&mut self, cx: &<T::Summary as Summary>::Context) {
|
pub fn next(&mut self, cx: &<T::Summary as Summary>::Context) {
|
||||||
self.next_internal(|_| true, cx)
|
self.next_internal(|_| true, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn next_internal<F>(&mut self, mut filter_node: F, cx: &<T::Summary as Summary>::Context)
|
fn next_internal<F>(&mut self, mut filter_node: F, cx: &<T::Summary as Summary>::Context)
|
||||||
where
|
where
|
||||||
F: FnMut(&T::Summary) -> bool,
|
F: FnMut(&T::Summary) -> bool,
|
||||||
|
@ -329,6 +340,7 @@ where
|
||||||
debug_assert!(self.stack.is_empty() || self.stack.last().unwrap().tree.0.is_leaf());
|
debug_assert!(self.stack.is_empty() || self.stack.last().unwrap().tree.0.is_leaf());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn assert_did_seek(&self) {
|
fn assert_did_seek(&self) {
|
||||||
assert!(
|
assert!(
|
||||||
self.did_seek,
|
self.did_seek,
|
||||||
|
@ -342,6 +354,7 @@ where
|
||||||
T: Item,
|
T: Item,
|
||||||
D: Dimension<'a, T::Summary>,
|
D: Dimension<'a, T::Summary>,
|
||||||
{
|
{
|
||||||
|
#[track_caller]
|
||||||
pub fn seek<Target>(
|
pub fn seek<Target>(
|
||||||
&mut self,
|
&mut self,
|
||||||
pos: &Target,
|
pos: &Target,
|
||||||
|
@ -355,6 +368,7 @@ where
|
||||||
self.seek_internal(pos, bias, &mut (), cx)
|
self.seek_internal(pos, bias, &mut (), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn seek_forward<Target>(
|
pub fn seek_forward<Target>(
|
||||||
&mut self,
|
&mut self,
|
||||||
pos: &Target,
|
pos: &Target,
|
||||||
|
@ -367,6 +381,7 @@ where
|
||||||
self.seek_internal(pos, bias, &mut (), cx)
|
self.seek_internal(pos, bias, &mut (), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn slice<Target>(
|
pub fn slice<Target>(
|
||||||
&mut self,
|
&mut self,
|
||||||
end: &Target,
|
end: &Target,
|
||||||
|
@ -386,10 +401,12 @@ where
|
||||||
slice.tree
|
slice.tree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn suffix(&mut self, cx: &<T::Summary as Summary>::Context) -> SumTree<T> {
|
pub fn suffix(&mut self, cx: &<T::Summary as Summary>::Context) -> SumTree<T> {
|
||||||
self.slice(&End::new(), Bias::Right, cx)
|
self.slice(&End::new(), Bias::Right, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn summary<Target, Output>(
|
pub fn summary<Target, Output>(
|
||||||
&mut self,
|
&mut self,
|
||||||
end: &Target,
|
end: &Target,
|
||||||
|
@ -406,6 +423,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether we found the item you where seeking for
|
/// Returns whether we found the item you where seeking for
|
||||||
|
#[track_caller]
|
||||||
fn seek_internal(
|
fn seek_internal(
|
||||||
&mut self,
|
&mut self,
|
||||||
target: &dyn SeekTarget<'a, T::Summary, D>,
|
target: &dyn SeekTarget<'a, T::Summary, D>,
|
||||||
|
|
|
@ -2,7 +2,6 @@ use crate::{
|
||||||
locator::Locator, BufferId, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset,
|
locator::Locator, BufferId, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset,
|
||||||
ToPoint, ToPointUtf16,
|
ToPoint, ToPointUtf16,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
|
||||||
use std::{cmp::Ordering, fmt::Debug, ops::Range};
|
use std::{cmp::Ordering, fmt::Debug, ops::Range};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
|
|
||||||
|
@ -136,14 +135,14 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AnchorRangeExt {
|
pub trait AnchorRangeExt {
|
||||||
fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Result<Ordering>;
|
fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Ordering;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnchorRangeExt for Range<Anchor> {
|
impl AnchorRangeExt for Range<Anchor> {
|
||||||
fn cmp(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> Result<Ordering> {
|
fn cmp(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> Ordering {
|
||||||
Ok(match self.start.cmp(&other.start, buffer) {
|
match self.start.cmp(&other.start, buffer) {
|
||||||
Ordering::Equal => other.end.cmp(&self.end, buffer),
|
Ordering::Equal => other.end.cmp(&self.end, buffer),
|
||||||
ord => ord,
|
ord => ord,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue