editor: Represent scroll offset with more precision (#39367)

Closes #5355

Release Notes:

- Fixed rendering glitches with files with more than 16 million lines
(that occured due to floating number rounding errors).

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
Piotr Osiewicz
2025-10-02 23:04:31 +02:00
committed by GitHub
co-authored by Smit Barmase
parent 4c35274b6e
commit d359a814f8
61 changed files with 711 additions and 530 deletions
+54 -39
View File
@@ -210,6 +210,7 @@ use crate::{
code_context_menus::CompletionsMenuSource,
editor_settings::MultiCursorModifier,
hover_links::{find_url, find_url_from_range},
scroll::{ScrollOffset, ScrollPixelOffset},
signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
};
@@ -8621,8 +8622,8 @@ impl Editor {
self.context_menu_options = Some(options);
}
const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
fn render_edit_prediction_popover(
&mut self,
@@ -8631,11 +8632,12 @@ impl Editor {
right_margin: Pixels,
editor_snapshot: &EditorSnapshot,
visible_row_range: Range<DisplayRow>,
scroll_top: f32,
scroll_bottom: f32,
scroll_top: ScrollOffset,
scroll_bottom: ScrollOffset,
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
scroll_position: gpui::Point<ScrollOffset>,
scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
newest_selection_head: Option<DisplayPoint>,
editor_width: Pixels,
style: &EditorStyle,
@@ -8727,6 +8729,7 @@ impl Editor {
visible_row_range,
line_layouts,
line_height,
scroll_position,
scroll_pixel_position,
newest_selection_head,
editor_width,
@@ -8769,14 +8772,14 @@ impl Editor {
visible_row_range: Range<DisplayRow>,
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
newest_selection_head: Option<DisplayPoint>,
target_display_point: DisplayPoint,
window: &mut Window,
cx: &mut App,
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
let scrolled_content_origin =
content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
const SCROLL_PADDING_Y: Pixels = px(12.);
@@ -8811,8 +8814,8 @@ impl Editor {
let target_column = target_display_point.column() as usize;
let target_x = line_layout.x_for_index(target_column);
let target_y =
(target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
- scroll_pixel_position.y;
let flag_on_right = target_x < text_bounds.size.width / 2.;
@@ -8840,7 +8843,7 @@ impl Editor {
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
let mut origin = scrolled_content_origin + point(target_x, target_y)
let mut origin = scrolled_content_origin + point(target_x, target_y.into())
- point(
if flag_on_right {
POLE_WIDTH
@@ -8893,16 +8896,16 @@ impl Editor {
content_origin: gpui::Point<Pixels>,
editor_snapshot: &EditorSnapshot,
visible_row_range: Range<DisplayRow>,
scroll_top: f32,
scroll_bottom: f32,
scroll_top: ScrollOffset,
scroll_bottom: ScrollOffset,
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
target_display_point: DisplayPoint,
editor_width: Pixels,
window: &mut Window,
cx: &mut App,
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
if target_display_point.row().as_f32() < scroll_top {
if target_display_point.row().as_f64() < scroll_top {
let mut element = self
.render_edit_prediction_line_popover(
"Jump to Edit",
@@ -8921,7 +8924,7 @@ impl Editor {
let origin = text_bounds.origin + offset;
element.prepaint_at(origin, window, cx);
Some((element, origin))
} else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
} else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
let mut element = self
.render_edit_prediction_line_popover(
"Jump to Edit",
@@ -8963,7 +8966,7 @@ impl Editor {
visible_row_range: Range<DisplayRow>,
target_display_point: DisplayPoint,
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
content_origin: gpui::Point<Pixels>,
editor_width: Pixels,
window: &mut Window,
@@ -8982,7 +8985,7 @@ impl Editor {
let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
let mut origin = start_point
+ line_origin
+ point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
@@ -9023,7 +9026,8 @@ impl Editor {
visible_row_range: Range<DisplayRow>,
line_layouts: &[LineWithInvisibles],
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
scroll_position: gpui::Point<ScrollOffset>,
scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
newest_selection_head: Option<DisplayPoint>,
editor_width: Pixels,
style: &EditorStyle,
@@ -9136,9 +9140,11 @@ impl Editor {
..Default::default()
});
let x_after_longest =
text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
- scroll_pixel_position.x;
let x_after_longest = Pixels::from(
ScrollPixelOffset::from(
text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
) - scroll_pixel_position.x,
);
let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
@@ -9150,8 +9156,11 @@ impl Editor {
let mut origin = if can_position_to_the_right {
point(
x_after_longest,
text_bounds.origin.y + edit_start.row().as_f32() * line_height
- scroll_pixel_position.y,
text_bounds.origin.y
+ Pixels::from(
edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
- scroll_pixel_position.y,
),
)
} else {
let cursor_row = newest_selection_head.map(|head| head.row());
@@ -9181,8 +9190,10 @@ impl Editor {
content_origin
+ point(
-scroll_pixel_position.x,
row_target.as_f32() * line_height - scroll_pixel_position.y,
Pixels::from(-scroll_pixel_position.x),
Pixels::from(
(row_target.as_f64() - scroll_position.y) * f64::from(line_height),
),
)
};
@@ -14250,7 +14261,7 @@ impl Editor {
let mut row = range.start.row();
let positions =
if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
px(start)..px(end)
Pixels::from(start)..Pixels::from(end)
} else {
let start_x =
display_map.x_for_display_point(range.start, &text_layout_details);
@@ -15884,7 +15895,7 @@ impl Editor {
if should_scroll_up {
let new_scroll_position =
current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
self.set_scroll_position(new_scroll_position, window, cx);
}
}
@@ -21737,11 +21748,11 @@ impl Editor {
.scroll_position(editor_snapshot)
.y;
if source.row().as_f32() < scroll_top.floor() {
if source.row().as_f64() < scroll_top.floor() {
return None;
}
let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
let source_y = line_height * (source.row().as_f32() - scroll_top);
let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
Some(gpui::Point::new(source_x, source_y))
}
@@ -23323,7 +23334,7 @@ impl EditorSnapshot {
.map(|display_map| display_map.text())
}
pub fn scroll_position(&self) -> gpui::Point<f32> {
pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
self.scroll_anchor.scroll_position(&self.display_snapshot)
}
@@ -23889,12 +23900,16 @@ impl EntityInputHandler for Editor {
let snapshot = self.snapshot(window, cx);
let scroll_position = snapshot.scroll_position();
let scroll_left = scroll_position.x * em_advance;
let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
+ self.gutter_dimensions.full_width();
let y = line_height * (start.row().as_f32() - scroll_position.y);
let x = Pixels::from(
ScrollOffset::from(
snapshot.x_for_display_point(start, &text_layout_details)
+ self.gutter_dimensions.full_width(),
) - scroll_left,
);
let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
Some(Bounds {
origin: element_bounds.origin + point(x, y),
@@ -24161,7 +24176,7 @@ impl<T: ToOffset> RangeToAnchorExt for Range<T> {
}
pub trait RowExt {
fn as_f32(&self) -> f32;
fn as_f64(&self) -> f64;
fn next_row(&self) -> Self;
@@ -24171,8 +24186,8 @@ pub trait RowExt {
}
impl RowExt for DisplayRow {
fn as_f32(&self) -> f32 {
self.0 as f32
fn as_f64(&self) -> f64 {
self.0 as _
}
fn next_row(&self) -> Self {
@@ -24189,8 +24204,8 @@ impl RowExt for DisplayRow {
}
impl RowExt for MultiBufferRow {
fn as_f32(&self) -> f32 {
self.0 as f32
fn as_f64(&self) -> f64 {
self.0 as _
}
fn next_row(&self) -> Self {