Introduce a new BlockStyle field for blocks

This new field allows blocks to specify how they want to be laid out:

- If `Fixed` they can take up all the width they want and they will impact
the scroll width of the editor. This is useful for diagnostic messages and
allows scrolling the editor further to the right to visualize the entire message.
- If `Flex` they can extend all the way to the scroll width without impacting it
any further. This is useful for the rename editor that we insert as a block
decoration when hitting `F2`.
- If `Sticky`, they will be as wide as the editor element and won't participate
in the horizontal scrolling of the editor. This is useful for headers in general,
where we want e.g. the filename and the jump button to always be visible
independently of how much the user has scrolled to the right.
This commit is contained in:
Antonio Scandurra 2022-06-10 13:47:40 +02:00
parent 8e440bf7ca
commit 666ea61dbc
5 changed files with 235 additions and 170 deletions

View File

@ -4,7 +4,7 @@ use anyhow::Result;
use collections::{BTreeMap, HashSet}; use collections::{BTreeMap, HashSet};
use editor::{ use editor::{
diagnostic_block_renderer, diagnostic_block_renderer,
display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock}, display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, ExcerptRange, MultiBuffer, highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, ExcerptRange, MultiBuffer,
ToOffset, ToOffset,
}; };
@ -348,6 +348,7 @@ impl ProjectDiagnosticsEditor {
blocks_to_add.push(BlockProperties { blocks_to_add.push(BlockProperties {
position: header_position, position: header_position,
height: 2, height: 2,
style: BlockStyle::Sticky,
render: diagnostic_header_renderer(primary), render: diagnostic_header_renderer(primary),
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
}); });
@ -366,6 +367,7 @@ impl ProjectDiagnosticsEditor {
blocks_to_add.push(BlockProperties { blocks_to_add.push(BlockProperties {
position: (excerpt_id.clone(), entry.range.start.clone()), position: (excerpt_id.clone(), entry.range.start.clone()),
height: diagnostic.message.matches('\n').count() as u8 + 1, height: diagnostic.message.matches('\n').count() as u8 + 1,
style: BlockStyle::Fixed,
render: diagnostic_block_renderer(diagnostic, true), render: diagnostic_block_renderer(diagnostic, true),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
}); });
@ -402,6 +404,7 @@ impl ProjectDiagnosticsEditor {
BlockProperties { BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor), position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
height: block.height, height: block.height,
style: block.style,
render: block.render, render: block.render,
disposition: block.disposition, disposition: block.disposition,
} }
@ -621,7 +624,6 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.with_color(theme.warning_diagnostic.message.text.color) .with_color(theme.warning_diagnostic.message.text.color)
}; };
let x_padding = cx.gutter_padding + cx.scroll_x * cx.em_width;
Flex::row() Flex::row()
.with_child( .with_child(
icon.constrained() icon.constrained()
@ -651,8 +653,8 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
})) }))
.contained() .contained()
.with_style(style.container) .with_style(style.container)
.with_padding_left(x_padding) .with_padding_left(cx.gutter_padding)
.with_padding_right(x_padding) .with_padding_right(cx.gutter_padding)
.expanded() .expanded()
.named("diagnostic header") .named("diagnostic header")
}) })

View File

