Compare commits

...

26 Commits

Author SHA1 Message Date
Antonio Scandurra
967dd02414 WIP: start using replace blocks for patches 2024-10-24 16:33:15 +02:00
Antonio Scandurra
a32dd07e27 Fix BlockMap::buffer_rows now that we have replacement blocks 2024-10-24 12:41:26 +02:00
Antonio Scandurra
57333fdcf0 Ensure clipping BlockPoints right works correctly 2024-10-24 12:17:35 +02:00
Antonio Scandurra
dd5b4f23e6 Ensure replace blocks are at least one line tall
Otherwise it becomes impossible to address certain ranges in the buffer.
2024-10-24 11:46:14 +02:00
Antonio Scandurra
397c19c103 WIP: start on fixing point translation in BlockMap
SEED=437554 SIMPLE_TEXT=1 OPERATIONS=1 cargo test --package=editor random_blocks

Co-Authored-By: Richard <richard@zed.dev>
2024-10-23 17:08:18 +02:00
Antonio Scandurra
5f515089ad Fix BlockMap::longest_row 2024-10-23 14:48:09 +02:00
Antonio Scandurra
f8fe881631 Include zero-height blocks in expected blocks 2024-10-23 13:44:27 +02:00
Antonio Scandurra
10eed50765 Include replacements in expected blocks 2024-10-23 13:37:32 +02:00
Antonio Scandurra
6b44b11fc4 Fix randomized tests (passing with 20 operations, 1m iterations)
Now we need to uncomment other parts of the randomized test (e.g., buffer rows)
2024-10-23 12:47:57 +02:00
Max Brunsfeld
2bb7567cbb Re-enable non-singleton multibuffers in random blocks test
failure:RUST_LOG=info SIMPLE_TEXT=1 OPERATIONS=2 ITERATIONS=1 SEED=26086 ct -p editor random_blocks -- --nocapture
2024-10-22 17:06:32 -07:00
Max Brunsfeld
0d8b8492f0 Merge branch 'main' into block-placement-2 2024-10-22 16:34:56 -07:00
Richard Feldman
4bf61f892f Get text chunks assertions in BlockMap randomized tests passing
Co-authored-by: Richard <richard@zed.dev>
Co-authored-by: Antonio <antonio@zed.dev>
2024-10-22 16:20:15 -07:00
Antonio Scandurra
fc524ad02a WIP: failing test, see below
RUST_LOG=info SEED=22484 SIMPLE_TEXT=1 ITERATIONS=1 OPERATIONS=5 cargo test -p editor test_random_blocks -- --nocapture
2024-10-22 18:13:39 +02:00
Antonio Scandurra
607fb34124 Clarify how the expected text is constructed in randomized test
Co-Authored-By: Nathan <nathan@zed.dev>
2024-10-22 17:01:01 +02:00
Antonio Scandurra
a931c7ac06 Make BlockMap::chunks randomized tests pass again with OPERATIONS=1 2024-10-22 14:00:45 +02:00
Antonio Scandurra
9322069dce Fix some randomized test failures with OPERATIONS=1
I disabled all assertions but the ones that exercise `chunks`, as I'd like to
make sure that things work at a basic level. We should re-enable the remaining
assertions once basic features work correctly.
2024-10-22 13:42:30 +02:00
Antonio Scandurra
52732e75a0 Sort and deduplicate blocks correctly 2024-10-22 10:58:35 +02:00
Nathan Sobo
192aa78f94 Start on including replace blocks in randomized test
Simple failure with OPERATIONS=1

Co-Authored-By: Max <max@zed.dev>
2024-10-21 18:41:34 -06:00
Max Brunsfeld
c50b572faf Avoid subtraction overflow in BlockChunks::next
When the end of a block is after the max row, don't overflow.

