Fix layout panic on empty editors with blocks

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Max Brunsfeld 2022-03-14 12:03:26 -07:00
parent 40a4c18ee4
commit e392368d89
2 changed files with 86 additions and 13 deletions

View file

@ -26,6 +26,7 @@ use smallvec::SmallVec;
use std::{ use std::{
cmp::{self, Ordering}, cmp::{self, Ordering},
fmt::Write, fmt::Write,
iter,
ops::Range, ops::Range,
}; };
@ -610,11 +611,10 @@ impl EditorElement {
fn layout_lines( fn layout_lines(
&mut self, &mut self,
mut rows: Range<u32>, rows: Range<u32>,
snapshot: &mut EditorSnapshot, snapshot: &EditorSnapshot,
cx: &LayoutContext, cx: &LayoutContext,
) -> Vec<text_layout::Line> { ) -> Vec<text_layout::Line> {
rows.end = cmp::min(rows.end, snapshot.max_point().row() + 1);
if rows.start >= rows.end { if rows.start >= rows.end {
return Vec::new(); return Vec::new();
} }
@ -632,6 +632,7 @@ impl EditorElement {
.map_or("", AsRef::as_ref) .map_or("", AsRef::as_ref)
.split('\n') .split('\n')
.skip(rows.start as usize) .skip(rows.start as usize)
.chain(iter::repeat(""))
.take(rows.len()); .take(rows.len());
return placeholder_lines return placeholder_lines
.map(|line| { .map(|line| {
@ -880,7 +881,12 @@ impl Element for EditorElement {
let scroll_position = snapshot.scroll_position(); let scroll_position = snapshot.scroll_position();
let start_row = scroll_position.y() as u32; let start_row = scroll_position.y() as u32;
let scroll_top = scroll_position.y() * line_height; let scroll_top = scroll_position.y() * line_height;
let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
// Add 1 to ensure selections bleed off screen
let end_row = 1 + cmp::min(
((scroll_top + size.y()) / line_height).ceil() as u32,
snapshot.max_point().row(),
);
let start_anchor = if start_row == 0 { let start_anchor = if start_row == 0 {
Anchor::min() Anchor::min()
@ -963,7 +969,7 @@ impl Element for EditorElement {
self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx); self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx);
let mut max_visible_line_width = 0.0; let mut max_visible_line_width = 0.0;
let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx); let line_layouts = self.layout_lines(start_row..end_row, &snapshot, cx);
for line in &line_layouts { for line in &line_layouts {
if line.width() > max_visible_line_width { if line.width() > max_visible_line_width {
max_visible_line_width = line.width(); max_visible_line_width = line.width();
@ -1466,8 +1472,13 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc;
use super::*; use super::*;
use crate::{Editor, MultiBuffer}; use crate::{
display_map::{BlockDisposition, BlockProperties},
Editor, MultiBuffer,
};
use util::test::sample_text; use util::test::sample_text;
use workspace::Settings; use workspace::Settings;
@ -1492,4 +1503,58 @@ mod tests {
}); });
assert_eq!(layouts.len(), 6); assert_eq!(layouts.len(), 6);
} }
#[gpui::test]
fn test_layout_with_placeholder_text_and_blocks(cx: &mut gpui::MutableAppContext) {
cx.add_app_state(Settings::test(cx));
let buffer = MultiBuffer::build_simple("", cx);
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx);
editor.insert_blocks(
[BlockProperties {
disposition: BlockDisposition::Above,
height: 3,
position: Anchor::min(),
render: Arc::new(|_| Empty::new().boxed()),
}],
cx,
);
// Blur the editor so that it displays placeholder text.
cx.blur();
});
let mut element = EditorElement::new(
editor.downgrade(),
editor.read(cx).style(cx),
CursorShape::Bar,
);
let mut scene = Scene::new(1.0);
let mut presenter = cx.build_presenter(window_id, 30.);
let mut layout_cx = presenter.build_layout_context(false, cx);
let (size, mut state) = element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
&mut layout_cx,
);
assert_eq!(state.line_layouts.len(), 4);
assert_eq!(
state
.line_number_layouts
.iter()
.map(Option::is_some)
.collect::<Vec<_>>(),
&[false, false, false, true]
);
// Don't panic.
let bounds = RectF::new(Default::default(), size);
let mut paint_cx = presenter.build_paint_context(&mut scene, cx);
element.paint(bounds, bounds, &mut state, &mut paint_cx);
}
} }

View file

@ -110,13 +110,7 @@ impl Presenter {
if let Some(root_view_id) = cx.root_view_id(self.window_id) { if let Some(root_view_id) = cx.root_view_id(self.window_id) {
self.layout(window_size, refreshing, cx); self.layout(window_size, refreshing, cx);
let mut paint_cx = PaintContext { let mut paint_cx = self.build_paint_context(&mut scene, cx);
scene: &mut scene,
font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache,
rendered_views: &mut self.rendered_views,
app: cx.as_ref(),
};
paint_cx.paint( paint_cx.paint(
root_view_id, root_view_id,
Vector2F::zero(), Vector2F::zero(),
@ -159,6 +153,20 @@ impl Presenter {
} }
} }
pub fn build_paint_context<'a>(
&'a mut self,
scene: &'a mut Scene,
cx: &'a mut MutableAppContext,
) -> PaintContext {
PaintContext {
scene,
font_cache: &self.font_cache,
text_layout_cache: &self.text_layout_cache,
rendered_views: &mut self.rendered_views,
app: cx,
}
}
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) { pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) {
if let Some(root_view_id) = cx.root_view_id(self.window_id) { if let Some(root_view_id) = cx.root_view_id(self.window_id) {
match event { match event {