Allow editor blocks to replace ranges of text (#19531)
This PR adds the ability for editor blocks to replace lines of text, but does not yet use that feature anywhere. We'll update assistant patches to use replace blocks on another branch: https://github.com/zed-industries/zed/tree/assistant-patch-replace-blocks Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra <me@as-cii.com> Co-authored-by: Richard Feldman <richard@zed.dev> Co-authored-by: Marshall Bowers <marshall@zed.dev> Co-authored-by: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
3617873431
commit
08a3c54bac
13 changed files with 1118 additions and 599 deletions
|
@ -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,
|
||||||
|
@ -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,
|
||||||
}),
|
}),
|
||||||
|
@ -2242,11 +2241,10 @@ 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::Flex,
|
||||||
render: render_block,
|
render: render_block,
|
||||||
disposition: BlockDisposition::Below,
|
placement: BlockPlacement::Below(patch_start),
|
||||||
priority: 0,
|
priority: 0,
|
||||||
}],
|
}],
|
||||||
None,
|
None,
|
||||||
|
@ -2731,12 +2729,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 +3371,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 +3392,6 @@ impl ContextEditor {
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
disposition: BlockDisposition::Above,
|
|
||||||
priority: 0,
|
priority: 0,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -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
|
@ -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>;
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue