@@ -4,7 +4,7 @@ use anyhow::Result;
use collections::{BTreeMap, HashSet};
use editor::{
diagnostic_block_renderer,
- display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock},
+ display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, ExcerptRange, MultiBuffer,
ToOffset,
};
@@ -348,6 +348,7 @@ impl ProjectDiagnosticsEditor {
blocks_to_add.push(BlockProperties {
position: header_position,
height: 2,
+ style: BlockStyle::Sticky,
render: diagnostic_header_renderer(primary),
disposition: BlockDisposition::Above,
});
@@ -366,6 +367,7 @@ impl ProjectDiagnosticsEditor {
blocks_to_add.push(BlockProperties {
position: (excerpt_id.clone(), entry.range.start.clone()),
height: diagnostic.message.matches('\n').count() as u8 + 1,
+ style: BlockStyle::Fixed,
render: diagnostic_block_renderer(diagnostic, true),
disposition: BlockDisposition::Below,
});
@@ -402,6 +404,7 @@ impl ProjectDiagnosticsEditor {
BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
height: block.height,
+ style: block.style,
render: block.render,
disposition: block.disposition,
}
@@ -621,7 +624,6 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.with_color(theme.warning_diagnostic.message.text.color)
};
- let x_padding = cx.gutter_padding + cx.scroll_x * cx.em_width;
Flex::row()
.with_child(
icon.constrained()
@@ -651,8 +653,8 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
}))
.contained()
.with_style(style.container)
- .with_padding_left(x_padding)
- .with_padding_right(x_padding)
+ .with_padding_left(cx.gutter_padding)
+ .with_padding_right(cx.gutter_padding)
.expanded()
.named("diagnostic header")
})
@@ -56,6 +56,7 @@ pub struct Block {
id: BlockId,
position: Anchor,
height: u8,
+ style: BlockStyle,
render: Mutex<RenderBlock>,
disposition: BlockDisposition,
}
@@ -67,10 +68,18 @@ where
{
pub position: P,
pub height: u8,
+ pub style: BlockStyle,
pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
pub disposition: BlockDisposition,
}
+#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum BlockStyle {
+ Fixed,
+ Flex,
+ Sticky,
+}
+
pub struct BlockContext<'a, 'b> {
pub cx: &'b mut RenderContext<'a, crate::Editor>,
pub anchor_x: f32,
@@ -513,6 +522,7 @@ impl<'a> BlockMapWriter<'a> {
height: block.height,
render: Mutex::new(block.render),
disposition: block.disposition,
+ style: block.style,
}),
);
@@ -940,6 +950,10 @@ impl Block {
pub fn position(&self) -> &Anchor {
&self.position
}
+
+ pub fn style(&self) -> BlockStyle {
+ self.style
+ }
}
impl Debug for Block {
@@ -1018,18 +1032,21 @@ mod tests {
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
let block_ids = writer.insert(vec![
BlockProperties {
+ style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 0)),
height: 1,
disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 1")),
},
BlockProperties {
+ style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 2)),
height: 2,
disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 2")),
},
BlockProperties {
+ style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(3, 3)),
height: 3,
disposition: BlockDisposition::Below,
@@ -1183,12 +1200,14 @@ mod tests {
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
writer.insert(vec![
BlockProperties {
+ style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 12)),
disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 1")),
height: 1,
},
BlockProperties {
+ style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 1)),
disposition: BlockDisposition::Below,
render: Arc::new(|_| Empty::new().named("block 2")),
@@ -1286,6 +1305,7 @@ mod tests {
height
);
BlockProperties {
+ style: BlockStyle::Fixed,
position,
height,
disposition,
@@ -3,9 +3,9 @@ use super::{
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase,
SoftWrap, ToPoint, MAX_LINE_LEN,
};
-use crate::hover_popover::HoverAt;
use crate::{
- display_map::{DisplaySnapshot, TransformBlock},
+ display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
+ hover_popover::HoverAt,
EditorStyle,
};
use clock::ReplicaId;
@@ -617,10 +617,13 @@ impl EditorElement {
let scroll_left = scroll_position.x() * layout.em_width;
let scroll_top = scroll_position.y() * layout.line_height;
- for (row, element) in &mut layout.blocks {
- let origin = bounds.origin()
- + vec2f(-scroll_left, *row as f32 * layout.line_height - scroll_top);
- element.paint(origin, visible_bounds, cx);
+ for block in &mut layout.blocks {
+ let mut origin =
+ bounds.origin() + vec2f(0., block.row as f32 * layout.line_height - scroll_top);
+ if !matches!(block.style, BlockStyle::Sticky) {
+ origin += vec2f(-scroll_left, 0.);
+ }
+ block.element.paint(origin, visible_bounds, cx);
}
}
@@ -788,7 +791,8 @@ impl EditorElement {
&mut self,
rows: Range<u32>,
snapshot: &EditorSnapshot,
- width: f32,
+ editor_width: f32,
+ scroll_width: f32,
gutter_padding: f32,
gutter_width: f32,
em_width: f32,
@@ -797,7 +801,7 @@ impl EditorElement {
style: &EditorStyle,
line_layouts: &[text_layout::Line],
cx: &mut LayoutContext,
- ) -> Vec<(u32, ElementBox)> {
+ ) -> (f32, Vec<BlockLayout>) {
let editor = if let Some(editor) = self.view.upgrade(cx) {
editor
} else {
@@ -806,158 +810,189 @@ impl EditorElement {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let scroll_x = snapshot.scroll_position.x();
- snapshot
+ let (fixed_blocks, non_fixed_blocks) = snapshot
.blocks_in_range(rows.clone())
- .map(|(block_row, block)| {
- let mut element = match block {
- TransformBlock::Custom(block) => {
- let align_to = block
- .position()
- .to_point(&snapshot.buffer_snapshot)
- .to_display_point(snapshot);
- let anchor_x = text_x
- + if rows.contains(&align_to.row()) {
- line_layouts[(align_to.row() - rows.start) as usize]
- .x_for_index(align_to.column() as usize)
- } else {
- layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
- .x_for_index(align_to.column() as usize)
- };
+ .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 {
+ TransformBlock::Custom(block) => {
+ let align_to = block
+ .position()
+ .to_point(&snapshot.buffer_snapshot)
+ .to_display_point(snapshot);
+ let anchor_x = text_x
+ + if rows.contains(&align_to.row()) {
+ line_layouts[(align_to.row() - rows.start) as usize]
+ .x_for_index(align_to.column() as usize)
+ } else {
+ layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
+ .x_for_index(align_to.column() as usize)
+ };
+
+ cx.render(&editor, |_, cx| {
+ block.render(&mut BlockContext {
+ cx,
+ anchor_x,
+ gutter_padding,
+ line_height,
+ scroll_x,
+ gutter_width,
+ em_width,
+ })
+ })
+ }
+ TransformBlock::ExcerptHeader {
+ key,
+ buffer,
+ range,
+ starts_new_buffer,
+ ..
+ } => {
+ let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
+ let jump_position = range
+ .primary
+ .as_ref()
+ .map_or(range.context.start, |primary| primary.start);
+ let jump_action = crate::Jump {
+ path: ProjectPath {
+ worktree_id: file.worktree_id(cx),
+ path: file.path.clone(),
+ },
+ position: language::ToPoint::to_point(&jump_position, buffer),
+ anchor: jump_position,
+ };
+ enum JumpIcon {}
cx.render(&editor, |_, cx| {
- block.render(&mut BlockContext {
- cx,
- anchor_x,
- gutter_padding,
- line_height,
- scroll_x,
- gutter_width,
- em_width,
+ MouseEventHandler::new::<JumpIcon, _, _>(*key, cx, |state, _| {
+ let style = style.jump_icon.style_for(state, false);
+ Svg::new("icons/jump.svg")
+ .with_color(style.color)
+ .constrained()
+ .with_width(style.icon_width)
+ .aligned()
+ .contained()
+ .with_style(style.container)
+ .constrained()
+ .with_width(style.button_width)
+ .with_height(style.button_width)
+ .boxed()
})
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(move |_, _, cx| cx.dispatch_action(jump_action.clone()))
+ .with_tooltip(
+ *key,
+ "Jump to Buffer".to_string(),
+ Some(Box::new(crate::OpenExcerpts)),
+ tooltip_style.clone(),
+ cx,
+ )
+ .aligned()
+ .flex_float()
+ .boxed()
})
- }
- TransformBlock::ExcerptHeader {
- key,
- buffer,
- range,
- starts_new_buffer,
- ..
- } => {
- let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
- let jump_position = range
- .primary
- .as_ref()
- .map_or(range.context.start, |primary| primary.start);
- let jump_action = crate::Jump {
- path: ProjectPath {
- worktree_id: file.worktree_id(cx),
- path: file.path.clone(),
- },
- position: language::ToPoint::to_point(&jump_position, buffer),
- anchor: jump_position,
- };
+ });
- enum JumpIcon {}
- cx.render(&editor, |_, cx| {
- MouseEventHandler::new::<JumpIcon, _, _>(*key, cx, |state, _| {
- let style = style.jump_icon.style_for(state, false);
- Svg::new("icons/jump.svg")
- .with_color(style.color)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .contained()
- .with_style(style.container)
- .constrained()
- .with_width(style.button_width)
- .with_height(style.button_width)
- .boxed()
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click({
- move |_, _, cx| cx.dispatch_action(jump_action.clone())
- })
- .with_tooltip(
- *key,
- "Jump to Buffer".to_string(),
- Some(Box::new(crate::OpenExcerpts)),
- tooltip_style.clone(),
- cx,
- )
- .aligned()
- .flex_float()
- .boxed()
- })
- });
+ if *starts_new_buffer {
+ let style = &self.style.diagnostic_path_header;
+ let font_size =
+ (style.text_scale_factor * self.style.text.font_size).round();
+
+ let mut filename = None;
+ let mut parent_path = None;
+ if let Some(file) = buffer.file() {
+ let path = file.path();
+ filename = path.file_name().map(|f| f.to_string_lossy().to_string());
+ parent_path =
+ path.parent().map(|p| p.to_string_lossy().to_string() + "/");
+ }
- let padding = gutter_padding + scroll_x * em_width;
- if *starts_new_buffer {
- let style = &self.style.diagnostic_path_header;
- let font_size =
- (style.text_scale_factor * self.style.text.font_size).round();
-
- let mut filename = None;
- let mut parent_path = None;
- if let Some(file) = buffer.file() {
- let path = file.path();
- filename =
- path.file_name().map(|f| f.to_string_lossy().to_string());
- parent_path =
- path.parent().map(|p| p.to_string_lossy().to_string() + "/");
- }
-
- Flex::row()
- .with_child(
- Label::new(
- filename.unwrap_or_else(|| "untitled".to_string()),
- style.filename.text.clone().with_font_size(font_size),
- )
- .contained()
- .with_style(style.filename.container)
- .aligned()
- .boxed(),
+ Flex::row()
+ .with_child(
+ Label::new(
+ filename.unwrap_or_else(|| "untitled".to_string()),
+ style.filename.text.clone().with_font_size(font_size),
)
- .with_children(parent_path.map(|path| {
- Label::new(
- path,
- style.path.text.clone().with_font_size(font_size),
- )
+ .contained()
+ .with_style(style.filename.container)
+ .aligned()
+ .boxed(),
+ )
+ .with_children(parent_path.map(|path| {
+ Label::new(path, style.path.text.clone().with_font_size(font_size))
.contained()
.with_style(style.path.container)
.aligned()
.boxed()
- }))
- .with_children(jump_icon)
- .contained()
- .with_style(style.container)
- .with_padding_left(padding)
- .with_padding_right(padding)
- .expanded()
- .named("path header block")
- } else {
- let text_style = self.style.text.clone();
- Flex::row()
- .with_child(Label::new("…".to_string(), text_style).boxed())
- .with_children(jump_icon)
- .contained()
- .with_padding_left(padding)
- .with_padding_right(padding)
- .expanded()
- .named("collapsed context")
- }
+ }))
+ .with_children(jump_icon)
+ .contained()
+ .with_style(style.container)
+ .with_padding_left(gutter_padding)
+ .with_padding_right(gutter_padding)
+ .expanded()
+ .named("path header block")
+ } else {
+ let text_style = self.style.text.clone();
+ Flex::row()
+ .with_child(Label::new("…".to_string(), text_style).boxed())
+ .with_children(jump_icon)
+ .contained()
+ .with_padding_left(gutter_padding)
+ .with_padding_right(gutter_padding)
+ .expanded()
+ .named("collapsed context")
}
- };
+ }
+ };
- element.layout(
- SizeConstraint {
- min: Vector2F::zero(),
- max: vec2f(width, block.height() as f32 * line_height),
- },
- cx,
- );
- (block_row, element)
- })
- .collect()
+ element.layout(
+ SizeConstraint {
+ min: Vector2F::zero(),
+ max: vec2f(width, block.height() as f32 * line_height),
+ },
+ cx,
+ );
+ element
+ };
+
+ let mut fixed_block_max_width = 0f32;
+ let mut blocks = Vec::new();
+ for (row, block) in fixed_blocks {
+ let element = render_block(block, f32::INFINITY);
+ fixed_block_max_width = fixed_block_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::Sticky => editor_width,
+ BlockStyle::Flex => editor_width
+ .max(fixed_block_max_width)
+ .max(gutter_width + scroll_width),
+ BlockStyle::Fixed => unreachable!(),
+ };
+ let element = render_block(block, width);
+ blocks.push(BlockLayout {
+ row,
+ element,
+ style,
+ });
+ }
+ (
+ scroll_width.max(fixed_block_max_width - gutter_width),
+ blocks,
+ )
}
}
@@ -1148,6 +1183,21 @@ impl Element for EditorElement {
.width();
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x();
let em_width = style.text.em_width(cx.font_cache);
+ let (scroll_width, blocks) = self.layout_blocks(
+ start_row..end_row,
+ &snapshot,
+ size.x(),
+ scroll_width,
+ gutter_padding,
+ gutter_width,
+ em_width,
+ gutter_width + gutter_margin,
+ line_height,
+ &style,
+ &line_layouts,
+ cx,
+ );
+
let max_row = snapshot.max_point().row();
let scroll_max = vec2f(
((scroll_width - text_size.x()) / em_width).max(0.0),
@@ -1246,20 +1296,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,
LayoutState {
@@ -1353,8 +1389,8 @@ impl Element for EditorElement {
}
}
- for (_, block) in &mut layout.blocks {
- if block.dispatch_event(event, cx) {
+ for block in &mut layout.blocks {
+ if block.element.dispatch_event(event, cx) {
return true;
}
}
@@ -1440,7 +1476,7 @@ pub struct LayoutState {
highlighted_rows: Option<Range<u32>>,
line_layouts: Vec<text_layout::Line>,
line_number_layouts: Vec<Option<text_layout::Line>>,
- blocks: Vec<(u32, ElementBox)>,
+ blocks: Vec<BlockLayout>,
line_height: f32,
em_width: f32,
em_advance: f32,
@@ -1451,6 +1487,12 @@ pub struct LayoutState {
hover: Option<(DisplayPoint, ElementBox)>,
}
+struct BlockLayout {
+ row: u32,
+ element: ElementBox,
+ style: BlockStyle,
+}
+
fn layout_line(
row: u32,
snapshot: &EditorSnapshot,
@@ -1763,6 +1805,7 @@ mod tests {
editor.set_placeholder_text("hello", cx);
editor.insert_blocks(
[BlockProperties {
+ style: BlockStyle::Fixed,
disposition: BlockDisposition::Above,
height: 3,
position: Anchor::min(),