Detailed changes
@@ -26,8 +26,8 @@ use collections::{BTreeSet, HashMap, HashSet};
use editor::{
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
display_map::{
- BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease,
- CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
+ BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata,
+ CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
},
scroll::{Autoscroll, AutoscrollStrategy},
Anchor, Editor, EditorEvent, ProposedChangeLocation, ProposedChangesEditor, RowExt,
@@ -2009,13 +2009,12 @@ impl ContextEditor {
})
.map(|(command, error_message)| BlockProperties {
style: BlockStyle::Fixed,
- position: Anchor {
+ height: 1,
+ placement: BlockPlacement::Below(Anchor {
buffer_id: Some(buffer_id),
excerpt_id,
text_anchor: command.source_range.start,
- },
- height: 1,
- disposition: BlockDisposition::Below,
+ }),
render: slash_command_error_block_renderer(error_message),
priority: 0,
}),
@@ -2242,11 +2241,10 @@ impl ContextEditor {
} else {
let block_ids = editor.insert_blocks(
[BlockProperties {
- position: patch_start,
height: path_count as u32 + 1,
style: BlockStyle::Flex,
render: render_block,
- disposition: BlockDisposition::Below,
+ placement: BlockPlacement::Below(patch_start),
priority: 0,
}],
None,
@@ -2731,12 +2729,13 @@ impl ContextEditor {
})
};
let create_block_properties = |message: &Message| BlockProperties {
- position: buffer
- .anchor_in_excerpt(excerpt_id, message.anchor_range.start)
- .unwrap(),
height: 2,
style: BlockStyle::Sticky,
- disposition: BlockDisposition::Above,
+ placement: BlockPlacement::Above(
+ buffer
+ .anchor_in_excerpt(excerpt_id, message.anchor_range.start)
+ .unwrap(),
+ ),
priority: usize::MAX,
render: render_block(MessageMetadata::from(message)),
};
@@ -3372,7 +3371,7 @@ impl ContextEditor {
let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
let image = render_image.clone();
anchor.is_valid(&buffer).then(|| BlockProperties {
- position: anchor,
+ placement: BlockPlacement::Above(anchor),
height: MAX_HEIGHT_IN_LINES,
style: BlockStyle::Sticky,
render: Box::new(move |cx| {
@@ -3393,8 +3392,6 @@ impl ContextEditor {
)
.into_any_element()
}),
-
- disposition: BlockDisposition::Above,
priority: 0,
})
})
@@ -9,7 +9,7 @@ use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
actions::{MoveDown, MoveUp, SelectAll},
display_map::{
- BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
+ BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
ToDisplayPoint,
},
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
@@ -446,15 +446,14 @@ impl InlineAssistant {
let assist_blocks = vec![
BlockProperties {
style: BlockStyle::Sticky,
- position: range.start,
+ placement: BlockPlacement::Above(range.start),
height: prompt_editor_height,
render: build_assist_editor_renderer(prompt_editor),
- disposition: BlockDisposition::Above,
priority: 0,
},
BlockProperties {
style: BlockStyle::Sticky,
- position: range.end,
+ placement: BlockPlacement::Below(range.end),
height: 0,
render: Box::new(|cx| {
v_flex()
@@ -464,7 +463,6 @@ impl InlineAssistant {
.border_color(cx.theme().status().info_border)
.into_any_element()
}),
- disposition: BlockDisposition::Below,
priority: 0,
},
];
@@ -1179,7 +1177,7 @@ impl InlineAssistant {
let height =
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
new_blocks.push(BlockProperties {
- position: new_row,
+ placement: BlockPlacement::Above(new_row),
height,
style: BlockStyle::Flex,
render: Box::new(move |cx| {
@@ -1191,7 +1189,6 @@ impl InlineAssistant {
.child(deleted_lines_editor.clone())
.into_any_element()
}),
- disposition: BlockDisposition::Above,
priority: 0,
});
}
@@ -9,7 +9,7 @@ use anyhow::Result;
use collections::{BTreeSet, HashSet};
use editor::{
diagnostic_block_renderer,
- display_map::{BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},
+ display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},
highlight_diagnostic_message,
scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
@@ -439,11 +439,10 @@ impl ProjectDiagnosticsEditor {
primary.message.split('\n').next().unwrap().to_string();
group_state.block_count += 1;
blocks_to_add.push(BlockProperties {
- position: header_position,
+ placement: BlockPlacement::Above(header_position),
height: 2,
style: BlockStyle::Sticky,
render: diagnostic_header_renderer(primary),
- disposition: BlockDisposition::Above,
priority: 0,
});
}
@@ -459,13 +458,15 @@ impl ProjectDiagnosticsEditor {
if !diagnostic.message.is_empty() {
group_state.block_count += 1;
blocks_to_add.push(BlockProperties {
- position: (excerpt_id, entry.range.start),
+ placement: BlockPlacement::Below((
+ excerpt_id,
+ entry.range.start,
+ )),
height: diagnostic.message.matches('\n').count() as u32 + 1,
style: BlockStyle::Fixed,
render: diagnostic_block_renderer(
diagnostic, None, true, true,
),
- disposition: BlockDisposition::Below,
priority: 0,
});
}
@@ -498,13 +499,24 @@ impl ProjectDiagnosticsEditor {
editor.remove_blocks(blocks_to_remove, None, cx);
let block_ids = editor.insert_blocks(
blocks_to_add.into_iter().flat_map(|block| {
- let (excerpt_id, text_anchor) = block.position;
+ let placement = match block.placement {
+ BlockPlacement::Above((excerpt_id, text_anchor)) => BlockPlacement::Above(
+ excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
+ ),
+ BlockPlacement::Below((excerpt_id, text_anchor)) => BlockPlacement::Below(
+ excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
+ ),
+ BlockPlacement::Replace(_) => {
+ unreachable!(
+ "no Replace block should have been pushed to blocks_to_add"
+ )
+ }
+ };
Some(BlockProperties {
- position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?,
+ placement,
height: block.height,
style: block.style,
render: block.render,
- disposition: block.disposition,
priority: 0,
})
}),
@@ -29,8 +29,8 @@ use crate::{
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
};
pub use block_map::{
- Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId,
- BlockMap, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
+ Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
+ BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
};
use block_map::{BlockRow, BlockSnapshot};
use char_map::{CharMap, CharSnapshot};
@@ -1180,6 +1180,7 @@ impl ToDisplayPoint for Anchor {
pub mod tests {
use super::*;
use crate::{movement, test::marked_display_snapshot};
+ use block_map::BlockPlacement;
use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla};
use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
@@ -1293,24 +1294,22 @@ pub mod tests {
Bias::Left,
));
- let disposition = if rng.gen() {
- BlockDisposition::Above
+ let placement = if rng.gen() {
+ BlockPlacement::Above(position)
} else {
- BlockDisposition::Below
+ BlockPlacement::Below(position)
};
let height = rng.gen_range(1..5);
log::info!(
- "inserting block {:?} {:?} with height {}",
- disposition,
- position.to_point(&buffer),
+ "inserting block {:?} with height {}",
+ placement.as_ref().map(|p| p.to_point(&buffer)),
height
);
let priority = rng.gen_range(1..100);
BlockProperties {
+ placement,
style: BlockStyle::Fixed,
- position,
height,
- disposition,
render: Box::new(|_| div().into_any()),
priority,
}
@@ -6,7 +6,9 @@ use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
use language::{Chunk, Patch, Point};
-use multi_buffer::{Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, ToPoint as _};
+use multi_buffer::{
+ Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, MultiBufferSnapshot, ToPoint as _,
+};
use parking_lot::Mutex;
use std::{
cell::RefCell,
@@ -18,7 +20,7 @@ use std::{
Arc,
},
};
-use sum_tree::{Bias, SumTree, TreeMap};
+use sum_tree::{Bias, SumTree, Summary, TreeMap};
use text::Edit;
use ui::ElementId;
@@ -77,32 +79,173 @@ struct WrapRow(u32);
pub type RenderBlock = Box<dyn Send + FnMut(&mut BlockContext) -> AnyElement>;
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum BlockPlacement<T> {
+ Above(T),
+ Below(T),
+ Replace(Range<T>),
+}
+
+impl<T> BlockPlacement<T> {
+ fn start(&self) -> &T {
+ match self {
+ BlockPlacement::Above(position) => position,
+ BlockPlacement::Below(position) => position,
+ BlockPlacement::Replace(range) => &range.start,
+ }
+ }
+
+ fn end(&self) -> &T {
+ match self {
+ BlockPlacement::Above(position) => position,
+ BlockPlacement::Below(position) => position,
+ BlockPlacement::Replace(range) => &range.end,
+ }
+ }
+
+ pub fn as_ref(&self) -> BlockPlacement<&T> {
+ match self {
+ BlockPlacement::Above(position) => BlockPlacement::Above(position),
+ BlockPlacement::Below(position) => BlockPlacement::Below(position),
+ BlockPlacement::Replace(range) => BlockPlacement::Replace(&range.start..&range.end),
+ }
+ }
+
+ pub fn map<R>(self, mut f: impl FnMut(T) -> R) -> BlockPlacement<R> {
+ match self {
+ BlockPlacement::Above(position) => BlockPlacement::Above(f(position)),
+ BlockPlacement::Below(position) => BlockPlacement::Below(f(position)),
+ BlockPlacement::Replace(range) => BlockPlacement::Replace(f(range.start)..f(range.end)),
+ }
+ }
+}
+
+impl BlockPlacement<Anchor> {
+ fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
+ match (self, other) {
+ (BlockPlacement::Above(anchor_a), BlockPlacement::Above(anchor_b))
+ | (BlockPlacement::Below(anchor_a), BlockPlacement::Below(anchor_b)) => {
+ anchor_a.cmp(anchor_b, buffer)
+ }
+ (BlockPlacement::Above(anchor_a), BlockPlacement::Below(anchor_b)) => {
+ anchor_a.cmp(anchor_b, buffer).then(Ordering::Less)
+ }
+ (BlockPlacement::Below(anchor_a), BlockPlacement::Above(anchor_b)) => {
+ anchor_a.cmp(anchor_b, buffer).then(Ordering::Greater)
+ }
+ (BlockPlacement::Above(anchor), BlockPlacement::Replace(range)) => {
+ anchor.cmp(&range.start, buffer).then(Ordering::Less)
+ }
+ (BlockPlacement::Replace(range), BlockPlacement::Above(anchor)) => {
+ range.start.cmp(anchor, buffer).then(Ordering::Greater)
+ }
+ (BlockPlacement::Below(anchor), BlockPlacement::Replace(range)) => {
+ anchor.cmp(&range.start, buffer).then(Ordering::Greater)
+ }
+ (BlockPlacement::Replace(range), BlockPlacement::Below(anchor)) => {
+ range.start.cmp(anchor, buffer).then(Ordering::Less)
+ }
+ (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => range_a
+ .start
+ .cmp(&range_b.start, buffer)
+ .then_with(|| range_b.end.cmp(&range_a.end, buffer)),
+ }
+ }
+
+ fn to_wrap_row(&self, wrap_snapshot: &WrapSnapshot) -> Option<BlockPlacement<WrapRow>> {
+ let buffer_snapshot = wrap_snapshot.buffer_snapshot();
+ match self {
+ BlockPlacement::Above(position) => {
+ let mut position = position.to_point(buffer_snapshot);
+ position.column = 0;
+ let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
+ Some(BlockPlacement::Above(wrap_row))
+ }
+ BlockPlacement::Below(position) => {
+ let mut position = position.to_point(buffer_snapshot);
+ position.column = buffer_snapshot.line_len(MultiBufferRow(position.row));
+ let wrap_row = WrapRow(wrap_snapshot.make_wrap_point(position, Bias::Left).row());
+ Some(BlockPlacement::Below(wrap_row))
+ }
+ BlockPlacement::Replace(range) => {
+ let mut start = range.start.to_point(buffer_snapshot);
+ let mut end = range.end.to_point(buffer_snapshot);
+ if start == end {
+ None
+ } else {
+ start.column = 0;
+ let start_wrap_row =
+ WrapRow(wrap_snapshot.make_wrap_point(start, Bias::Left).row());
+ end.column = buffer_snapshot.line_len(MultiBufferRow(end.row));
+ let end_wrap_row =
+ WrapRow(wrap_snapshot.make_wrap_point(end, Bias::Left).row());
+ Some(BlockPlacement::Replace(start_wrap_row..end_wrap_row))
+ }
+ }
+ }
+ }
+}
+
+impl Ord for BlockPlacement<WrapRow> {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match (self, other) {
+ (BlockPlacement::Above(row_a), BlockPlacement::Above(row_b))
+ | (BlockPlacement::Below(row_a), BlockPlacement::Below(row_b)) => row_a.cmp(row_b),
+ (BlockPlacement::Above(row_a), BlockPlacement::Below(row_b)) => {
+ row_a.cmp(row_b).then(Ordering::Less)
+ }
+ (BlockPlacement::Below(row_a), BlockPlacement::Above(row_b)) => {
+ row_a.cmp(row_b).then(Ordering::Greater)
+ }
+ (BlockPlacement::Above(row), BlockPlacement::Replace(range)) => {
+ row.cmp(&range.start).then(Ordering::Less)
+ }
+ (BlockPlacement::Replace(range), BlockPlacement::Above(row)) => {
+ range.start.cmp(row).then(Ordering::Greater)
+ }
+ (BlockPlacement::Below(row), BlockPlacement::Replace(range)) => {
+ row.cmp(&range.start).then(Ordering::Greater)
+ }
+ (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => {
+ range.start.cmp(row).then(Ordering::Less)
+ }
+ (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => range_a
+ .start
+ .cmp(&range_b.start)
+ .then_with(|| range_b.end.cmp(&range_a.end)),
+ }
+ }
+}
+
+impl PartialOrd for BlockPlacement<WrapRow> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
pub struct CustomBlock {
id: CustomBlockId,
- position: Anchor,
+ placement: BlockPlacement<Anchor>,
height: u32,
style: BlockStyle,
render: Arc<Mutex<RenderBlock>>,
- disposition: BlockDisposition,
priority: usize,
}
pub struct BlockProperties<P> {
- pub position: P,
+ pub placement: BlockPlacement<P>,
pub height: u32,
pub style: BlockStyle,
pub render: RenderBlock,
- pub disposition: BlockDisposition,
pub priority: usize,
}
impl<P: Debug> Debug for BlockProperties<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlockProperties")
- .field("position", &self.position)
+ .field("placement", &self.placement)
.field("height", &self.height)
.field("style", &self.style)
- .field("disposition", &self.disposition)
.finish()
}
}
@@ -125,10 +268,10 @@ pub struct BlockContext<'a, 'b> {
pub editor_style: &'b EditorStyle,
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum BlockId {
- Custom(CustomBlockId),
ExcerptBoundary(Option<ExcerptId>),
+ Custom(CustomBlockId),
}
impl From<BlockId> for ElementId {
@@ -152,30 +295,12 @@ impl std::fmt::Display for BlockId {
}
}
-/// Whether the block should be considered above or below the anchor line
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum BlockDisposition {
- Above,
- Below,
-}
-
#[derive(Clone, Debug)]
struct Transform {
summary: TransformSummary,
block: Option<Block>,
}
-pub(crate) enum BlockType {
- Custom(CustomBlockId),
- ExcerptBoundary,
-}
-
-pub(crate) trait BlockLike {
- fn block_type(&self) -> BlockType;
- fn disposition(&self) -> BlockDisposition;
- fn priority(&self) -> usize;
-}
-
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum Block {
@@ -189,26 +314,6 @@ pub enum Block {
},
}
-impl BlockLike for Block {
- fn block_type(&self) -> BlockType {
- match self {
- Block::Custom(block) => BlockType::Custom(block.id),
- Block::ExcerptBoundary { .. } => BlockType::ExcerptBoundary,
- }
- }
-
- fn disposition(&self) -> BlockDisposition {
- self.disposition()
- }
-
- fn priority(&self) -> usize {
- match self {
- Block::Custom(block) => block.priority,
- Block::ExcerptBoundary { .. } => usize::MAX,
- }
- }
-}
-
impl Block {
pub fn id(&self) -> BlockId {
match self {
@@ -219,19 +324,6 @@ impl Block {
}
}
- fn disposition(&self) -> BlockDisposition {
- match self {
- Block::Custom(block) => block.disposition,
- Block::ExcerptBoundary { next_excerpt, .. } => {
- if next_excerpt.is_some() {
- BlockDisposition::Above
- } else {
- BlockDisposition::Below
- }
- }
- }
- }
-
pub fn height(&self) -> u32 {
match self {
Block::Custom(block) => block.height,
@@ -245,6 +337,20 @@ impl Block {
Block::ExcerptBoundary { .. } => BlockStyle::Sticky,
}
}
+
+ fn place_above(&self) -> bool {
+ match self {
+ Block::Custom(block) => matches!(block.placement, BlockPlacement::Above(_)),
+ Block::ExcerptBoundary { next_excerpt, .. } => next_excerpt.is_some(),
+ }
+ }
+
+ fn place_below(&self) -> bool {
+ match self {
+ Block::Custom(block) => matches!(block.placement, BlockPlacement::Below(_)),
+ Block::ExcerptBoundary { next_excerpt, .. } => next_excerpt.is_none(),
+ }
+ }
}
impl Debug for Block {
@@ -270,6 +376,8 @@ impl Debug for Block {
struct TransformSummary {
input_rows: u32,
output_rows: u32,
+ longest_row: u32,
+ longest_row_chars: u32,
}
pub struct BlockChunks<'a> {
@@ -298,11 +406,13 @@ impl BlockMap {
excerpt_footer_height: u32,
) -> Self {
let row_count = wrap_snapshot.max_point().row() + 1;
+ let mut transforms = SumTree::default();
+ push_isomorphic(&mut transforms, row_count, &wrap_snapshot);
let map = Self {
next_block_id: AtomicUsize::new(0),
custom_blocks: Vec::new(),
custom_blocks_by_id: TreeMap::default(),
- transforms: RefCell::new(SumTree::from_item(Transform::isomorphic(row_count), &())),
+ transforms: RefCell::new(transforms),
wrap_snapshot: RefCell::new(wrap_snapshot.clone()),
show_excerpt_controls,
buffer_header_height,
@@ -368,28 +478,29 @@ impl BlockMap {
let mut transforms = self.transforms.borrow_mut();
let mut new_transforms = SumTree::default();
- let old_row_count = transforms.summary().input_rows;
- let new_row_count = wrap_snapshot.max_point().row() + 1;
let mut cursor = transforms.cursor::<WrapRow>(&());
let mut last_block_ix = 0;
let mut blocks_in_edit = Vec::new();
let mut edits = edits.into_iter().peekable();
while let Some(edit) = edits.next() {
- // Preserve any old transforms that precede this edit.
- let old_start = WrapRow(edit.old.start);
- let new_start = WrapRow(edit.new.start);
+ let mut old_start = WrapRow(edit.old.start);
+ let mut new_start = WrapRow(edit.new.start);
+
+ // Preserve transforms that:
+ // * strictly precedes this edit
+ // * isomorphic or replace transforms that end *at* the start of the edit
+ // * below blocks that end at the start of the edit
new_transforms.append(cursor.slice(&old_start, Bias::Left, &()), &());
if let Some(transform) = cursor.item() {
- if transform.is_isomorphic() && old_start == cursor.end(&()) {
+ if transform.summary.input_rows > 0 && cursor.end(&()) == old_start {
+ // Preserve the transform (push and next)
new_transforms.push(transform.clone(), &());
cursor.next(&());
+
+ // Preserve below blocks at end of edit
while let Some(transform) = cursor.item() {
- if transform
- .block
- .as_ref()
- .map_or(false, |b| b.disposition().is_below())
- {
+ if transform.block.as_ref().map_or(false, |b| b.place_below()) {
new_transforms.push(transform.clone(), &());
cursor.next(&());
} else {
@@ -399,50 +510,70 @@ impl BlockMap {
}
}
- // Preserve any portion of an old transform that precedes this edit.
- let extent_before_edit = old_start.0 - cursor.start().0;
- push_isomorphic(&mut new_transforms, extent_before_edit);
+ // Ensure the edit starts at a transform boundary.
+ // If the edit starts within an isomorphic transform, preserve its prefix
+ // If the edit lands within a replacement block, expand the edit to include the start of the replaced input range
+ let mut preserved_blocks_above_edit = false;
+ let transform = cursor.item().unwrap();
+ let transform_rows_before_edit = old_start.0 - cursor.start().0;
+ if transform_rows_before_edit > 0 {
+ if transform.block.is_none() {
+ // Preserve any portion of the old isomorphic transform that precedes this edit.
+ push_isomorphic(
+ &mut new_transforms,
+ transform_rows_before_edit,
+ wrap_snapshot,
+ );
+ } else {
+ // We landed within a block that replaces some lines, so we
+ // extend the edit to start at the beginning of the
+ // replacement.
+ debug_assert!(transform.summary.input_rows > 0);
+ old_start.0 -= transform_rows_before_edit;
+ new_start.0 -= transform_rows_before_edit;
+ // The blocks *above* it are already in the new transforms, so
+ // we don't need to re-insert them when querying blocks.
+ preserved_blocks_above_edit = true;
+ }
+ }
- // Skip over any old transforms that intersect this edit.
+ // Decide where the edit ends
+ // * It should end at a transform boundary
+ // * Coalesce edits that intersect the same transform
let mut old_end = WrapRow(edit.old.end);
let mut new_end = WrapRow(edit.new.end);
- cursor.seek(&old_end, Bias::Left, &());
- cursor.next(&());
- if old_end == *cursor.start() {
- while let Some(transform) = cursor.item() {
- if transform
- .block
- .as_ref()
- .map_or(false, |b| b.disposition().is_below())
- {
+ loop {
+ // Seek to the transform starting at or after the end of the edit
+ cursor.seek(&old_end, Bias::Left, &());
+ cursor.next(&());
+
+ // Extend edit to the end of the discarded transform so it is reconstructed in full
+ let transform_rows_after_edit = cursor.start().0 - old_end.0;
+ old_end.0 += transform_rows_after_edit;
+ new_end.0 += transform_rows_after_edit;
+
+ // Combine this edit with any subsequent edits that intersect the same transform.
+ while let Some(next_edit) = edits.peek() {
+ if next_edit.old.start <= cursor.start().0 {
+ old_end = WrapRow(next_edit.old.end);
+ new_end = WrapRow(next_edit.new.end);
+ cursor.seek(&old_end, Bias::Left, &());
cursor.next(&());
+ edits.next();
} else {
break;
}
}
+
+ if *cursor.start() == old_end {
+ break;
+ }
}
- // Combine this edit with any subsequent edits that intersect the same transform.
- while let Some(next_edit) = edits.peek() {
- if next_edit.old.start <= cursor.start().0 {
- old_end = WrapRow(next_edit.old.end);
- new_end = WrapRow(next_edit.new.end);
- cursor.seek(&old_end, Bias::Left, &());
+ // Discard below blocks at the end of the edit. They'll be reconstructed.
+ while let Some(transform) = cursor.item() {
+ if transform.block.as_ref().map_or(false, |b| b.place_below()) {
cursor.next(&());
- if old_end == *cursor.start() {
- while let Some(transform) = cursor.item() {
- if transform
- .block
- .as_ref()
- .map_or(false, |b| b.disposition().is_below())
- {
- cursor.next(&());
- } else {
- break;
- }
- }
- }
- edits.next();
} else {
break;
}
@@ -455,9 +586,10 @@ impl BlockMap {
let start_block_ix =
match self.custom_blocks[last_block_ix..].binary_search_by(|probe| {
probe
- .position
+ .start()
.to_point(buffer)
.cmp(&new_buffer_start)
+ // Move left until we find the index of the first block starting within this edit
.then(Ordering::Greater)
}) {
Ok(ix) | Err(ix) => last_block_ix + ix,
@@ -473,7 +605,7 @@ impl BlockMap {
end_bound = Bound::Excluded(new_buffer_end);
match self.custom_blocks[start_block_ix..].binary_search_by(|probe| {
probe
- .position
+ .start()
.to_point(buffer)
.cmp(&new_buffer_end)
.then(Ordering::Greater)
@@ -484,19 +616,17 @@ impl BlockMap {
last_block_ix = end_block_ix;
debug_assert!(blocks_in_edit.is_empty());
- blocks_in_edit.extend(self.custom_blocks[start_block_ix..end_block_ix].iter().map(
- |block| {
- let mut position = block.position.to_point(buffer);
- match block.disposition {
- BlockDisposition::Above => position.column = 0,
- BlockDisposition::Below => {
- position.column = buffer.line_len(MultiBufferRow(position.row))
- }
- }
- let position = wrap_snapshot.make_wrap_point(position, Bias::Left);
- (position.row(), Block::Custom(block.clone()))
- },
- ));
+
+ blocks_in_edit.extend(
+ self.custom_blocks[start_block_ix..end_block_ix]
+ .iter()
+ .filter_map(|block| {
+ Some((
+ block.placement.to_wrap_row(wrap_snapshot)?,
+ Block::Custom(block.clone()),
+ ))
+ }),
+ );
if buffer.show_headers() {
blocks_in_edit.extend(BlockMap::header_and_footer_blocks(
@@ -514,26 +644,49 @@ impl BlockMap {
// For each of these blocks, insert a new isomorphic transform preceding the block,
// and then insert the block itself.
- for (block_row, block) in blocks_in_edit.drain(..) {
- let insertion_row = match block.disposition() {
- BlockDisposition::Above => block_row,
- BlockDisposition::Below => block_row + 1,
+ for (block_placement, block) in blocks_in_edit.drain(..) {
+ if preserved_blocks_above_edit
+ && block_placement == BlockPlacement::Above(new_start)
+ {
+ continue;
+ }
+
+ let mut summary = TransformSummary {
+ input_rows: 0,
+ output_rows: block.height(),
+ longest_row: 0,
+ longest_row_chars: 0,
};
- let extent_before_block = insertion_row - new_transforms.summary().input_rows;
- push_isomorphic(&mut new_transforms, extent_before_block);
- new_transforms.push(Transform::block(block), &());
- }
- old_end = WrapRow(old_end.0.min(old_row_count));
- new_end = WrapRow(new_end.0.min(new_row_count));
+ let rows_before_block;
+ match block_placement {
+ BlockPlacement::Above(position) => {
+ rows_before_block = position.0 - new_transforms.summary().input_rows;
+ }
+ BlockPlacement::Below(position) => {
+ rows_before_block = (position.0 + 1) - new_transforms.summary().input_rows;
+ }
+ BlockPlacement::Replace(range) => {
+ rows_before_block = range.start.0 - new_transforms.summary().input_rows;
+ summary.input_rows = range.end.0 - range.start.0 + 1;
+ }
+ }
- // Insert an isomorphic transform after the final block.
- let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows;
- push_isomorphic(&mut new_transforms, extent_after_last_block);
+ push_isomorphic(&mut new_transforms, rows_before_block, wrap_snapshot);
+ new_transforms.push(
+ Transform {
+ summary,
+ block: Some(block),
+ },
+ &(),
+ );
+ }
- // Preserve any portion of the old transform after this edit.
- let extent_after_edit = cursor.start().0 - old_end.0;
- push_isomorphic(&mut new_transforms, extent_after_edit);
+ // Insert an isomorphic transform after the final block.
+ let rows_after_last_block = new_end
+ .0
+ .saturating_sub(new_transforms.summary().input_rows);
+ push_isomorphic(&mut new_transforms, rows_after_last_block, wrap_snapshot);
}
new_transforms.append(cursor.suffix(&()), &());
@@ -558,7 +711,7 @@ impl BlockMap {
self.show_excerpt_controls
}
- pub fn header_and_footer_blocks<'a, 'b: 'a, 'c: 'a + 'b, R, T>(
+ fn header_and_footer_blocks<'a, 'b: 'a, 'c: 'a + 'b, R, T>(
show_excerpt_controls: bool,
excerpt_footer_height: u32,
buffer_header_height: u32,
@@ -566,7 +719,7 @@ impl BlockMap {
buffer: &'b multi_buffer::MultiBufferSnapshot,
range: R,
wrap_snapshot: &'c WrapSnapshot,
- ) -> impl Iterator<Item = (u32, Block)> + 'b
+ ) -> impl Iterator<Item = (BlockPlacement<WrapRow>, Block)> + 'b
where
R: RangeBounds<T>,
T: multi_buffer::ToOffset,
@@ -619,7 +772,11 @@ impl BlockMap {
}
Some((
- wrap_row,
+ if excerpt_boundary.next.is_some() {
+ BlockPlacement::Above(WrapRow(wrap_row))
+ } else {
+ BlockPlacement::Below(WrapRow(wrap_row))
+ },
Block::ExcerptBoundary {
prev_excerpt: excerpt_boundary.prev,
next_excerpt: excerpt_boundary.next,
@@ -631,45 +788,96 @@ impl BlockMap {
})
}
- pub(crate) fn sort_blocks<B: BlockLike>(blocks: &mut [(u32, B)]) {
- // Place excerpt headers and footers above custom blocks on the same row
- blocks.sort_unstable_by(|(row_a, block_a), (row_b, block_b)| {
- row_a.cmp(row_b).then_with(|| {
- block_a
- .disposition()
- .cmp(&block_b.disposition())
- .then_with(|| match ((block_a.block_type()), (block_b.block_type())) {
- (BlockType::ExcerptBoundary, BlockType::ExcerptBoundary) => Ordering::Equal,
- (BlockType::ExcerptBoundary, _) => Ordering::Less,
- (_, BlockType::ExcerptBoundary) => Ordering::Greater,
- (BlockType::Custom(a_id), BlockType::Custom(b_id)) => block_b
- .priority()
- .cmp(&block_a.priority())
- .then_with(|| a_id.cmp(&b_id)),
- })
- })
+ fn sort_blocks(blocks: &mut Vec<(BlockPlacement<WrapRow>, Block)>) {
+ blocks.sort_unstable_by(|(placement_a, block_a), (placement_b, block_b)| {
+ placement_a
+ .cmp(&placement_b)
+ .then_with(|| match (block_a, block_b) {
+ (
+ Block::ExcerptBoundary {
+ next_excerpt: next_excerpt_a,
+ ..
+ },
+ Block::ExcerptBoundary {
+ next_excerpt: next_excerpt_b,
+ ..
+ },
+ ) => next_excerpt_a
+ .as_ref()
+ .map(|excerpt| excerpt.id)
+ .cmp(&next_excerpt_b.as_ref().map(|excerpt| excerpt.id)),
+ (Block::ExcerptBoundary { next_excerpt, .. }, Block::Custom(_)) => {
+ if next_excerpt.is_some() {
+ Ordering::Less
+ } else {
+ Ordering::Greater
+ }
+ }
+ (Block::Custom(_), Block::ExcerptBoundary { next_excerpt, .. }) => {
+ if next_excerpt.is_some() {
+ Ordering::Greater
+ } else {
+ Ordering::Less
+ }
+ }
+ (Block::Custom(block_a), Block::Custom(block_b)) => block_a
+ .priority
+ .cmp(&block_b.priority)
+ .then_with(|| block_a.id.cmp(&block_b.id)),
+ })
+ });
+ blocks.dedup_by(|(right, _), (left, _)| match (left, right) {
+ (BlockPlacement::Replace(range), BlockPlacement::Above(row)) => {
+ range.start < *row && range.end >= *row
+ }
+ (BlockPlacement::Replace(range), BlockPlacement::Below(row)) => {
+ range.start <= *row && range.end > *row
+ }
+ (BlockPlacement::Replace(range_a), BlockPlacement::Replace(range_b)) => {
+ if range_a.end >= range_b.start && range_a.start <= range_b.end {
+ range_a.end = range_a.end.max(range_b.end);
+ true
+ } else {
+ false
+ }
+ }
+ _ => false,
});
}
}
-fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32) {
+fn push_isomorphic(tree: &mut SumTree<Transform>, rows: u32, wrap_snapshot: &WrapSnapshot) {
if rows == 0 {
return;
}
- let mut extent = Some(rows);
+ let wrap_row_start = tree.summary().input_rows;
+ let wrap_row_end = wrap_row_start + rows;
+ let wrap_summary = wrap_snapshot.text_summary_for_range(wrap_row_start..wrap_row_end);
+ let summary = TransformSummary {
+ input_rows: rows,
+ output_rows: rows,
+ longest_row: wrap_summary.longest_row,
+ longest_row_chars: wrap_summary.longest_row_chars,
+ };
+ let mut merged = false;
tree.update_last(
|last_transform| {
- if last_transform.is_isomorphic() {
- let extent = extent.take().unwrap();
- last_transform.summary.input_rows += extent;
- last_transform.summary.output_rows += extent;
+ if last_transform.block.is_none() {
+ last_transform.summary.add_summary(&summary, &());
+ merged = true;
}
},
&(),
);
- if let Some(extent) = extent {
- tree.push(Transform::isomorphic(extent), &());
+ if !merged {
+ tree.push(
+ Transform {
+ summary,
+ block: None,
+ },
+ &(),
+ );
}
}
@@ -711,7 +919,7 @@ impl<'a> BlockMapReader<'a> {
pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
let block = self.blocks.iter().find(|block| block.id == block_id)?;
let buffer_row = block
- .position
+ .start()
.to_point(self.wrap_snapshot.buffer_snapshot())
.row;
let wrap_row = self
@@ -735,9 +943,7 @@ impl<'a> BlockMapReader<'a> {
break;
}
- if let Some(BlockType::Custom(id)) =
- transform.block.as_ref().map(|block| block.block_type())
- {
+ if let Some(BlockId::Custom(id)) = transform.block.as_ref().map(|block| block.id()) {
if id == block_id {
return Some(cursor.start().1);
}
@@ -762,21 +968,27 @@ impl<'a> BlockMapWriter<'a> {
let mut previous_wrap_row_range: Option<Range<u32>> = None;
for block in blocks {
+ if let BlockPlacement::Replace(_) = &block.placement {
+ debug_assert!(block.height > 0);
+ }
+
let id = CustomBlockId(self.0.next_block_id.fetch_add(1, SeqCst));
ids.push(id);
- let position = block.position;
- let point = position.to_point(buffer);
- let wrap_row = wrap_snapshot
- .make_wrap_point(Point::new(point.row, 0), Bias::Left)
- .row();
+ let start = block.placement.start().to_point(buffer);
+ let end = block.placement.end().to_point(buffer);
+ let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
+ let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
let (start_row, end_row) = {
- previous_wrap_row_range.take_if(|range| !range.contains(&wrap_row));
+ previous_wrap_row_range.take_if(|range| {
+ !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
+ });
let range = previous_wrap_row_range.get_or_insert_with(|| {
- let start_row = wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
+ let start_row =
+ wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
let end_row = wrap_snapshot
- .next_row_boundary(WrapPoint::new(wrap_row, 0))
+ .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
.unwrap_or(wrap_snapshot.max_point().row() + 1);
start_row..end_row
});
@@ -785,16 +997,15 @@ impl<'a> BlockMapWriter<'a> {
let block_ix = match self
.0
.custom_blocks
- .binary_search_by(|probe| probe.position.cmp(&position, buffer))
+ .binary_search_by(|probe| probe.placement.cmp(&block.placement, buffer))
{
Ok(ix) | Err(ix) => ix,
};
let new_block = Arc::new(CustomBlock {
id,
- position,
+ placement: block.placement,
height: block.height,
render: Arc::new(Mutex::new(block.render)),
- disposition: block.disposition,
style: block.style,
priority: block.priority,
});
@@ -819,34 +1030,41 @@ impl<'a> BlockMapWriter<'a> {
for block in &mut self.0.custom_blocks {
if let Some(new_height) = heights.remove(&block.id) {
+ if let BlockPlacement::Replace(_) = &block.placement {
+ debug_assert!(new_height > 0);
+ }
+
if block.height != new_height {
let new_block = CustomBlock {
id: block.id,
- position: block.position,
+ placement: block.placement.clone(),
height: new_height,
style: block.style,
render: block.render.clone(),
- disposition: block.disposition,
priority: block.priority,
};
let new_block = Arc::new(new_block);
*block = new_block.clone();
self.0.custom_blocks_by_id.insert(block.id, new_block);
- let buffer_row = block.position.to_point(buffer).row;
- if last_block_buffer_row != Some(buffer_row) {
- last_block_buffer_row = Some(buffer_row);
- let wrap_row = wrap_snapshot
- .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
+ let start_row = block.placement.start().to_point(buffer).row;
+ let end_row = block.placement.end().to_point(buffer).row;
+ if last_block_buffer_row != Some(end_row) {
+ last_block_buffer_row = Some(end_row);
+ let start_wrap_row = wrap_snapshot
+ .make_wrap_point(Point::new(start_row, 0), Bias::Left)
+ .row();
+ let end_wrap_row = wrap_snapshot
+ .make_wrap_point(Point::new(end_row, 0), Bias::Left)
.row();
- let start_row =
- wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
- let end_row = wrap_snapshot
- .next_row_boundary(WrapPoint::new(wrap_row, 0))
+ let start =
+ wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
+ let end = wrap_snapshot
+ .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
.unwrap_or(wrap_snapshot.max_point().row() + 1);
edits.push(Edit {
- old: start_row..end_row,
- new: start_row..end_row,
+ old: start..end,
+ new: start..end,
})
}
}
@@ -864,19 +1082,21 @@ impl<'a> BlockMapWriter<'a> {
let mut previous_wrap_row_range: Option<Range<u32>> = None;
self.0.custom_blocks.retain(|block| {
if block_ids.contains(&block.id) {
- let buffer_row = block.position.to_point(buffer).row;
- if last_block_buffer_row != Some(buffer_row) {
- last_block_buffer_row = Some(buffer_row);
- let wrap_row = wrap_snapshot
- .make_wrap_point(Point::new(buffer_row, 0), Bias::Left)
- .row();
+ let start = block.placement.start().to_point(buffer);
+ let end = block.placement.end().to_point(buffer);
+ if last_block_buffer_row != Some(end.row) {
+ last_block_buffer_row = Some(end.row);
+ let start_wrap_row = wrap_snapshot.make_wrap_point(start, Bias::Left).row();
+ let end_wrap_row = wrap_snapshot.make_wrap_point(end, Bias::Left).row();
let (start_row, end_row) = {
- previous_wrap_row_range.take_if(|range| !range.contains(&wrap_row));
+ previous_wrap_row_range.take_if(|range| {
+ !range.contains(&start_wrap_row) || !range.contains(&end_wrap_row)
+ });
let range = previous_wrap_row_range.get_or_insert_with(|| {
let start_row =
- wrap_snapshot.prev_row_boundary(WrapPoint::new(wrap_row, 0));
+ wrap_snapshot.prev_row_boundary(WrapPoint::new(start_wrap_row, 0));
let end_row = wrap_snapshot
- .next_row_boundary(WrapPoint::new(wrap_row, 0))
+ .next_row_boundary(WrapPoint::new(end_wrap_row, 0))
.unwrap_or(wrap_snapshot.max_point().row() + 1);
start_row..end_row
});
@@ -921,31 +1141,24 @@ impl BlockSnapshot {
highlights: Highlights<'a>,
) -> BlockChunks<'a> {
let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows);
+
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
- let input_end = {
- cursor.seek(&BlockRow(rows.end), Bias::Right, &());
- let overshoot = if cursor
- .item()
- .map_or(false, |transform| transform.is_isomorphic())
- {
- rows.end - cursor.start().0 .0
- } else {
- 0
- };
- cursor.start().1 .0 + overshoot
- };
- let input_start = {
- cursor.seek(&BlockRow(rows.start), Bias::Right, &());
- let overshoot = if cursor
- .item()
- .map_or(false, |transform| transform.is_isomorphic())
- {
- rows.start - cursor.start().0 .0
- } else {
- 0
- };
- cursor.start().1 .0 + overshoot
- };
+ cursor.seek(&BlockRow(rows.start), Bias::Right, &());
+ let transform_output_start = cursor.start().0 .0;
+ let transform_input_start = cursor.start().1 .0;
+
+ let mut input_start = transform_input_start;
+ let mut input_end = transform_input_start;
+ if let Some(transform) = cursor.item() {
+ if transform.block.is_none() {
+ input_start += rows.start - transform_output_start;
+ input_end += cmp::min(
+ rows.end - transform_output_start,
+ transform.summary.input_rows,
+ );
+ }
+ }
+
BlockChunks {
input_chunks: self.wrap_snapshot.chunks(
input_start..input_end,
@@ -964,7 +1177,10 @@ impl BlockSnapshot {
let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&());
cursor.seek(&start_row, Bias::Right, &());
let (output_start, input_start) = cursor.start();
- let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) {
+ let overshoot = if cursor
+ .item()
+ .map_or(false, |transform| transform.block.is_none())
+ {
start_row.0 - output_start.0
} else {
0
@@ -1049,13 +1265,12 @@ impl BlockSnapshot {
}
pub fn max_point(&self) -> BlockPoint {
- let row = self.transforms.summary().output_rows - 1;
+ let row = self.transforms.summary().output_rows.saturating_sub(1);
BlockPoint::new(row, self.line_len(BlockRow(row)))
}
pub fn longest_row(&self) -> u32 {
- let input_row = self.wrap_snapshot.longest_row();
- self.to_block_point(WrapPoint::new(input_row, 0)).row
+ self.transforms.summary().longest_row
}
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
@@ -1069,6 +1284,8 @@ impl BlockSnapshot {
} else {
self.wrap_snapshot.line_len(input_start.0 + overshoot)
}
+ } else if row.0 == 0 {
+ 0
} else {
panic!("row out of range");
}
@@ -1091,26 +1308,40 @@ impl BlockSnapshot {
loop {
if let Some(transform) = cursor.item() {
- if transform.is_isomorphic() {
- let (output_start_row, input_start_row) = cursor.start();
- let (output_end_row, input_end_row) = cursor.end(&());
- let output_start = Point::new(output_start_row.0, 0);
- let input_start = Point::new(input_start_row.0, 0);
- let input_end = Point::new(input_end_row.0, 0);
- let input_point = if point.row >= output_end_row.0 {
- let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
- self.wrap_snapshot
- .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
- } else {
- let output_overshoot = point.0.saturating_sub(output_start);
- self.wrap_snapshot
- .clip_point(WrapPoint(input_start + output_overshoot), bias)
- };
-
- if (input_start..input_end).contains(&input_point.0) {
- let input_overshoot = input_point.0.saturating_sub(input_start);
- return BlockPoint(output_start + input_overshoot);
+ let (output_start_row, input_start_row) = cursor.start();
+ let (output_end_row, input_end_row) = cursor.end(&());
+ let output_start = Point::new(output_start_row.0, 0);
+ let output_end = Point::new(output_end_row.0, 0);
+ let input_start = Point::new(input_start_row.0, 0);
+ let input_end = Point::new(input_end_row.0, 0);
+
+ match transform.block.as_ref() {
+ Some(Block::Custom(block))
+ if matches!(block.placement, BlockPlacement::Replace(_)) =>
+ {
+ if bias == Bias::Left {
+ return BlockPoint(output_start);
+ } else {
+ return BlockPoint(Point::new(output_end.row - 1, 0));
+ }
}
+ None => {
+ let input_point = if point.row >= output_end_row.0 {
+ let line_len = self.wrap_snapshot.line_len(input_end_row.0 - 1);
+ self.wrap_snapshot
+ .clip_point(WrapPoint::new(input_end_row.0 - 1, line_len), bias)
+ } else {
+ let output_overshoot = point.0.saturating_sub(output_start);
+ self.wrap_snapshot
+ .clip_point(WrapPoint(input_start + output_overshoot), bias)
+ };
+
+ if (input_start..input_end).contains(&input_point.0) {
+ let input_overshoot = input_point.0.saturating_sub(input_start);
+ return BlockPoint(output_start + input_overshoot);
+ }
+ }
+ _ => {}
}
if search_left {
@@ -252,6 +252,7 @@ impl CharSnapshot {
};
TabChunks {
+ snapshot: self,
fold_chunks: self.fold_snapshot.chunks(
input_start..input_end,
language_aware,
@@ -492,6 +493,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
const SPACES: &str = " ";
pub struct TabChunks<'a> {
+ snapshot: &'a CharSnapshot,
fold_chunks: FoldChunks<'a>,
chunk: Chunk<'a>,
column: u32,
@@ -503,6 +505,37 @@ pub struct TabChunks<'a> {
inside_leading_tab: bool,
}
+impl<'a> TabChunks<'a> {
+ pub(crate) fn seek(&mut self, range: Range<CharPoint>) {
+ let (input_start, expanded_char_column, to_next_stop) =
+ self.snapshot.to_fold_point(range.start, Bias::Left);
+ let input_column = input_start.column();
+ let input_start = input_start.to_offset(&self.snapshot.fold_snapshot);
+ let input_end = self
+ .snapshot
+ .to_fold_point(range.end, Bias::Right)
+ .0
+ .to_offset(&self.snapshot.fold_snapshot);
+ let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 {
+ range.end.column() - range.start.column()
+ } else {
+ to_next_stop
+ };
+
+ self.fold_chunks.seek(input_start..input_end);
+ self.input_column = input_column;
+ self.column = expanded_char_column;
+ self.output_position = range.start.0;
+ self.max_output_position = range.end.0;
+ self.chunk = Chunk {
+ text: &SPACES[0..(to_next_stop as usize)],
+ is_tab: true,
+ ..Default::default()
+ };
+ self.inside_leading_tab = to_next_stop > 0;
+ }
+}
+
impl<'a> Iterator for TabChunks<'a> {
type Item = Chunk<'a>;
@@ -1100,6 +1100,17 @@ pub struct FoldBufferRows<'a> {
fold_point: FoldPoint,
}
+impl<'a> FoldBufferRows<'a> {
+ pub(crate) fn seek(&mut self, row: u32) {
+ let fold_point = FoldPoint::new(row, 0);
+ self.cursor.seek(&fold_point, Bias::Left, &());
+ let overshoot = fold_point.0 - self.cursor.start().0 .0;
+ let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot);
+ self.input_buffer_rows.seek(inlay_point.row());
+ self.fold_point = fold_point;
+ }
+}
+
impl<'a> Iterator for FoldBufferRows<'a> {
type Item = Option<u32>;
@@ -1135,6 +1146,38 @@ pub struct FoldChunks<'a> {
max_output_offset: FoldOffset,
}
+impl<'a> FoldChunks<'a> {
+ pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
+ self.transform_cursor.seek(&range.start, Bias::Right, &());
+
+ let inlay_start = {
+ let overshoot = range.start.0 - self.transform_cursor.start().0 .0;
+ self.transform_cursor.start().1 + InlayOffset(overshoot)
+ };
+
+ let transform_end = self.transform_cursor.end(&());
+
+ let inlay_end = if self
+ .transform_cursor
+ .item()
+ .map_or(true, |transform| transform.is_fold())
+ {
+ inlay_start
+ } else if range.end < transform_end.0 {
+ let overshoot = range.end.0 - self.transform_cursor.start().0 .0;
+ self.transform_cursor.start().1 + InlayOffset(overshoot)
+ } else {
+ transform_end.1
+ };
+
+ self.inlay_chunks.seek(inlay_start..inlay_end);
+ self.inlay_chunk = None;
+ self.inlay_offset = inlay_start;
+ self.output_offset = range.start;
+ self.max_output_offset = range.end;
+ }
+}
+
impl<'a> Iterator for FoldChunks<'a> {
type Item = Chunk<'a>;
@@ -56,6 +56,7 @@ pub struct WrapChunks<'a> {
output_position: WrapPoint,
max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>,
+ snapshot: &'a WrapSnapshot,
}
#[derive(Clone)]
@@ -68,6 +69,21 @@ pub struct WrapBufferRows<'a> {
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>,
}
+impl<'a> WrapBufferRows<'a> {
+ pub(crate) fn seek(&mut self, start_row: u32) {
+ self.transforms
+ .seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
+ let mut input_row = self.transforms.start().1.row();
+ if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
+ input_row += start_row - self.transforms.start().0.row();
+ }
+ self.soft_wrapped = self.transforms.item().map_or(false, |t| !t.is_isomorphic());
+ self.input_buffer_rows.seek(input_row);
+ self.input_buffer_row = self.input_buffer_rows.next().unwrap();
+ self.output_row = start_row;
+ }
+}
+
impl WrapMap {
pub fn new(
char_snapshot: CharSnapshot,
@@ -602,6 +618,7 @@ impl WrapSnapshot {
output_position: output_start,
max_output_row: rows.end,
transforms,
+ snapshot: self,
}
}
@@ -629,6 +646,67 @@ impl WrapSnapshot {
}
}
+ pub fn text_summary_for_range(&self, rows: Range<u32>) -> TextSummary {
+ let mut summary = TextSummary::default();
+
+ let start = WrapPoint::new(rows.start, 0);
+ let end = WrapPoint::new(rows.end, 0);
+
+ let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
+ cursor.seek(&start, Bias::Right, &());
+ if let Some(transform) = cursor.item() {
+ let start_in_transform = start.0 - cursor.start().0 .0;
+ let end_in_transform = cmp::min(end, cursor.end(&()).0).0 - cursor.start().0 .0;
+ if transform.is_isomorphic() {
+ let char_start = CharPoint(cursor.start().1 .0 + start_in_transform);
+ let char_end = CharPoint(cursor.start().1 .0 + end_in_transform);
+ summary += &self
+ .char_snapshot
+ .text_summary_for_range(char_start..char_end);
+ } else {
+ debug_assert_eq!(start_in_transform.row, end_in_transform.row);
+ let indent_len = end_in_transform.column - start_in_transform.column;
+ summary += &TextSummary {
+ lines: Point::new(0, indent_len),
+ first_line_chars: indent_len,
+ last_line_chars: indent_len,
+ longest_row: 0,
+ longest_row_chars: indent_len,
+ };
+ }
+
+ cursor.next(&());
+ }
+
+ if rows.end > cursor.start().0.row() {
+ summary += &cursor
+ .summary::<_, TransformSummary>(&WrapPoint::new(rows.end, 0), Bias::Right, &())
+ .output;
+
+ if let Some(transform) = cursor.item() {
+ let end_in_transform = end.0 - cursor.start().0 .0;
+ if transform.is_isomorphic() {
+ let char_start = cursor.start().1;
+ let char_end = CharPoint(char_start.0 + end_in_transform);
+ summary += &self
+ .char_snapshot
+ .text_summary_for_range(char_start..char_end);
+ } else {
+ debug_assert_eq!(end_in_transform, Point::new(1, 0));
+ summary += &TextSummary {
+ lines: Point::new(1, 0),
+ first_line_chars: 0,
+ last_line_chars: 0,
+ longest_row: 0,
+ longest_row_chars: 0,
+ };
+ }
+ }
+ }
+
+ summary
+ }
+
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
let mut cursor = self.transforms.cursor::<WrapPoint>(&());
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
@@ -745,6 +823,21 @@ impl WrapSnapshot {
None
}
+ #[cfg(test)]
+ pub fn text(&self) -> String {
+ self.text_chunks(0).collect()
+ }
+
+ #[cfg(test)]
+ pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
+ self.chunks(
+ wrap_row..self.max_point().row() + 1,
+ false,
+ Highlights::default(),
+ )
+ .map(|h| h.text)
+ }
+
fn check_invariants(&self) {
#[cfg(test)]
{
@@ -791,6 +884,26 @@ impl WrapSnapshot {
}
}
+impl<'a> WrapChunks<'a> {
+ pub(crate) fn seek(&mut self, rows: Range<u32>) {
+ let output_start = WrapPoint::new(rows.start, 0);
+ let output_end = WrapPoint::new(rows.end, 0);
+ self.transforms.seek(&output_start, Bias::Right, &());
+ let mut input_start = CharPoint(self.transforms.start().1 .0);
+ if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
+ input_start.0 += output_start.0 - self.transforms.start().0 .0;
+ }
+ let input_end = self
+ .snapshot
+ .to_char_point(output_end)
+ .min(self.snapshot.char_snapshot.max_point());
+ self.input_chunks.seek(input_start..input_end);
+ self.input_chunk = Chunk::default();
+ self.output_position = output_start;
+ self.max_output_row = rows.end;
+ }
+}
+
impl<'a> Iterator for WrapChunks<'a> {
type Item = Chunk<'a>;
@@ -1336,19 +1449,6 @@ mod tests {
}
impl WrapSnapshot {
- pub fn text(&self) -> String {
- self.text_chunks(0).collect()
- }
-
- pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
- self.chunks(
- wrap_row..self.max_point().row() + 1,
- false,
- Highlights::default(),
- )
- .map(|h| h.text)
- }
-
fn verify_chunks(&mut self, rng: &mut impl Rng) {
for _ in 0..5 {
let mut end_row = rng.gen_range(0..=self.max_point().row());
@@ -10210,7 +10210,7 @@ impl Editor {
let block_id = this.insert_blocks(
[BlockProperties {
style: BlockStyle::Flex,
- position: range.start,
+ placement: BlockPlacement::Below(range.start),
height: 1,
render: Box::new({
let rename_editor = rename_editor.clone();
@@ -10246,7 +10246,6 @@ impl Editor {
.into_any_element()
}
}),
- disposition: BlockDisposition::Below,
priority: 0,
}],
Some(Autoscroll::fit()),
@@ -10531,10 +10530,11 @@ impl Editor {
let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
BlockProperties {
style: BlockStyle::Fixed,
- position: buffer.anchor_after(entry.range.start),
+ placement: BlockPlacement::Below(
+ buffer.anchor_after(entry.range.start),
+ ),
height: message_height,
render: diagnostic_block_renderer(diagnostic, None, true, true),
- disposition: BlockDisposition::Below,
priority: 0,
}
}),
@@ -3868,8 +3868,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
editor.insert_blocks(
[BlockProperties {
style: BlockStyle::Fixed,
- position: snapshot.anchor_after(Point::new(2, 0)),
- disposition: BlockDisposition::Below,
+ placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
height: 1,
render: Box::new(|_| div().into_any()),
priority: 0,
@@ -2071,7 +2071,7 @@ impl EditorElement {
let mut element = match block {
Block::Custom(block) => {
let align_to = block
- .position()
+ .start()
.to_point(&snapshot.buffer_snapshot)
.to_display_point(snapshot);
let anchor_x = text_x
@@ -6294,7 +6294,7 @@ fn compute_auto_height_layout(
mod tests {
use super::*;
use crate::{
- display_map::{BlockDisposition, BlockProperties},
+ display_map::{BlockPlacement, BlockProperties},
editor_tests::{init_test, update_test_language_settings},
Editor, MultiBuffer,
};
@@ -6550,9 +6550,8 @@ mod tests {
editor.insert_blocks(
[BlockProperties {
style: BlockStyle::Fixed,
- disposition: BlockDisposition::Above,
+ placement: BlockPlacement::Above(Anchor::min()),
height: 3,
- position: Anchor::min(),
render: Box::new(|cx| div().h(3. * cx.line_height()).into_any()),
priority: 0,
}],
@@ -17,7 +17,7 @@ use workspace::Item;
use crate::{
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyDiffHunk,
- BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow,
+ BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow,
DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile,
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
};
@@ -417,10 +417,9 @@ impl Editor {
};
BlockProperties {
- position: hunk.multi_buffer_range.start,
+ placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
height: 1,
style: BlockStyle::Sticky,
- disposition: BlockDisposition::Above,
priority: 0,
render: Box::new({
let editor = cx.view().clone();
@@ -700,10 +699,9 @@ impl Editor {
let hunk = hunk.clone();
let height = editor_height.max(deleted_text_height);
BlockProperties {
- position: hunk.multi_buffer_range.start,
+ placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
height,
style: BlockStyle::Flex,
- disposition: BlockDisposition::Above,
priority: 0,
render: Box::new(move |cx| {
let width = EditorElement::diff_hunk_strip_width(cx.line_height());
@@ -8,7 +8,7 @@ use client::telemetry::Telemetry;
use collections::{HashMap, HashSet};
use editor::{
display_map::{
- BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, CustomBlockId,
+ BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId,
RenderBlock,
},
scroll::Autoscroll,
@@ -90,12 +90,11 @@ impl EditorBlock {
let invalidation_anchor = buffer.read(cx).read(cx).anchor_before(next_row_start);
let block = BlockProperties {
- position: code_range.end,
+ placement: BlockPlacement::Below(code_range.end),
// Take up at least one height for status, allow the editor to determine the real height based on the content from render
height: 1,
style: BlockStyle::Sticky,
render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()),
- disposition: BlockDisposition::Below,
priority: 0,
};