editor: Fix edit prediction popovers painting outside the editor's bounds (#50361)

Deferred draws previously didn't use a content mask, so you could
horizontally scroll an EP popover all the way out of the containing
pane.

This also affects other UI elements that use the deferred draw system; I
think in practice it doesn't make much difference because most of those
seem to require something in the editor to be hovered, so if you scroll
horizontally the element goes away.

Release Notes:

- Fixed being able to scroll the edit prediction popover out of the
containing pane.
This commit is contained in:
Cole Miller authored and GitHub committed 2026-03-02 13:38:50 +00:00
1 parent 65e60a53db
commit 904fe6368a
4 files changed
+39 -19

No files matched your search

+8 -1
View File
@@ -9893,7 +9893,14 @@ impl Editor {
origin.x -= BORDER_WIDTH;
window.defer_draw(element, origin, 1);
window.with_content_mask(
Some(gpui::ContentMask {
bounds: *text_bounds,
}),
|window| {
window.defer_draw(element, origin, 1, Some(window.content_mask()));
},
);
// Do not return an element, since it will already be drawn due to defer_draw.
None
+8 -8
View File
@@ -2863,7 +2863,7 @@ impl EditorElement {
}
});
window.defer_draw(element, origin, 2);
window.defer_draw(element, origin, 2, None);
}
}
@@ -5108,7 +5108,7 @@ impl EditorElement {
current_position.y -= size.height;
}
let position = current_position;
window.defer_draw(element, current_position, 1);
window.defer_draw(element, current_position, 1, None);
if !y_flipped {
current_position.y += size.height + MENU_GAP;
} else {
@@ -5211,7 +5211,7 @@ impl EditorElement {
// Skip drawing if it doesn't fit anywhere.
if let Some((aside, position, size)) = positioned_aside {
let aside_bounds = Bounds::new(position, size);
window.defer_draw(aside, position, 2);
window.defer_draw(aside, position, 2, None);
return Some(aside_bounds);
}
@@ -5420,7 +5420,7 @@ impl EditorElement {
.on_mouse_move(|_, _, cx| cx.stop_propagation())
.into_any_element();
occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), window, cx);
window.defer_draw(occlusion, origin, 2);
window.defer_draw(occlusion, origin, 2, None);
}
fn place_popovers_above(
@@ -5437,7 +5437,7 @@ impl EditorElement {
current_y - size.height,
);
window.defer_draw(popover.element, popover_origin, 2);
window.defer_draw(popover.element, popover_origin, 2, None);
if position != itertools::Position::Last {
let origin = point(popover_origin.x, popover_origin.y - HOVER_POPOVER_GAP);
draw_occluder(size.width, origin, window, cx);
@@ -5459,7 +5459,7 @@ impl EditorElement {
let size = popover.size;
let popover_origin = point(hovered_point.x + popover.horizontal_offset, current_y);
window.defer_draw(popover.element, popover_origin, 2);
window.defer_draw(popover.element, popover_origin, 2, None);
if position != itertools::Position::Last {
let origin = point(popover_origin.x, popover_origin.y + size.height);
draw_occluder(size.width, origin, window, cx);
@@ -5561,7 +5561,7 @@ impl EditorElement {
let size = popover.size;
let popover_origin = point(origin.x, current_y);
window.defer_draw(popover.element, popover_origin, 2);
window.defer_draw(popover.element, popover_origin, 2, None);
if position != itertools::Position::Last {
let origin = point(popover_origin.x, popover_origin.y + size.height);
draw_occluder(size.width, origin, window, cx);
@@ -5893,7 +5893,7 @@ impl EditorElement {
})
};
window.defer_draw(element, final_origin, 2);
window.defer_draw(element, final_origin, 2, None);
}
fn paint_background(&self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
+1 -1
View File
@@ -62,7 +62,7 @@ impl Element for Deferred {
) {
let child = self.child.take().unwrap();
let element_offset = window.element_offset();
window.defer_draw(child, element_offset, self.priority)
window.defer_draw(child, element_offset, self.priority, None)
}
fn paint(
+22 -9
View File
@@ -726,6 +726,7 @@ pub(crate) struct DeferredDraw {
parent_node: DispatchNodeId,
element_id_stack: SmallVec<[ElementId; 32]>,
text_style_stack: Vec<TextStyleRefinement>,
content_mask: Option<ContentMask<Pixels>>,
rem_size: Pixels,
element: Option<AnyElement>,
absolute_offset: Point<Pixels>,
@@ -2429,15 +2430,18 @@ impl Window {
.set_active_node(deferred_draw.parent_node);
let prepaint_start = self.prepaint_index();
let content_mask = deferred_draw.content_mask.clone();
if let Some(element) = deferred_draw.element.as_mut() {
self.with_rendered_view(deferred_draw.current_view, |window| {
window.with_rem_size(Some(deferred_draw.rem_size), |window| {
window.with_absolute_element_offset(
deferred_draw.absolute_offset,
|window| {
element.prepaint(window, cx);
},
);
window.with_content_mask(content_mask, |window| {
window.with_rem_size(Some(deferred_draw.rem_size), |window| {
window.with_absolute_element_offset(
deferred_draw.absolute_offset,
|window| {
element.prepaint(window, cx);
},
);
});
});
})
} else {
@@ -2469,10 +2473,13 @@ impl Window {
.set_active_node(deferred_draw.parent_node);
let paint_start = self.paint_index();
let content_mask = deferred_draw.content_mask.clone();
if let Some(element) = deferred_draw.element.as_mut() {
self.with_rendered_view(deferred_draw.current_view, |window| {
window.with_rem_size(Some(deferred_draw.rem_size), |window| {
element.paint(window, cx);
window.with_content_mask(content_mask, |window| {
window.with_rem_size(Some(deferred_draw.rem_size), |window| {
element.paint(window, cx);
});
})
})
} else {
@@ -2536,6 +2543,7 @@ impl Window {
parent_node: reused_subtree.refresh_node_id(deferred_draw.parent_node),
element_id_stack: deferred_draw.element_id_stack.clone(),
text_style_stack: deferred_draw.text_style_stack.clone(),
content_mask: deferred_draw.content_mask.clone(),
rem_size: deferred_draw.rem_size,
priority: deferred_draw.priority,
element: None,
@@ -3019,12 +3027,16 @@ impl Window {
/// at a later time. The `priority` parameter determines the drawing order relative to other deferred elements,
/// with higher values being drawn on top.
///
/// When `content_mask` is provided, the deferred element will be clipped to that region during
/// both prepaint and paint. When `None`, no additional clipping is applied.
///
/// This method should only be called as part of the prepaint phase of element drawing.
pub fn defer_draw(
&mut self,
element: AnyElement,
absolute_offset: Point<Pixels>,
priority: usize,
content_mask: Option<ContentMask<Pixels>>,
) {
self.invalidator.debug_assert_prepaint();
let parent_node = self.next_frame.dispatch_tree.active_node_id().unwrap();
@@ -3033,6 +3045,7 @@ impl Window {
parent_node,
element_id_stack: self.element_id_stack.clone(),
text_style_stack: self.text_style_stack.clone(),
content_mask,
rem_size: self.rem_size(),
priority,
element: Some(element),