Side by side diff spacer polish (#50182)
Co-authored-by: Cole Miller <cole@zed.dev>
This commit is contained in:
1 parent
c56dcef972
commit
df7b08f695
4 files changed
+277
-92
No files matched your search
@@ -265,6 +265,10 @@ impl<P: Debug> Debug for BlockProperties<P> {
|
||||
pub enum BlockStyle {
|
||||
Fixed,
|
||||
Flex,
|
||||
/// Like `Flex` but doesn't use the gutter:
|
||||
/// - block content scrolls with buffer content
|
||||
/// - doesn't paint in gutter
|
||||
Spacer,
|
||||
Sticky,
|
||||
}
|
||||
|
||||
@@ -272,6 +276,7 @@ pub enum BlockStyle {
|
||||
pub struct EditorMargins {
|
||||
pub gutter: GutterDimensions,
|
||||
pub right: Pixels,
|
||||
pub extended_right: Pixels,
|
||||
}
|
||||
|
||||
#[derive(gpui::AppContext, gpui::VisualContext)]
|
||||
@@ -289,6 +294,7 @@ pub struct BlockContext<'a, 'b> {
|
||||
pub height: u32,
|
||||
pub selected: bool,
|
||||
pub editor_style: &'b EditorStyle,
|
||||
pub indent_guide_padding: Pixels,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
|
||||
@@ -393,8 +399,8 @@ impl Block {
|
||||
Block::Custom(block) => block.style,
|
||||
Block::ExcerptBoundary { .. }
|
||||
| Block::FoldedBuffer { .. }
|
||||
| Block::BufferHeader { .. }
|
||||
| Block::Spacer { .. } => BlockStyle::Sticky,
|
||||
| Block::BufferHeader { .. } => BlockStyle::Sticky,
|
||||
Block::Spacer { .. } => BlockStyle::Spacer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1702,12 +1708,13 @@ pub(crate) fn balancing_block(
|
||||
Some(BlockProperties {
|
||||
placement: their_placement,
|
||||
height: my_block.height,
|
||||
style: BlockStyle::Sticky,
|
||||
style: BlockStyle::Spacer,
|
||||
render: Arc::new(move |cx| {
|
||||
crate::EditorElement::render_spacer_block(
|
||||
cx.block_id,
|
||||
cx.height,
|
||||
cx.line_height,
|
||||
cx.indent_guide_padding,
|
||||
cx.window,
|
||||
cx.app,
|
||||
)
|
||||
|
||||
+250
-88
@@ -47,8 +47,8 @@ use gpui::{
|
||||
MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, PaintQuad, ParentElement,
|
||||
Pixels, PressureStage, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString,
|
||||
Size, StatefulInteractiveElement, Style, Styled, StyledText, TextAlign, TextRun,
|
||||
TextStyleRefinement, WeakEntity, Window, anchored, checkerboard, deferred, div, fill,
|
||||
linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background,
|
||||
TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill, linear_color_stop,
|
||||
linear_gradient, outline, pattern_slash, point, px, quad, relative, size, solid_background,
|
||||
transparent_black,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@@ -186,7 +186,10 @@ impl SelectionLayout {
|
||||
|
||||
#[derive(Default)]
|
||||
struct RenderBlocksOutput {
|
||||
blocks: Vec<BlockLayout>,
|
||||
// We store spacer blocks separately because they paint in a different order
|
||||
// (spacers -> indent guides -> non-spacers)
|
||||
non_spacer_blocks: Vec<BlockLayout>,
|
||||
spacer_blocks: Vec<BlockLayout>,
|
||||
row_block_types: HashMap<DisplayRow, bool>,
|
||||
resized_blocks: Option<HashMap<CustomBlockId, u32>>,
|
||||
}
|
||||
@@ -2970,11 +2973,12 @@ impl EditorElement {
|
||||
- scroll_pixel_position.x,
|
||||
);
|
||||
if start_x >= text_origin.x {
|
||||
let (offset_y, length) = Self::calculate_indent_guide_bounds(
|
||||
indent_guide.start_row..indent_guide.end_row,
|
||||
line_height,
|
||||
snapshot,
|
||||
);
|
||||
let (offset_y, length, display_row_range) =
|
||||
Self::calculate_indent_guide_bounds(
|
||||
indent_guide.start_row..indent_guide.end_row,
|
||||
line_height,
|
||||
snapshot,
|
||||
);
|
||||
|
||||
let start_y = Pixels::from(
|
||||
ScrollOffset::from(content_origin.y) + offset_y
|
||||
@@ -2985,6 +2989,7 @@ impl EditorElement {
|
||||
origin: point(start_x, start_y),
|
||||
length,
|
||||
single_indent_width,
|
||||
display_row_range,
|
||||
depth: indent_guide.depth,
|
||||
active: active_indent_guide_indices.contains(&i),
|
||||
settings: indent_guide.settings,
|
||||
@@ -2997,6 +3002,22 @@ impl EditorElement {
|
||||
)
|
||||
}
|
||||
|
||||
fn depth_zero_indent_guide_padding_for_row(
|
||||
indent_guides: &[IndentGuideLayout],
|
||||
row: DisplayRow,
|
||||
) -> Pixels {
|
||||
indent_guides
|
||||
.iter()
|
||||
.find(|guide| guide.depth == 0 && guide.display_row_range.contains(&row))
|
||||
.and_then(|guide| {
|
||||
guide
|
||||
.settings
|
||||
.visible_line_width(guide.active)
|
||||
.map(|width| px(width as f32 * 2.0))
|
||||
})
|
||||
.unwrap_or(px(0.0))
|
||||
}
|
||||
|
||||
fn layout_wrap_guides(
|
||||
&self,
|
||||
em_advance: Pixels,
|
||||
@@ -3034,11 +3055,11 @@ impl EditorElement {
|
||||
row_range: Range<MultiBufferRow>,
|
||||
line_height: Pixels,
|
||||
snapshot: &DisplaySnapshot,
|
||||
) -> (f64, gpui::Pixels) {
|
||||
) -> (f64, gpui::Pixels, Range<DisplayRow>) {
|
||||
let start_point = Point::new(row_range.start.0, 0);
|
||||
let end_point = Point::new(row_range.end.0, 0);
|
||||
|
||||
let row_range = start_point.to_display_point(snapshot).row()
|
||||
let mut row_range = start_point.to_display_point(snapshot).row()
|
||||
..end_point.to_display_point(snapshot).row();
|
||||
|
||||
let mut prev_line = start_point;
|
||||
@@ -3076,6 +3097,7 @@ impl EditorElement {
|
||||
if !found_excerpt_header {
|
||||
offset_y -= block_offset as f64 * f64::from(line_height);
|
||||
length += block_height as f32 * line_height;
|
||||
row_range = DisplayRow(row_range.start.0.saturating_sub(block_offset))..row_range.end;
|
||||
}
|
||||
|
||||
// If there is a block (e.g. diagnostic) at the end of an multibuffer excerpt,
|
||||
@@ -3093,9 +3115,11 @@ impl EditorElement {
|
||||
}
|
||||
if found_excerpt_header {
|
||||
length -= block_height as f32 * line_height;
|
||||
} else {
|
||||
row_range = row_range.start..cons_line;
|
||||
}
|
||||
|
||||
(offset_y, length)
|
||||
(offset_y, length, row_range)
|
||||
}
|
||||
|
||||
fn layout_breakpoints(
|
||||
@@ -3857,6 +3881,7 @@ impl EditorElement {
|
||||
latest_selection_anchors: &HashMap<BufferId, Anchor>,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
indent_guides: &Option<Vec<IndentGuideLayout>>,
|
||||
block_resize_offset: &mut i32,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -3908,19 +3933,30 @@ impl EditorElement {
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.child(custom.render(&mut BlockContext {
|
||||
window,
|
||||
app: cx,
|
||||
anchor_x,
|
||||
margins: editor_margins,
|
||||
line_height,
|
||||
em_width,
|
||||
block_id,
|
||||
height: custom.height.unwrap_or(1),
|
||||
selected,
|
||||
max_width: text_hitbox.size.width.max(*scroll_width),
|
||||
editor_style: &self.style,
|
||||
}))
|
||||
.child(
|
||||
custom.render(&mut BlockContext {
|
||||
window,
|
||||
app: cx,
|
||||
anchor_x,
|
||||
margins: editor_margins,
|
||||
line_height,
|
||||
em_width,
|
||||
block_id,
|
||||
height: custom.height.unwrap_or(1),
|
||||
selected,
|
||||
max_width: text_hitbox.size.width.max(*scroll_width),
|
||||
editor_style: &self.style,
|
||||
indent_guide_padding: indent_guides
|
||||
.as_ref()
|
||||
.map(|guides| {
|
||||
Self::depth_zero_indent_guide_padding_for_row(
|
||||
guides,
|
||||
block_row_start,
|
||||
)
|
||||
})
|
||||
.unwrap_or(px(0.0)),
|
||||
}),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@@ -4008,7 +4044,20 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
Block::Spacer { height, .. } => {
|
||||
Self::render_spacer_block(block_id, *height, line_height, window, cx)
|
||||
let indent_guide_padding = indent_guides
|
||||
.as_ref()
|
||||
.map(|guides| {
|
||||
Self::depth_zero_indent_guide_padding_for_row(guides, block_row_start)
|
||||
})
|
||||
.unwrap_or(px(0.0));
|
||||
Self::render_spacer_block(
|
||||
block_id,
|
||||
*height,
|
||||
line_height,
|
||||
indent_guide_padding,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4070,10 +4119,13 @@ impl EditorElement {
|
||||
Some((element, final_size, row, x_offset))
|
||||
}
|
||||
|
||||
/// The checkerboard pattern height must be an even factor of the line
|
||||
/// height, so that two consecutive spacer blocks can render contiguously
|
||||
/// without an obvious break in the pattern.
|
||||
fn checkerboard_size(line_height: f32, target_height: f32) -> f32 {
|
||||
/// The spacer pattern period must be an even factor of the line height, so
|
||||
/// that two consecutive spacer blocks can render contiguously without an
|
||||
/// obvious break in the pattern.
|
||||
///
|
||||
/// Two consecutive spacers can appear when the other side has a diff hunk
|
||||
/// and a custom block next to each other (e.g. merge conflict buttons).
|
||||
fn spacer_pattern_period(line_height: f32, target_height: f32) -> f32 {
|
||||
let k_approx = line_height / (2.0 * target_height);
|
||||
let k_floor = (k_approx.floor() as u32).max(1);
|
||||
let k_ceil = (k_approx.ceil() as u32).max(1);
|
||||
@@ -4092,24 +4144,39 @@ impl EditorElement {
|
||||
block_id: BlockId,
|
||||
block_height: u32,
|
||||
line_height: Pixels,
|
||||
indent_guide_padding: Pixels,
|
||||
window: &mut Window,
|
||||
cx: &App,
|
||||
) -> AnyElement {
|
||||
let target_size = 16.0;
|
||||
let scale = window.scale_factor();
|
||||
let pattern_size =
|
||||
Self::spacer_pattern_period(f32::from(line_height) * scale, target_size * scale);
|
||||
let color = cx.theme().colors().panel_background;
|
||||
let background = pattern_slash(color, 2.0, pattern_size - 2.0);
|
||||
|
||||
div()
|
||||
.id(block_id)
|
||||
.w_full()
|
||||
.h((block_height as f32) * line_height)
|
||||
// the checkerboard pattern is semi-transparent, so we render a
|
||||
// solid background to prevent indent guides peeking through
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(div().flex_shrink_0().w(indent_guide_padding).h_full())
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.bg(checkerboard(cx.theme().colors().panel_background, {
|
||||
let target_size = 16.0;
|
||||
let scale = window.scale_factor();
|
||||
Self::checkerboard_size(f32::from(line_height) * scale, target_size * scale)
|
||||
})),
|
||||
.flex_1()
|
||||
.h_full()
|
||||
.relative()
|
||||
.overflow_x_hidden()
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.right_0()
|
||||
.left(-indent_guide_padding)
|
||||
.bg(background),
|
||||
),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
@@ -4154,6 +4221,7 @@ impl EditorElement {
|
||||
latest_selection_anchors: &HashMap<BufferId, Anchor>,
|
||||
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
|
||||
sticky_header_excerpt_id: Option<ExcerptId>,
|
||||
indent_guides: &Option<Vec<IndentGuideLayout>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> RenderBlocksOutput {
|
||||
@@ -4166,6 +4234,7 @@ impl EditorElement {
|
||||
.update(cx, |editor, _| editor.take_focused_block());
|
||||
let mut fixed_block_max_width = Pixels::ZERO;
|
||||
let mut blocks = Vec::new();
|
||||
let mut spacer_blocks = Vec::new();
|
||||
let mut resized_blocks = HashMap::default();
|
||||
let mut row_block_types = HashMap::default();
|
||||
let mut block_resize_offset: i32 = 0;
|
||||
@@ -4199,6 +4268,7 @@ impl EditorElement {
|
||||
latest_selection_anchors,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
indent_guides,
|
||||
&mut block_resize_offset,
|
||||
window,
|
||||
cx,
|
||||
@@ -4226,7 +4296,15 @@ impl EditorElement {
|
||||
.size
|
||||
.width
|
||||
.max(fixed_block_max_width)
|
||||
.max(editor_margins.gutter.width + *scroll_width)
|
||||
.max(
|
||||
editor_margins.gutter.width + *scroll_width + editor_margins.extended_right,
|
||||
)
|
||||
.into(),
|
||||
(BlockStyle::Spacer, _) => hitbox
|
||||
.size
|
||||
.width
|
||||
.max(fixed_block_max_width)
|
||||
.max(*scroll_width + editor_margins.extended_right)
|
||||
.into(),
|
||||
(BlockStyle::Fixed, _) => unreachable!(),
|
||||
};
|
||||
@@ -4258,20 +4336,26 @@ impl EditorElement {
|
||||
latest_selection_anchors,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
indent_guides,
|
||||
&mut block_resize_offset,
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
blocks.push(BlockLayout {
|
||||
let layout = BlockLayout {
|
||||
id: block_id,
|
||||
x_offset,
|
||||
row: Some(row),
|
||||
element,
|
||||
available_space: size(width, element_size.height.into()),
|
||||
style,
|
||||
overlaps_gutter: !block.place_near(),
|
||||
overlaps_gutter: !block.place_near() && style != BlockStyle::Spacer,
|
||||
is_buffer_header: block.is_buffer_header(),
|
||||
});
|
||||
};
|
||||
if style == BlockStyle::Spacer {
|
||||
spacer_blocks.push(layout);
|
||||
} else {
|
||||
blocks.push(layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4283,12 +4367,17 @@ impl EditorElement {
|
||||
let style = block.style();
|
||||
let width = match style {
|
||||
BlockStyle::Fixed => AvailableSpace::MinContent,
|
||||
BlockStyle::Flex => AvailableSpace::Definite(
|
||||
BlockStyle::Flex => {
|
||||
AvailableSpace::Definite(hitbox.size.width.max(fixed_block_max_width).max(
|
||||
editor_margins.gutter.width + *scroll_width + editor_margins.extended_right,
|
||||
))
|
||||
}
|
||||
BlockStyle::Spacer => AvailableSpace::Definite(
|
||||
hitbox
|
||||
.size
|
||||
.width
|
||||
.max(fixed_block_max_width)
|
||||
.max(editor_margins.gutter.width + *scroll_width),
|
||||
.max(*scroll_width + editor_margins.extended_right),
|
||||
),
|
||||
BlockStyle::Sticky => AvailableSpace::Definite(hitbox.size.width),
|
||||
};
|
||||
@@ -4315,6 +4404,7 @@ impl EditorElement {
|
||||
latest_selection_anchors,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
indent_guides,
|
||||
&mut block_resize_offset,
|
||||
window,
|
||||
cx,
|
||||
@@ -4338,7 +4428,8 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
RenderBlocksOutput {
|
||||
blocks,
|
||||
non_spacer_blocks: blocks,
|
||||
spacer_blocks,
|
||||
row_block_types,
|
||||
resized_blocks: (!resized_blocks.is_empty()).then_some(resized_blocks),
|
||||
}
|
||||
@@ -4348,9 +4439,11 @@ impl EditorElement {
|
||||
&self,
|
||||
blocks: &mut Vec<BlockLayout>,
|
||||
hitbox: &Hitbox,
|
||||
gutter_hitbox: &Hitbox,
|
||||
line_height: Pixels,
|
||||
scroll_position: gpui::Point<ScrollOffset>,
|
||||
scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
|
||||
editor_margins: &EditorMargins,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
@@ -4369,6 +4462,13 @@ impl EditorElement {
|
||||
hitbox.origin + point(Pixels::ZERO, hitbox.size.height)
|
||||
};
|
||||
|
||||
if block.style == BlockStyle::Spacer {
|
||||
origin += point(
|
||||
gutter_hitbox.size.width + editor_margins.gutter.margin,
|
||||
Pixels::ZERO,
|
||||
);
|
||||
}
|
||||
|
||||
if !matches!(block.style, BlockStyle::Sticky) {
|
||||
origin += point(Pixels::from(-scroll_pixel_position.x), Pixels::ZERO);
|
||||
}
|
||||
@@ -6023,22 +6123,18 @@ impl EditorElement {
|
||||
)),
|
||||
};
|
||||
|
||||
let requested_line_width = if indent_guide.active {
|
||||
settings.active_line_width
|
||||
} else {
|
||||
settings.line_width
|
||||
}
|
||||
.clamp(1, 10);
|
||||
let mut line_indicator_width = 0.;
|
||||
if let Some(color) = line_color {
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: indent_guide.origin,
|
||||
size: size(px(requested_line_width as f32), indent_guide.length),
|
||||
},
|
||||
color,
|
||||
));
|
||||
line_indicator_width = requested_line_width as f32;
|
||||
if let Some(requested_line_width) = settings.visible_line_width(indent_guide.active) {
|
||||
if let Some(color) = line_color {
|
||||
window.paint_quad(fill(
|
||||
Bounds {
|
||||
origin: indent_guide.origin,
|
||||
size: size(px(requested_line_width as f32), indent_guide.length),
|
||||
},
|
||||
color,
|
||||
));
|
||||
line_indicator_width = requested_line_width as f32;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(color) = background_color {
|
||||
@@ -7468,7 +7564,27 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_blocks(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) {
|
||||
fn paint_spacer_blocks(
|
||||
&mut self,
|
||||
layout: &mut EditorLayout,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
for mut block in layout.spacer_blocks.drain(..) {
|
||||
let mut bounds = layout.hitbox.bounds;
|
||||
bounds.origin.x += layout.gutter_hitbox.bounds.size.width;
|
||||
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
block.element.paint(window, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_non_spacer_blocks(
|
||||
&mut self,
|
||||
layout: &mut EditorLayout,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
for mut block in layout.blocks.drain(..) {
|
||||
if block.overlaps_gutter {
|
||||
block.element.paint(window, cx);
|
||||
@@ -9591,11 +9707,12 @@ impl Element for EditorElement {
|
||||
|
||||
let right_margin = minimap_width + vertical_scrollbar_width;
|
||||
|
||||
let editor_width =
|
||||
text_width - gutter_dimensions.margin - 2 * em_width - right_margin;
|
||||
let extended_right = 2 * em_width + right_margin;
|
||||
let editor_width = text_width - gutter_dimensions.margin - extended_right;
|
||||
let editor_margins = EditorMargins {
|
||||
gutter: gutter_dimensions,
|
||||
right: right_margin,
|
||||
extended_right,
|
||||
};
|
||||
|
||||
snapshot = self.editor.update(cx, |editor, cx| {
|
||||
@@ -10216,6 +10333,26 @@ impl Element for EditorElement {
|
||||
let sticky_header_excerpt_id =
|
||||
sticky_header_excerpt.as_ref().map(|top| top.excerpt.id);
|
||||
|
||||
let buffer = snapshot.buffer_snapshot();
|
||||
let start_buffer_row = MultiBufferRow(start_anchor.to_point(&buffer).row);
|
||||
let end_buffer_row = MultiBufferRow(end_anchor.to_point(&buffer).row);
|
||||
|
||||
let preliminary_scroll_pixel_position = point(
|
||||
scroll_position.x * f64::from(em_layout_width),
|
||||
scroll_position.y * f64::from(line_height),
|
||||
);
|
||||
let indent_guides = self.layout_indent_guides(
|
||||
content_origin,
|
||||
text_hitbox.origin,
|
||||
start_buffer_row..end_buffer_row,
|
||||
preliminary_scroll_pixel_position,
|
||||
line_height,
|
||||
&snapshot,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let indent_guides_for_spacers = indent_guides.clone();
|
||||
|
||||
let blocks = (!is_minimap)
|
||||
.then(|| {
|
||||
window.with_element_namespace("blocks", |window| {
|
||||
@@ -10236,6 +10373,7 @@ impl Element for EditorElement {
|
||||
&latest_selection_anchors,
|
||||
is_row_soft_wrapped,
|
||||
sticky_header_excerpt_id,
|
||||
&indent_guides_for_spacers,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -10243,7 +10381,8 @@ impl Element for EditorElement {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let RenderBlocksOutput {
|
||||
mut blocks,
|
||||
non_spacer_blocks: mut blocks,
|
||||
mut spacer_blocks,
|
||||
row_block_types,
|
||||
resized_blocks,
|
||||
} = blocks;
|
||||
@@ -10294,11 +10433,6 @@ impl Element for EditorElement {
|
||||
None
|
||||
};
|
||||
|
||||
let start_buffer_row =
|
||||
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot()).row);
|
||||
let end_buffer_row =
|
||||
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot()).row);
|
||||
|
||||
let scroll_max: gpui::Point<ScrollPixelOffset> = point(
|
||||
ScrollPixelOffset::from(
|
||||
((scroll_width - editor_width) / em_layout_width).max(0.0),
|
||||
@@ -10359,16 +10493,21 @@ impl Element for EditorElement {
|
||||
sticky_headers.as_ref().map_or(0, |h| h.lines.len()),
|
||||
);
|
||||
});
|
||||
let indent_guides = self.layout_indent_guides(
|
||||
content_origin,
|
||||
text_hitbox.origin,
|
||||
start_buffer_row..end_buffer_row,
|
||||
scroll_pixel_position,
|
||||
line_height,
|
||||
&snapshot,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let indent_guides =
|
||||
if scroll_pixel_position != preliminary_scroll_pixel_position {
|
||||
self.layout_indent_guides(
|
||||
content_origin,
|
||||
text_hitbox.origin,
|
||||
start_buffer_row..end_buffer_row,
|
||||
scroll_pixel_position,
|
||||
line_height,
|
||||
&snapshot,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
indent_guides
|
||||
};
|
||||
|
||||
let crease_trailers =
|
||||
window.with_element_namespace("crease_trailers", |window| {
|
||||
@@ -10507,9 +10646,22 @@ impl Element for EditorElement {
|
||||
self.layout_blocks(
|
||||
&mut blocks,
|
||||
&hitbox,
|
||||
&gutter_hitbox,
|
||||
line_height,
|
||||
scroll_position,
|
||||
scroll_pixel_position,
|
||||
&editor_margins,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.layout_blocks(
|
||||
&mut spacer_blocks,
|
||||
&hitbox,
|
||||
&gutter_hitbox,
|
||||
line_height,
|
||||
scroll_position,
|
||||
scroll_pixel_position,
|
||||
&editor_margins,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
@@ -10904,6 +11056,7 @@ impl Element for EditorElement {
|
||||
inline_blame_layout,
|
||||
inline_code_actions,
|
||||
blocks,
|
||||
spacer_blocks,
|
||||
cursors,
|
||||
visible_cursors,
|
||||
selections,
|
||||
@@ -10965,6 +11118,13 @@ impl Element for EditorElement {
|
||||
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
self.paint_mouse_listeners(layout, window, cx);
|
||||
self.paint_background(layout, window, cx);
|
||||
|
||||
if !layout.spacer_blocks.is_empty() {
|
||||
window.with_element_namespace("blocks", |window| {
|
||||
self.paint_spacer_blocks(layout, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
self.paint_indent_guides(layout, window, cx);
|
||||
|
||||
if layout.gutter_hitbox.size.width > Pixels::ZERO {
|
||||
@@ -10981,7 +11141,7 @@ impl Element for EditorElement {
|
||||
|
||||
if !layout.blocks.is_empty() {
|
||||
window.with_element_namespace("blocks", |window| {
|
||||
self.paint_blocks(layout, window, cx);
|
||||
self.paint_non_spacer_blocks(layout, window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11083,6 +11243,7 @@ pub struct EditorLayout {
|
||||
inline_blame_layout: Option<InlineBlameLayout>,
|
||||
inline_code_actions: Option<AnyElement>,
|
||||
blocks: Vec<BlockLayout>,
|
||||
spacer_blocks: Vec<BlockLayout>,
|
||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
|
||||
redacted_ranges: Vec<Range<DisplayPoint>>,
|
||||
@@ -11847,11 +12008,12 @@ pub fn layout_line(
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IndentGuideLayout {
|
||||
origin: gpui::Point<Pixels>,
|
||||
length: Pixels,
|
||||
single_indent_width: Pixels,
|
||||
display_row_range: Range<DisplayRow>,
|
||||
depth: u32,
|
||||
active: bool,
|
||||
settings: IndentGuideSettings,
|
||||
@@ -13357,26 +13519,26 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_checkerboard_size() {
|
||||
fn test_spacer_pattern_period() {
|
||||
// line height is smaller than target height, so we just return half the line height
|
||||
assert_eq!(EditorElement::checkerboard_size(10.0, 20.0), 5.0);
|
||||
assert_eq!(EditorElement::spacer_pattern_period(10.0, 20.0), 5.0);
|
||||
|
||||
// line height is exactly half the target height, perfect match
|
||||
assert_eq!(EditorElement::checkerboard_size(20.0, 10.0), 10.0);
|
||||
assert_eq!(EditorElement::spacer_pattern_period(20.0, 10.0), 10.0);
|
||||
|
||||
// line height is close to half the target height
|
||||
assert_eq!(EditorElement::checkerboard_size(20.0, 9.0), 10.0);
|
||||
assert_eq!(EditorElement::spacer_pattern_period(20.0, 9.0), 10.0);
|
||||
|
||||
// line height is close to 1/4 the target height
|
||||
assert_eq!(EditorElement::checkerboard_size(20.0, 4.8), 5.0);
|
||||
assert_eq!(EditorElement::spacer_pattern_period(20.0, 4.8), 5.0);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
fn test_random_checkerboard_size(mut rng: StdRng) {
|
||||
fn test_random_spacer_pattern_period(mut rng: StdRng) {
|
||||
let line_height = rng.next_u32() as f32;
|
||||
let target_height = rng.next_u32() as f32;
|
||||
|
||||
let result = EditorElement::checkerboard_size(line_height, target_height);
|
||||
let result = EditorElement::spacer_pattern_period(line_height, target_height);
|
||||
|
||||
let k = line_height / result;
|
||||
assert!(k - k.round() < 0.0000001); // approximately integer
|
||||
|
||||
@@ -290,7 +290,7 @@ fn conflicts_updated(
|
||||
blocks.push(BlockProperties {
|
||||
placement: BlockPlacement::Above(anchor),
|
||||
height: Some(1),
|
||||
style: BlockStyle::Fixed,
|
||||
style: BlockStyle::Sticky,
|
||||
render: Arc::new({
|
||||
let conflict = conflict.clone();
|
||||
move |cx| render_conflict_buttons(&conflict, excerpt_id, editor_handle.clone(), cx)
|
||||
|
||||
@@ -229,6 +229,22 @@ pub struct IndentGuideSettings {
|
||||
pub background_coloring: settings::IndentGuideBackgroundColoring,
|
||||
}
|
||||
|
||||
impl IndentGuideSettings {
|
||||
/// Returns the clamped line width in pixels for an indent guide based on
|
||||
/// whether it is active, or `None` when line coloring is disabled.
|
||||
pub fn visible_line_width(&self, active: bool) -> Option<u32> {
|
||||
if self.coloring == settings::IndentGuideColoring::Disabled {
|
||||
return None;
|
||||
}
|
||||
let width = if active {
|
||||
self.active_line_width
|
||||
} else {
|
||||
self.line_width
|
||||
};
|
||||
Some(width.clamp(1, 10))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LanguageTaskSettings {
|
||||
/// Extra task variables to set for a particular language.
|
||||
|
||||
Reference in new issue
Block a user