Co-authored-by: Nathan <nathan@zed.dev>
2024-10-21 16:47:20 -07:00
Max Brunsfeld
2e54737a23 Get existing BlockMap randomized test passing
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Richard <richard@zed.dev>
2024-10-21 16:15:22 -07:00
Antonio Scandurra
2e0ae8f1e1 Checkpoint
Co-Authored-By: Max <max@zed.dev>
Co-Authored-By: Richard <richard@zed.dev>
Co-Authored-By: Marshall <marshall@zed.dev>
Co-Authored-By: Nathan <nathan@zed.dev>
2024-10-21 20:11:12 +02:00
Antonio Scandurra
f92c892b49 WIP 2024-10-21 17:51:04 +02:00
Antonio Scandurra
1504f9d661 Seek input chunks when advancing to the next isomorphic transform
Notably, this makes our basic unit test for replacing lines pass!

Co-Authored-By: Richard <richard@zed.dev>
2024-10-21 17:35:19 +02:00
Antonio Scandurra
a5e6b222dd WIP: Introduce a new BlockPlacement::Replace variant
Co-Authored-By: Richard <richard@zed.dev>
2024-10-21 15:54:37 +02:00
Antonio Scandurra
23e3539f54 Checkpoint: compiles and tests pass 2024-10-21 14:24:01 +02:00
Antonio Scandurra
3303be678e WIP: Replace BlockDisposition with BlockPlacement
We still need to update users of this API and run tests.
2024-10-21 14:04:06 +02:00
13 changed files with 1114 additions and 639 deletions

View File