@ -20,7 +20,7 @@ use wrap_map::WrapMap;
pub use block_map::{ pub use block_map::{
BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
BlockDisposition, BlockId, BlockProperties, RenderBlock, TransformBlock, BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
}; };
pub trait ToDisplayPoint { pub trait ToDisplayPoint {
@ -650,6 +650,7 @@ pub mod tests {
height height
); );
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position, position,
height, height,
disposition, disposition,

View File

@ -56,6 +56,7 @@ pub struct Block {
id: BlockId, id: BlockId,
position: Anchor, position: Anchor,
height: u8, height: u8,
style: BlockStyle,
render: Mutex<RenderBlock>, render: Mutex<RenderBlock>,
disposition: BlockDisposition, disposition: BlockDisposition,
} }
@ -67,10 +68,18 @@ where
{ {
pub position: P, pub position: P,
pub height: u8, pub height: u8,
pub style: BlockStyle,
pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>, pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
pub disposition: BlockDisposition, pub disposition: BlockDisposition,
} }
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum BlockStyle {
Fixed,
Flex,
Sticky,
}
pub struct BlockContext<'a, 'b> { pub struct BlockContext<'a, 'b> {
pub cx: &'b mut RenderContext<'a, crate::Editor>, pub cx: &'b mut RenderContext<'a, crate::Editor>,
pub anchor_x: f32, pub anchor_x: f32,
@ -513,6 +522,7 @@ impl<'a> BlockMapWriter<'a> {
height: block.height, height: block.height,
render: Mutex::new(block.render), render: Mutex::new(block.render),
disposition: block.disposition, disposition: block.disposition,
style: block.style,
}), }),
); );
@ -940,6 +950,10 @@ impl Block {
pub fn position(&self) -> &Anchor { pub fn position(&self) -> &Anchor {
&self.position &self.position
} }
pub fn style(&self) -> BlockStyle {
self.style
}
} }
impl Debug for Block { impl Debug for Block {
@ -1018,18 +1032,21 @@ mod tests {
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default()); let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
let block_ids = writer.insert(vec![ let block_ids = writer.insert(vec![
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 0)), position: buffer_snapshot.anchor_after(Point::new(1, 0)),
height: 1, height: 1,
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 1")), render: Arc::new(|_| Empty::new().named("block 1")),
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 2)), position: buffer_snapshot.anchor_after(Point::new(1, 2)),
height: 2, height: 2,
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 2")), render: Arc::new(|_| Empty::new().named("block 2")),
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(3, 3)), position: buffer_snapshot.anchor_after(Point::new(3, 3)),
height: 3, height: 3,
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
@ -1183,12 +1200,14 @@ mod tests {
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default()); let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
writer.insert(vec![ writer.insert(vec![
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 12)), position: buffer_snapshot.anchor_after(Point::new(1, 12)),
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 1")), render: Arc::new(|_| Empty::new().named("block 1")),
height: 1, height: 1,
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 1)), position: buffer_snapshot.anchor_after(Point::new(1, 1)),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
render: Arc::new(|_| Empty::new().named("block 2")), render: Arc::new(|_| Empty::new().named("block 2")),
@ -1286,6 +1305,7 @@ mod tests {
height height
); );
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position, position,
height, height,
disposition, disposition,

View File