@@ -26,8 +26,8 @@ use collections::{BTreeSet, HashMap, HashSet};
use editor::{ use editor::{
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt}, actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
display_map::{ display_map::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata,
CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
}, },
scroll::{Autoscroll, AutoscrollStrategy}, scroll::{Autoscroll, AutoscrollStrategy},
Anchor, Editor, EditorEvent, ProposedChangeLocation, ProposedChangesEditor, RowExt, Anchor, Editor, EditorEvent, ProposedChangeLocation, ProposedChangesEditor, RowExt,
@@ -1446,8 +1446,8 @@ struct ScrollPosition {
} }
struct PatchViewState { struct PatchViewState {
footer_block_id: CustomBlockId, block_id: CustomBlockId,
crease_id: CreaseId, // crease_id: CreaseId,
editor: Option<PatchEditorState>, editor: Option<PatchEditorState>,
update_task: Option<Task<()>>, update_task: Option<Task<()>>,
} }
@@ -2009,13 +2009,12 @@ impl ContextEditor {
}) })
.map(|(command, error_message)| BlockProperties { .map(|(command, error_message)| BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position: Anchor { height: 1,
placement: BlockPlacement::Below(Anchor {
buffer_id: Some(buffer_id), buffer_id: Some(buffer_id),
excerpt_id, excerpt_id,
text_anchor: command.source_range.start, text_anchor: command.source_range.start,
}, }),
height: 1,
disposition: BlockDisposition::Below,
render: slash_command_error_block_renderer(error_message), render: slash_command_error_block_renderer(error_message),
priority: 0, priority: 0,
}), }),
@@ -2159,8 +2158,8 @@ impl ContextEditor {
for range in removed { for range in removed {
if let Some(state) = self.patches.remove(range) { if let Some(state) = self.patches.remove(range) {
editors_to_close.extend(state.editor.and_then(|state| state.editor.upgrade())); editors_to_close.extend(state.editor.and_then(|state| state.editor.upgrade()));
removed_block_ids.insert(state.footer_block_id); removed_block_ids.insert(state.block_id);
removed_crease_ids.push(state.crease_id); // removed_crease_ids.push(state.crease_id);
} }
} }
@@ -2190,7 +2189,7 @@ impl ContextEditor {
let gutter_width = cx.gutter_dimensions.full_width(); let gutter_width = cx.gutter_dimensions.full_width();
let block_id = cx.block_id; let block_id = cx.block_id;
this.update(&mut **cx, |this, cx| { this.update(&mut **cx, |this, cx| {
this.render_patch_footer( this.render_patch(
patch_range.clone(), patch_range.clone(),
max_width, max_width,
gutter_width, gutter_width,
@@ -2204,26 +2203,9 @@ impl ContextEditor {
} }
}); });
let header_placeholder = FoldPlaceholder {
render: {
let this = this.clone();
let patch_range = range.clone();
Arc::new(move |fold_id, _range, cx| {
this.update(cx, |this, cx| {
this.render_patch_header(patch_range.clone(), fold_id, cx)
})
.ok()
.flatten()
.unwrap_or_else(|| Empty.into_any())
})
},
constrain_width: false,
merge_adjacent: false,
};
let should_refold; let should_refold;
if let Some(state) = self.patches.get_mut(&range) { if let Some(state) = self.patches.get_mut(&range) {
replaced_blocks.insert(state.footer_block_id, render_block); replaced_blocks.insert(state.block_id, render_block);
if let Some(editor_state) = &state.editor { if let Some(editor_state) = &state.editor {
if editor_state.opened_patch != patch { if editor_state.opened_patch != patch {
state.update_task = Some({ state.update_task = Some({
@@ -2242,32 +2224,31 @@ impl ContextEditor {
} else { } else {
let block_ids = editor.insert_blocks( let block_ids = editor.insert_blocks(
[BlockProperties { [BlockProperties {
position: patch_start,
height: path_count as u32 + 1, height: path_count as u32 + 1,
style: BlockStyle::Flex, style: BlockStyle::Fixed,
render: render_block, render: render_block,
disposition: BlockDisposition::Below, placement: BlockPlacement::Replace(patch_start..patch_end),
priority: 0, priority: 0,
}], }],
None, None,
cx, cx,
); );
let new_crease_ids = editor.insert_creases( // let new_crease_ids = editor.insert_creases(
[Crease::new( // [Crease::new(
patch_start..patch_end, // patch_start..patch_end,
header_placeholder.clone(), // header_placeholder.clone(),
fold_toggle("patch-header"), // fold_toggle("patch-header"),
|_, _, _| Empty.into_any_element(), // |_, _, _| Empty.into_any_element(),
)], // )],
cx, // cx,
); // );
self.patches.insert( self.patches.insert(
range.clone(), range.clone(),
PatchViewState { PatchViewState {
footer_block_id: block_ids[0], block_id: block_ids[0],
crease_id: new_crease_ids[0], // crease_id: new_crease_ids[0],
editor: None, editor: None,
update_task: None, update_task: None,
}, },
@@ -2277,8 +2258,8 @@ impl ContextEditor {
} }
if should_refold { if should_refold {
editor.unfold_ranges([patch_start..patch_end], true, false, cx); // editor.unfold_ranges([patch_start..patch_end], true, false, cx);
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx); // editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
} }
} }
@@ -2731,12 +2712,13 @@ impl ContextEditor {
}) })
}; };
let create_block_properties = |message: &Message| BlockProperties { let create_block_properties = |message: &Message| BlockProperties {
position: buffer
.anchor_in_excerpt(excerpt_id, message.anchor_range.start)
.unwrap(),
height: 2, height: 2,
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
disposition: BlockDisposition::Above, placement: BlockPlacement::Above(
buffer
.anchor_in_excerpt(excerpt_id, message.anchor_range.start)
.unwrap(),
),
priority: usize::MAX, priority: usize::MAX,
render: render_block(MessageMetadata::from(message)), render: render_block(MessageMetadata::from(message)),
}; };
@@ -3372,7 +3354,7 @@ impl ContextEditor {
let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap(); let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
let image = render_image.clone(); let image = render_image.clone();
anchor.is_valid(&buffer).then(|| BlockProperties { anchor.is_valid(&buffer).then(|| BlockProperties {
position: anchor, placement: BlockPlacement::Above(anchor),
height: MAX_HEIGHT_IN_LINES, height: MAX_HEIGHT_IN_LINES,
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
render: Box::new(move |cx| { render: Box::new(move |cx| {
@@ -3393,8 +3375,6 @@ impl ContextEditor {
) )
.into_any_element() .into_any_element()
}), }),
disposition: BlockDisposition::Above,
priority: 0, priority: 0,
}) })
}) })
@@ -3434,28 +3414,7 @@ impl ContextEditor {
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE)) .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
} }
fn render_patch_header( fn render_patch(
&self,
range: Range<text::Anchor>,
_id: FoldId,
cx: &mut ViewContext<Self>,
) -> Option<AnyElement> {
let patch = self.context.read(cx).patch_for_range(&range, cx)?;
let theme = cx.theme().clone();
Some(
h_flex()
.px_1()
.py_0p5()
.border_b_1()
.border_color(theme.status().info_border)
.gap_1()
.child(Icon::new(IconName::Diff).size(IconSize::Small))
.child(Label::new(patch.title.clone()).size(LabelSize::Small))
.into_any(),
)
}
fn render_patch_footer(
&mut self, &mut self,
range: Range<text::Anchor>, range: Range<text::Anchor>,
max_width: Pixels, max_width: Pixels,
@@ -3471,10 +3430,6 @@ impl ContextEditor {
.anchor_in_excerpt(excerpt_id, range.start) .anchor_in_excerpt(excerpt_id, range.start)
.unwrap(); .unwrap();
if !snapshot.intersects_fold(anchor) {
return None;
}
let patch = self.context.read(cx).patch_for_range(&range, cx)?; let patch = self.context.read(cx).patch_for_range(&range, cx)?;
let paths = patch let paths = patch
.paths() .paths()
@@ -3483,10 +3438,13 @@ impl ContextEditor {
Some( Some(
v_flex() v_flex()
.border_1()
.border_color(cx.theme().colors().border)
.id(id) .id(id)
.pl(gutter_width) .ml(gutter_width)
.w(max_width) .p_2()
.py_2() .rounded_md()
.min_h(cx.line_height() * 3.)
.cursor(CursorStyle::PointingHand) .cursor(CursorStyle::PointingHand)
.on_click(cx.listener(move |this, _, cx| { .on_click(cx.listener(move |this, _, cx| {
this.editor.update(cx, |editor, cx| { this.editor.update(cx, |editor, cx| {
@@ -3496,6 +3454,7 @@ impl ContextEditor {
}); });
this.focus_active_patch(cx); this.focus_active_patch(cx);
})) }))
.child(Label::new(patch.title.clone()))
.children(paths.into_iter().map(|path| { .children(paths.into_iter().map(|path| {
h_flex() h_flex()
.pl_1() .pl_1()

View File

@@ -9,7 +9,7 @@ use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{ use editor::{
actions::{MoveDown, MoveUp, SelectAll}, actions::{MoveDown, MoveUp, SelectAll},
display_map::{ display_map::{
BlockContext, BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
ToDisplayPoint, ToDisplayPoint,
}, },
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode, Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorElement, EditorEvent, EditorMode,
@@ -446,15 +446,14 @@ impl InlineAssistant {
let assist_blocks = vec![ let assist_blocks = vec![
BlockProperties { BlockProperties {
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
position: range.start, placement: BlockPlacement::Above(range.start),
height: prompt_editor_height, height: prompt_editor_height,
render: build_assist_editor_renderer(prompt_editor), render: build_assist_editor_renderer(prompt_editor),
disposition: BlockDisposition::Above,
priority: 0, priority: 0,
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
position: range.end, placement: BlockPlacement::Below(range.end),
height: 0, height: 0,
render: Box::new(|cx| { render: Box::new(|cx| {
v_flex() v_flex()
@@ -464,7 +463,6 @@ impl InlineAssistant {
.border_color(cx.theme().status().info_border) .border_color(cx.theme().status().info_border)
.into_any_element() .into_any_element()
}), }),
disposition: BlockDisposition::Below,
priority: 0, priority: 0,
}, },
]; ];
@@ -1179,7 +1177,7 @@ impl InlineAssistant {
let height = let height =
deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1); deleted_lines_editor.update(cx, |editor, cx| editor.max_point(cx).row().0 + 1);
new_blocks.push(BlockProperties { new_blocks.push(BlockProperties {
position: new_row, placement: BlockPlacement::Above(new_row),
height, height,
style: BlockStyle::Flex, style: BlockStyle::Flex,
render: Box::new(move |cx| { render: Box::new(move |cx| {
@@ -1191,7 +1189,6 @@ impl InlineAssistant {
.child(deleted_lines_editor.clone()) .child(deleted_lines_editor.clone())
.into_any_element() .into_any_element()
}), }),
disposition: BlockDisposition::Above,
priority: 0, priority: 0,
}); });
} }

View File

@@ -9,7 +9,7 @@ use anyhow::Result;
use collections::{BTreeSet, HashSet}; use collections::{BTreeSet, HashSet};
use editor::{ use editor::{
diagnostic_block_renderer, diagnostic_block_renderer,
display_map::{BlockDisposition, BlockProperties, BlockStyle, CustomBlockId, RenderBlock}, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},
highlight_diagnostic_message, highlight_diagnostic_message,
scroll::Autoscroll, scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
@@ -439,11 +439,10 @@ impl ProjectDiagnosticsEditor {
primary.message.split('\n').next().unwrap().to_string(); primary.message.split('\n').next().unwrap().to_string();
group_state.block_count += 1; group_state.block_count += 1;
blocks_to_add.push(BlockProperties { blocks_to_add.push(BlockProperties {
position: header_position, placement: BlockPlacement::Above(header_position),
height: 2, height: 2,
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
render: diagnostic_header_renderer(primary), render: diagnostic_header_renderer(primary),
disposition: BlockDisposition::Above,
priority: 0, priority: 0,
}); });
} }
@@ -459,13 +458,15 @@ impl ProjectDiagnosticsEditor {
if !diagnostic.message.is_empty() { if !diagnostic.message.is_empty() {
group_state.block_count += 1; group_state.block_count += 1;
blocks_to_add.push(BlockProperties { 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, height: diagnostic.message.matches('\n').count() as u32 + 1,
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
render: diagnostic_block_renderer( render: diagnostic_block_renderer(
diagnostic, None, true, true, diagnostic, None, true, true,
), ),
disposition: BlockDisposition::Below,
priority: 0, priority: 0,
}); });
} }
@@ -498,13 +499,24 @@ impl ProjectDiagnosticsEditor {
editor.remove_blocks(blocks_to_remove, None, cx); editor.remove_blocks(blocks_to_remove, None, cx);
let block_ids = editor.insert_blocks( let block_ids = editor.insert_blocks(
blocks_to_add.into_iter().flat_map(|block| { 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 { Some(BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor)?, placement,
height: block.height, height: block.height,
style: block.style, style: block.style,
render: block.render, render: block.render,
disposition: block.disposition,
priority: 0, priority: 0,
}) })
}), }),

View File

@@ -29,8 +29,8 @@ use crate::{
hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt, hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt,
}; };
pub use block_map::{ pub use block_map::{
Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockDisposition, BlockId, Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap,
BlockMap, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
}; };
use block_map::{BlockRow, BlockSnapshot}; use block_map::{BlockRow, BlockSnapshot};
use char_map::{CharMap, CharSnapshot}; use char_map::{CharMap, CharSnapshot};
@@ -1180,6 +1180,7 @@ impl ToDisplayPoint for Anchor {
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::{movement, test::marked_display_snapshot}; use crate::{movement, test::marked_display_snapshot};
use block_map::BlockPlacement;
use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla}; use gpui::{div, font, observe, px, AppContext, BorrowAppContext, Context, Element, Hsla};
use language::{ use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent}, language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
@@ -1293,24 +1294,22 @@ pub mod tests {
Bias::Left, Bias::Left,
)); ));
let disposition = if rng.gen() { let placement = if rng.gen() {
BlockDisposition::Above BlockPlacement::Above(position)
} else { } else {
BlockDisposition::Below BlockPlacement::Below(position)
}; };
let height = rng.gen_range(1..5); let height = rng.gen_range(1..5);
log::info!( log::info!(
"inserting block {:?} {:?} with height {}", "inserting block {:?} with height {}",
disposition, placement.as_ref().map(|p| p.to_point(&buffer)),
position.to_point(&buffer),
height height
); );
let priority = rng.gen_range(1..100); let priority = rng.gen_range(1..100);
BlockProperties { BlockProperties {
placement,
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position,
height, height,
disposition,
render: Box::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
priority, priority,
} }

File diff suppressed because it is too large Load Diff

View File

@@ -252,6 +252,7 @@ impl CharSnapshot {
}; };
TabChunks { TabChunks {
snapshot: self,
fold_chunks: self.fold_snapshot.chunks( fold_chunks: self.fold_snapshot.chunks(
input_start..input_end, input_start..input_end,
language_aware, language_aware,
@@ -492,6 +493,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
const SPACES: &str = " "; const SPACES: &str = " ";
pub struct TabChunks<'a> { pub struct TabChunks<'a> {
snapshot: &'a CharSnapshot,
fold_chunks: FoldChunks<'a>, fold_chunks: FoldChunks<'a>,
chunk: Chunk<'a>, chunk: Chunk<'a>,
column: u32, column: u32,
@@ -503,6 +505,37 @@ pub struct TabChunks<'a> {
inside_leading_tab: bool, 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> { impl<'a> Iterator for TabChunks<'a> {
type Item = Chunk<'a>; type Item = Chunk<'a>;

View File

@@ -1100,6 +1100,17 @@ pub struct FoldBufferRows<'a> {
fold_point: FoldPoint, 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> { impl<'a> Iterator for FoldBufferRows<'a> {
type Item = Option<u32>; type Item = Option<u32>;
@@ -1135,6 +1146,38 @@ pub struct FoldChunks<'a> {
max_output_offset: FoldOffset, 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> { impl<'a> Iterator for FoldChunks<'a> {
type Item = Chunk<'a>; type Item = Chunk<'a>;

View File

@@ -56,6 +56,7 @@ pub struct WrapChunks<'a> {
output_position: WrapPoint, output_position: WrapPoint,
max_output_row: u32, max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>, transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>,
snapshot: &'a WrapSnapshot,
} }
#[derive(Clone)] #[derive(Clone)]
@@ -68,6 +69,21 @@ pub struct WrapBufferRows<'a> {
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>, 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 { impl WrapMap {
pub fn new( pub fn new(
char_snapshot: CharSnapshot, char_snapshot: CharSnapshot,
@@ -602,6 +618,7 @@ impl WrapSnapshot {
output_position: output_start, output_position: output_start,
max_output_row: rows.end, max_output_row: rows.end,
transforms, 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> { pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
let mut cursor = self.transforms.cursor::<WrapPoint>(&()); let mut cursor = self.transforms.cursor::<WrapPoint>(&());
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &()); cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &());
@@ -745,6 +823,21 @@ impl WrapSnapshot {
None 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) { fn check_invariants(&self) {
#[cfg(test)] #[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> { impl<'a> Iterator for WrapChunks<'a> {
type Item = Chunk<'a>; type Item = Chunk<'a>;
@@ -1336,19 +1449,6 @@ mod tests {
} }
impl WrapSnapshot { 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) { fn verify_chunks(&mut self, rng: &mut impl Rng) {
for _ in 0..5 { for _ in 0..5 {
let mut end_row = rng.gen_range(0..=self.max_point().row()); let mut end_row = rng.gen_range(0..=self.max_point().row());

View File

@@ -10210,7 +10210,7 @@ impl Editor {
let block_id = this.insert_blocks( let block_id = this.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Flex, style: BlockStyle::Flex,
position: range.start, placement: BlockPlacement::Below(range.start),
height: 1, height: 1,
render: Box::new({ render: Box::new({
let rename_editor = rename_editor.clone(); let rename_editor = rename_editor.clone();
@@ -10246,7 +10246,6 @@ impl Editor {
.into_any_element() .into_any_element()
} }
}), }),
disposition: BlockDisposition::Below,
priority: 0, priority: 0,
}], }],
Some(Autoscroll::fit()), Some(Autoscroll::fit()),
@@ -10531,10 +10530,11 @@ impl Editor {
let message_height = diagnostic.message.matches('\n').count() as u32 + 1; let message_height = diagnostic.message.matches('\n').count() as u32 + 1;
BlockProperties { BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position: buffer.anchor_after(entry.range.start), placement: BlockPlacement::Below(
buffer.anchor_after(entry.range.start),
),
height: message_height, height: message_height,
render: diagnostic_block_renderer(diagnostic, None, true, true), render: diagnostic_block_renderer(diagnostic, None, true, true),
disposition: BlockDisposition::Below,
priority: 0, priority: 0,
} }
}), }),

View File

@@ -3868,8 +3868,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
editor.insert_blocks( editor.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
position: snapshot.anchor_after(Point::new(2, 0)), placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
disposition: BlockDisposition::Below,
height: 1, height: 1,
render: Box::new(|_| div().into_any()), render: Box::new(|_| div().into_any()),
priority: 0, priority: 0,

View File

@@ -2071,7 +2071,7 @@ impl EditorElement {
let mut element = match block { let mut element = match block {
Block::Custom(block) => { Block::Custom(block) => {
let align_to = block let align_to = block
.position() .start()
.to_point(&snapshot.buffer_snapshot) .to_point(&snapshot.buffer_snapshot)
.to_display_point(snapshot); .to_display_point(snapshot);
let anchor_x = text_x let anchor_x = text_x
@@ -6294,7 +6294,7 @@ fn compute_auto_height_layout(
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{
display_map::{BlockDisposition, BlockProperties}, display_map::{BlockPlacement, BlockProperties},
editor_tests::{init_test, update_test_language_settings}, editor_tests::{init_test, update_test_language_settings},
Editor, MultiBuffer, Editor, MultiBuffer,
}; };
@@ -6550,9 +6550,8 @@ mod tests {
editor.insert_blocks( editor.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Fixed, style: BlockStyle::Fixed,
disposition: BlockDisposition::Above, placement: BlockPlacement::Above(Anchor::min()),
height: 3, height: 3,
position: Anchor::min(),
render: Box::new(|cx| div().h(3. * cx.line_height()).into_any()), render: Box::new(|cx| div().h(3. * cx.line_height()).into_any()),
priority: 0, priority: 0,
}], }],

View File

@@ -17,7 +17,7 @@ use workspace::Item;
use crate::{ use crate::{
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyDiffHunk, 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, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, RevertFile,
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
}; };
@@ -417,10 +417,9 @@ impl Editor {
}; };
BlockProperties { BlockProperties {
position: hunk.multi_buffer_range.start, placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
height: 1, height: 1,
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
disposition: BlockDisposition::Above,
priority: 0, priority: 0,
render: Box::new({ render: Box::new({
let editor = cx.view().clone(); let editor = cx.view().clone();
@@ -700,10 +699,9 @@ impl Editor {
let hunk = hunk.clone(); let hunk = hunk.clone();
let height = editor_height.max(deleted_text_height); let height = editor_height.max(deleted_text_height);
BlockProperties { BlockProperties {
position: hunk.multi_buffer_range.start, placement: BlockPlacement::Above(hunk.multi_buffer_range.start),
height, height,
style: BlockStyle::Flex, style: BlockStyle::Flex,
disposition: BlockDisposition::Above,
priority: 0, priority: 0,
render: Box::new(move |cx| { render: Box::new(move |cx| {
let width = EditorElement::diff_hunk_strip_width(cx.line_height()); let width = EditorElement::diff_hunk_strip_width(cx.line_height());

View File

@@ -8,7 +8,7 @@ use client::telemetry::Telemetry;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::{ use editor::{
display_map::{ display_map::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, CustomBlockId, BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId,
RenderBlock, RenderBlock,
}, },
scroll::Autoscroll, scroll::Autoscroll,
@@ -90,12 +90,11 @@ impl EditorBlock {
let invalidation_anchor = buffer.read(cx).read(cx).anchor_before(next_row_start); let invalidation_anchor = buffer.read(cx).read(cx).anchor_before(next_row_start);
let block = BlockProperties { 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 // Take up at least one height for status, allow the editor to determine the real height based on the content from render
height: 1, height: 1,
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()), render: Self::create_output_area_renderer(execution_view.clone(), on_close.clone()),
disposition: BlockDisposition::Below,
priority: 0, priority: 0,
}; };