@ -4801,6 +4801,7 @@ impl Editor {
cx.focus(&rename_editor); cx.focus(&rename_editor);
let block_id = this.insert_blocks( let block_id = this.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Flex,
position: range.start.clone(), position: range.start.clone(),
height: 1, height: 1,
render: Arc::new({ render: Arc::new({
@ -4985,6 +4986,7 @@ impl Editor {
let diagnostic = entry.diagnostic.clone(); let diagnostic = entry.diagnostic.clone();
let message_height = diagnostic.message.lines().count() as u8; let message_height = diagnostic.message.lines().count() as u8;
BlockProperties { BlockProperties {
style: BlockStyle::Fixed,
position: buffer.anchor_after(entry.range.start), position: buffer.anchor_after(entry.range.start),
height: message_height, height: message_height,
render: diagnostic_block_renderer(diagnostic, true), render: diagnostic_block_renderer(diagnostic, true),
@ -7932,6 +7934,7 @@ mod tests {
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.insert_blocks( editor.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Fixed,
position: snapshot.anchor_after(Point::new(2, 0)), position: snapshot.anchor_after(Point::new(2, 0)),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
height: 1, height: 1,

View File

@ -3,9 +3,9 @@ use super::{
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase, Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase,
SoftWrap, ToPoint, MAX_LINE_LEN, SoftWrap, ToPoint, MAX_LINE_LEN,
}; };
use crate::hover_popover::HoverAt;
use crate::{ use crate::{
display_map::{DisplaySnapshot, TransformBlock}, display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
hover_popover::HoverAt,
EditorStyle, EditorStyle,
}; };
use clock::ReplicaId; use clock::ReplicaId;
@ -617,10 +617,13 @@ impl EditorElement {
let scroll_left = scroll_position.x() * layout.em_width; let scroll_left = scroll_position.x() * layout.em_width;
let scroll_top = scroll_position.y() * layout.line_height; let scroll_top = scroll_position.y() * layout.line_height;
for (row, element) in &mut layout.blocks { for block in &mut layout.blocks {
let origin = bounds.origin() let mut origin =
+ vec2f(-scroll_left, *row as f32 * layout.line_height - scroll_top); bounds.origin() + vec2f(0., block.row as f32 * layout.line_height - scroll_top);
element.paint(origin, visible_bounds, cx); if !matches!(block.style, BlockStyle::Sticky) {
origin += vec2f(-scroll_left, 0.);
}
block.element.paint(origin, visible_bounds, cx);
} }
} }
@ -789,6 +792,7 @@ impl EditorElement {
rows: Range<u32>, rows: Range<u32>,
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
width: f32, width: f32,
scroll_width: f32,
gutter_padding: f32, gutter_padding: f32,
gutter_width: f32, gutter_width: f32,
em_width: f32, em_width: f32,
@ -797,7 +801,7 @@ impl EditorElement {
style: &EditorStyle, style: &EditorStyle,
line_layouts: &[text_layout::Line], line_layouts: &[text_layout::Line],
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> Vec<(u32, ElementBox)> { ) -> (f32, Vec<BlockLayout>) {
let editor = if let Some(editor) = self.view.upgrade(cx) { let editor = if let Some(editor) = self.view.upgrade(cx) {
editor editor
} else { } else {
@ -806,9 +810,13 @@ impl EditorElement {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let scroll_x = snapshot.scroll_position.x(); let scroll_x = snapshot.scroll_position.x();
snapshot let (fixed_blocks, non_fixed_blocks) = snapshot
.blocks_in_range(rows.clone()) .blocks_in_range(rows.clone())
.map(|(block_row, block)| { .partition::<Vec<_>, _>(|(_, block)| match block {
TransformBlock::ExcerptHeader { .. } => false,
TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
});
let mut render_block = |block: &TransformBlock, width: f32| {
let mut element = match block { let mut element = match block {
TransformBlock::Custom(block) => { TransformBlock::Custom(block) => {
let align_to = block let align_to = block
@ -874,9 +882,7 @@ impl EditorElement {
.boxed() .boxed()
}) })
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click({ .on_click(move |_, _, cx| cx.dispatch_action(jump_action.clone()))
move |_, _, cx| cx.dispatch_action(jump_action.clone())
})
.with_tooltip( .with_tooltip(
*key, *key,
"Jump to Buffer".to_string(), "Jump to Buffer".to_string(),
@ -890,7 +896,6 @@ impl EditorElement {
}) })
}); });
let padding = gutter_padding + scroll_x * em_width;
if *starts_new_buffer { if *starts_new_buffer {
let style = &self.style.diagnostic_path_header; let style = &self.style.diagnostic_path_header;
let font_size = let font_size =
@ -900,8 +905,7 @@ impl EditorElement {
let mut parent_path = None; let mut parent_path = None;
if let Some(file) = buffer.file() { if let Some(file) = buffer.file() {
let path = file.path(); let path = file.path();
filename = filename = path.file_name().map(|f| f.to_string_lossy().to_string());
path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path = parent_path =
path.parent().map(|p| p.to_string_lossy().to_string() + "/"); path.parent().map(|p| p.to_string_lossy().to_string() + "/");
} }
@ -918,10 +922,7 @@ impl EditorElement {
.boxed(), .boxed(),
) )
.with_children(parent_path.map(|path| { .with_children(parent_path.map(|path| {
Label::new( Label::new(path, style.path.text.clone().with_font_size(font_size))
path,
style.path.text.clone().with_font_size(font_size),
)
.contained() .contained()
.with_style(style.path.container) .with_style(style.path.container)
.aligned() .aligned()
@ -930,8 +931,8 @@ impl EditorElement {
.with_children(jump_icon) .with_children(jump_icon)
.contained() .contained()
.with_style(style.container) .with_style(style.container)
.with_padding_left(padding) .with_padding_left(gutter_padding)
.with_padding_right(padding) .with_padding_right(gutter_padding)
.expanded() .expanded()
.named("path header block") .named("path header block")
} else { } else {
@ -940,8 +941,8 @@ impl EditorElement {
.with_child(Label::new("".to_string(), text_style).boxed()) .with_child(Label::new("".to_string(), text_style).boxed())
.with_children(jump_icon) .with_children(jump_icon)
.contained() .contained()
.with_padding_left(padding) .with_padding_left(gutter_padding)
.with_padding_right(padding) .with_padding_right(gutter_padding)
.expanded() .expanded()
.named("collapsed context") .named("collapsed context")
} }
@ -955,9 +956,38 @@ impl EditorElement {
}, },
cx, cx,
); );
(block_row, element) element
}) };
.collect()
let mut max_width = width.max(scroll_width);
let mut blocks = Vec::new();
for (row, block) in fixed_blocks {
let element = render_block(block, f32::INFINITY);
max_width = max_width.max(element.size().x() + em_width);
blocks.push(BlockLayout {
row,
element,
style: BlockStyle::Fixed,
});
}
for (row, block) in non_fixed_blocks {
let style = match block {
TransformBlock::Custom(block) => block.style(),
TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
};
let width = match style {
BlockStyle::Fixed => unreachable!(),
BlockStyle::Sticky => width,
BlockStyle::Flex => max_width,
};
let element = render_block(block, width);
blocks.push(BlockLayout {
row,
element,
style,
});
}
(max_width, blocks)
} }
} }
@ -1146,8 +1176,24 @@ impl Element for EditorElement {
cx.text_layout_cache, cx.text_layout_cache,
) )
.width(); .width();
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x(); let mut scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x();
let em_width = style.text.em_width(cx.font_cache); let em_width = style.text.em_width(cx.font_cache);
let (blocks_max_width, blocks) = self.layout_blocks(
start_row..end_row,
&snapshot,
size.x(),
scroll_width + gutter_width,
gutter_padding,
gutter_width,
em_width,
gutter_width + gutter_margin,
line_height,
&style,
&line_layouts,
cx,
);
scroll_width = scroll_width.max(blocks_max_width - gutter_width);
let max_row = snapshot.max_point().row(); let max_row = snapshot.max_point().row();
let scroll_max = vec2f( let scroll_max = vec2f(
((scroll_width - text_size.x()) / em_width).max(0.0), ((scroll_width - text_size.x()) / em_width).max(0.0),
@ -1246,20 +1292,6 @@ impl Element for EditorElement {
); );
} }
let blocks = self.layout_blocks(
start_row..end_row,
&snapshot,
size.x().max(scroll_width + gutter_width),
gutter_padding,
gutter_width,
em_width,
gutter_width + gutter_margin,
line_height,
&style,
&line_layouts,
cx,
);
( (
size, size,
LayoutState { LayoutState {
@ -1353,8 +1385,8 @@ impl Element for EditorElement {
} }
} }
for (_, block) in &mut layout.blocks { for block in &mut layout.blocks {
if block.dispatch_event(event, cx) { if block.element.dispatch_event(event, cx) {
return true; return true;
} }
} }
@ -1440,7 +1472,7 @@ pub struct LayoutState {
highlighted_rows: Option<Range<u32>>, highlighted_rows: Option<Range<u32>>,
line_layouts: Vec<text_layout::Line>, line_layouts: Vec<text_layout::Line>,
line_number_layouts: Vec<Option<text_layout::Line>>, line_number_layouts: Vec<Option<text_layout::Line>>,
blocks: Vec<(u32, ElementBox)>, blocks: Vec<BlockLayout>,
line_height: f32, line_height: f32,
em_width: f32, em_width: f32,
em_advance: f32, em_advance: f32,
@ -1451,6 +1483,12 @@ pub struct LayoutState {
hover: Option<(DisplayPoint, ElementBox)>, hover: Option<(DisplayPoint, ElementBox)>,
} }
struct BlockLayout {
row: u32,
element: ElementBox,
style: BlockStyle,
}
fn layout_line( fn layout_line(
row: u32, row: u32,
snapshot: &EditorSnapshot, snapshot: &EditorSnapshot,
@ -1763,6 +1801,7 @@ mod tests {
editor.set_placeholder_text("hello", cx); editor.set_placeholder_text("hello", cx);
editor.insert_blocks( editor.insert_blocks(
[BlockProperties { [BlockProperties {
style: BlockStyle::Fixed,
disposition: BlockDisposition::Above, disposition: BlockDisposition::Above,
height: 3, height: 3,
position: Anchor::min(), position: Anchor::min(),