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
+7 -7
View File
@@ -1176,7 +1176,7 @@ impl DisplaySnapshot {
.map(|(row, block)| (DisplayRow(row), block))
}
pub fn sticky_header_excerpt(&self, row: f32) -> Option<StickyHeaderExcerpt<'_>> {
pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
self.block_snapshot.sticky_header_excerpt(row)
}
@@ -1877,33 +1877,33 @@ pub mod tests {
),
(
DisplayPoint::new(DisplayRow(0), 7),
language::SelectionGoal::HorizontalPosition(x.0)
language::SelectionGoal::HorizontalPosition(f64::from(x))
)
);
assert_eq!(
movement::down(
&snapshot,
DisplayPoint::new(DisplayRow(0), 7),
language::SelectionGoal::HorizontalPosition(x.0),
language::SelectionGoal::HorizontalPosition(f64::from(x)),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(1), 10),
language::SelectionGoal::HorizontalPosition(x.0)
language::SelectionGoal::HorizontalPosition(f64::from(x))
)
);
assert_eq!(
movement::down(
&snapshot,
DisplayPoint::new(DisplayRow(1), 10),
language::SelectionGoal::HorizontalPosition(x.0),
language::SelectionGoal::HorizontalPosition(f64::from(x)),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(2), 4),
language::SelectionGoal::HorizontalPosition(x.0)
language::SelectionGoal::HorizontalPosition(f64::from(x))
)
);
@@ -1920,7 +1920,7 @@ pub mod tests {
// Re-wrap on font size changes
map.update(cx, |map, cx| {
map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
});
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
+1 -1
View File
@@ -1395,7 +1395,7 @@ impl BlockSnapshot {
})
}
pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
let top_row = position as u32;
let mut cursor = self.transforms.cursor::<BlockRow>(());
cursor.seek(&BlockRow(top_row), Bias::Right);
+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 {
+2 -2
View File
@@ -31,7 +31,7 @@ pub struct EditorSettings {
pub minimap: Minimap,
pub gutter: Gutter,
pub scroll_beyond_last_line: ScrollBeyondLastLine,
pub vertical_scroll_margin: f32,
pub vertical_scroll_margin: f64,
pub autoscroll_on_clicks: bool,
pub horizontal_scroll_margin: f32,
pub scroll_sensitivity: f32,
@@ -248,7 +248,7 @@ impl Settings for EditorSettings {
folds: gutter.folds.unwrap(),
},
scroll_beyond_last_line: editor.scroll_beyond_last_line.unwrap(),
vertical_scroll_margin: editor.vertical_scroll_margin.unwrap(),
vertical_scroll_margin: editor.vertical_scroll_margin.unwrap() as f64,
autoscroll_on_clicks: editor.autoscroll_on_clicks.unwrap(),
horizontal_scroll_margin: editor.horizontal_scroll_margin.unwrap(),
scroll_sensitivity: editor.scroll_sensitivity.unwrap(),
+3 -3
View File
@@ -782,12 +782,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
assert!(pop_history(&mut editor, cx).is_none());
// Set scroll position to check later
editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
let original_scroll_position = editor.scroll_manager.anchor();
// Jump to the end of the document and adjust scroll
editor.move_to_end(&MoveToEnd, window, cx);
editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
let nav_entry = pop_history(&mut editor, cx).unwrap();
@@ -817,7 +817,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
);
assert_eq!(
editor.scroll_position(cx),
gpui::Point::new(0., editor.max_point(cx).row().as_f32())
gpui::Point::new(0., editor.max_point(cx).row().as_f64())
);
editor
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,7 +5,7 @@ use crate::{
display_map::HighlightKey,
editor_settings::SeedQuerySetting,
persistence::{DB, SerializedEditor},
scroll::ScrollAnchor,
scroll::{ScrollAnchor, ScrollOffset},
};
use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet};
@@ -1338,7 +1338,7 @@ struct EditorRestorationData {
#[derive(Default, Debug)]
pub struct RestorationData {
pub scroll_position: (BufferRow, gpui::Point<f32>),
pub scroll_position: (BufferRow, gpui::Point<ScrollOffset>),
pub folds: Vec<Range<Point>>,
pub selections: Vec<Range<Point>>,
}
+1 -1
View File
@@ -54,7 +54,7 @@ impl MouseContextMenu {
let content_origin = editor.last_bounds?.origin
+ Point {
x: editor.gutter_dimensions.width,
y: Pixels(0.0),
y: Pixels::ZERO,
};
let source_position = editor.to_pixel_point(source, &editor_snapshot, window)?;
let menu_position = MenuPosition::PinnedToEditor {
+18 -15
View File
@@ -2,7 +2,10 @@
//! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate.
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
use crate::{DisplayRow, EditorStyle, ToOffset, ToPoint, scroll::ScrollAnchor};
use crate::{
DisplayRow, EditorStyle, ToOffset, ToPoint,
scroll::{ScrollAnchor, ScrollOffset},
};
use gpui::{Pixels, WindowTextSystem};
use language::{CharClassifier, Point};
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
@@ -27,8 +30,8 @@ pub struct TextLayoutDetails {
pub(crate) editor_style: EditorStyle,
pub(crate) rem_size: Pixels,
pub scroll_anchor: ScrollAnchor,
pub visible_rows: Option<f32>,
pub vertical_scroll_margin: f32,
pub visible_rows: Option<f64>,
pub vertical_scroll_margin: ScrollOffset,
}
/// Returns a column to the left of the current point, wrapping
@@ -1220,13 +1223,13 @@ mod tests {
up(
&snapshot,
DisplayPoint::new(DisplayRow(0), 2),
SelectionGoal::HorizontalPosition(col_2_x.0),
SelectionGoal::HorizontalPosition(f64::from(col_2_x)),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(0), 0),
SelectionGoal::HorizontalPosition(col_2_x.0),
SelectionGoal::HorizontalPosition(f64::from(col_2_x)),
),
);
assert_eq!(
@@ -1251,26 +1254,26 @@ mod tests {
up(
&snapshot,
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_4_x.0),
SelectionGoal::HorizontalPosition(col_4_x.into()),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(0), 3),
SelectionGoal::HorizontalPosition(col_4_x.0)
SelectionGoal::HorizontalPosition(col_4_x.into())
),
);
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(0), 3),
SelectionGoal::HorizontalPosition(col_4_x.0),
SelectionGoal::HorizontalPosition(col_4_x.into()),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_4_x.0)
SelectionGoal::HorizontalPosition(col_4_x.into())
),
);
@@ -1282,26 +1285,26 @@ mod tests {
up(
&snapshot,
DisplayPoint::new(DisplayRow(3), 5),
SelectionGoal::HorizontalPosition(col_5_x.0),
SelectionGoal::HorizontalPosition(col_5_x.into()),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_5_x.0)
SelectionGoal::HorizontalPosition(col_5_x.into())
),
);
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_5_x.0),
SelectionGoal::HorizontalPosition(col_5_x.into()),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(3), 5),
SelectionGoal::HorizontalPosition(col_5_x.0)
SelectionGoal::HorizontalPosition(col_5_x.into())
),
);
@@ -1326,13 +1329,13 @@ mod tests {
down(
&snapshot,
DisplayPoint::new(DisplayRow(4), 2),
SelectionGoal::HorizontalPosition(max_point_x.0),
SelectionGoal::HorizontalPosition(max_point_x.into()),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(4), 2),
SelectionGoal::HorizontalPosition(max_point_x.0)
SelectionGoal::HorizontalPosition(max_point_x.into())
),
);
});
+3 -3
View File
@@ -235,7 +235,7 @@ impl EditorDb {
// Returns the scroll top row, and offset
query! {
pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<(u32, f32, f32)>> {
pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<(u32, f64, f64)>> {
SELECT scroll_top_row, scroll_horizontal_offset, scroll_vertical_offset
FROM editors
WHERE item_id = ? AND workspace_id = ?
@@ -247,8 +247,8 @@ impl EditorDb {
item_id: ItemId,
workspace_id: WorkspaceId,
top_row: u32,
vertical_offset: f32,
horizontal_offset: f32
vertical_offset: f64,
horizontal_offset: f64
) -> Result<()> {
UPDATE OR IGNORE editors
SET
+35 -26
View File
@@ -30,9 +30,11 @@ const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
pub struct WasScrolled(pub(crate) bool);
pub type ScrollOffset = f64;
pub type ScrollPixelOffset = f64;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollAnchor {
pub offset: gpui::Point<f32>,
pub offset: gpui::Point<ScrollOffset>,
pub anchor: Anchor,
}
@@ -44,12 +46,12 @@ impl ScrollAnchor {
}
}
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
self.offset.apply_along(Axis::Vertical, |offset| {
if self.anchor == Anchor::min() {
0.
} else {
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f32();
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
(offset + scroll_top).max(0.)
}
})
@@ -147,19 +149,24 @@ impl ActiveScrollbarState {
}
pub struct ScrollManager {
pub(crate) vertical_scroll_margin: f32,
pub(crate) vertical_scroll_margin: ScrollOffset,
anchor: ScrollAnchor,
ongoing: OngoingScroll,
/// The second element indicates whether the autoscroll request is local
/// (true) or remote (false). Local requests are initiated by user actions,
/// while remote requests come from external sources.
autoscroll_request: Option<(Autoscroll, bool)>,
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
last_autoscroll: Option<(
gpui::Point<ScrollOffset>,
ScrollOffset,
ScrollOffset,
AutoscrollStrategy,
)>,
show_scrollbars: bool,
hide_scrollbar_task: Option<Task<()>>,
active_scrollbar: Option<ActiveScrollbarState>,
visible_line_count: Option<f32>,
visible_column_count: Option<f32>,
visible_line_count: Option<f64>,
visible_column_count: Option<f64>,
forbid_vertical_scroll: bool,
minimap_thumb_state: Option<ScrollbarThumbState>,
}
@@ -200,13 +207,13 @@ impl ScrollManager {
self.ongoing.axis = axis;
}
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
self.anchor.scroll_position(snapshot)
}
fn set_scroll_position(
&mut self,
scroll_position: gpui::Point<f32>,
scroll_position: gpui::Point<ScrollOffset>,
map: &DisplaySnapshot,
local: bool,
autoscroll: bool,
@@ -219,7 +226,7 @@ impl ScrollManager {
ScrollBeyondLastLine::OnePage => scroll_top,
ScrollBeyondLastLine::Off => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().0 as f32;
let max_row = map.max_point().row().as_f64();
scroll_top.min(max_row - height_in_lines + 1.).max(0.)
} else {
scroll_top
@@ -227,7 +234,7 @@ impl ScrollManager {
}
ScrollBeyondLastLine::VerticalScrollMargin => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().0 as f32;
let max_row = map.max_point().row().as_f64();
scroll_top
.min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
.max(0.)
@@ -251,7 +258,7 @@ impl ScrollManager {
anchor: top_anchor,
offset: point(
scroll_position.x.max(0.),
scroll_top - top_anchor.to_display_point(map).row().as_f32(),
scroll_top - top_anchor.to_display_point(map).row().as_f64(),
),
},
scroll_top_buffer_point.row,
@@ -437,7 +444,7 @@ impl ScrollManager {
self.minimap_thumb_state
}
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
pub fn clamp_scroll_left(&mut self, max: f64) -> bool {
if max < self.anchor.offset.x {
self.anchor.offset.x = max;
true
@@ -461,11 +468,11 @@ impl Editor {
}
pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
cx.notify();
}
pub fn visible_line_count(&self) -> Option<f32> {
pub fn visible_line_count(&self) -> Option<f64> {
self.scroll_manager.visible_line_count
}
@@ -474,13 +481,13 @@ impl Editor {
.map(|line_count| line_count as u32 - 1)
}
pub fn visible_column_count(&self) -> Option<f32> {
pub fn visible_column_count(&self) -> Option<f64> {
self.scroll_manager.visible_column_count
}
pub(crate) fn set_visible_line_count(
&mut self,
lines: f32,
lines: f64,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -499,7 +506,7 @@ impl Editor {
}
}
pub(crate) fn set_visible_column_count(&mut self, columns: f32) {
pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
self.scroll_manager.visible_column_count = Some(columns);
}
@@ -514,13 +521,14 @@ impl Editor {
delta.y = 0.0;
}
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let position = self.scroll_manager.anchor.scroll_position(&display_map) + delta;
let position =
self.scroll_manager.anchor.scroll_position(&display_map) + delta.map(f64::from);
self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
}
pub fn set_scroll_position(
&mut self,
scroll_position: gpui::Point<f32>,
scroll_position: gpui::Point<ScrollOffset>,
window: &mut Window,
cx: &mut Context<Self>,
) -> WasScrolled {
@@ -556,7 +564,7 @@ impl Editor {
pub(crate) fn set_scroll_position_internal(
&mut self,
scroll_position: gpui::Point<f32>,
scroll_position: gpui::Point<ScrollOffset>,
local: bool,
autoscroll: bool,
window: &mut Window,
@@ -575,7 +583,7 @@ impl Editor {
fn set_scroll_position_taking_display_map(
&mut self,
scroll_position: gpui::Point<f32>,
scroll_position: gpui::Point<ScrollOffset>,
local: bool,
autoscroll: bool,
display_map: DisplaySnapshot,
@@ -610,7 +618,7 @@ impl Editor {
editor_was_scrolled
}
pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<f32> {
pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
self.scroll_manager.anchor.scroll_position(&display_map)
}
@@ -697,9 +705,9 @@ impl Editor {
if matches!(
settings.defaults.soft_wrap,
SoftWrap::PreferredLineLength | SoftWrap::Bounded
) && (settings.defaults.preferred_line_length as f32) < visible_column_count
) && (settings.defaults.preferred_line_length as f64) < visible_column_count
{
visible_column_count = settings.defaults.preferred_line_length as f32;
visible_column_count = settings.defaults.preferred_line_length as f64;
}
// If the scroll position is currently at the left edge of the document
@@ -710,7 +718,8 @@ impl Editor {
&& amount.columns(visible_column_count) > 0.
&& let Some(last_position_map) = &self.last_position_map
{
current_position.x += self.gutter_dimensions.margin / last_position_map.em_advance;
current_position.x +=
f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
}
let new_position = current_position
+ point(
+2 -2
View File
@@ -2,7 +2,7 @@ use super::Axis;
use crate::{
Autoscroll, Editor, EditorMode, NextScreen, NextScrollCursorCenterTopBottom,
SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT, ScrollCursorBottom, ScrollCursorCenter,
ScrollCursorCenterTopBottom, ScrollCursorTop, display_map::DisplayRow,
ScrollCursorCenterTopBottom, ScrollCursorTop, display_map::DisplayRow, scroll::ScrollOffset,
};
use gpui::{Context, Point, Window};
@@ -25,7 +25,7 @@ impl Editor {
pub fn scroll(
&mut self,
scroll_position: Point<f32>,
scroll_position: Point<ScrollOffset>,
axis: Option<Axis>,
window: &mut Window,
cx: &mut Context<Self>,
+30 -24
View File
@@ -1,11 +1,12 @@
use crate::{
DisplayRow, Editor, EditorMode, LineWithInvisibles, RowExt, SelectionEffects,
display_map::ToDisplayPoint, scroll::WasScrolled,
display_map::ToDisplayPoint,
scroll::{ScrollOffset, WasScrolled},
};
use gpui::{Bounds, Context, Pixels, Window, px};
use gpui::{Bounds, Context, Pixels, Window};
use language::Point;
use multi_buffer::Anchor;
use std::{cmp, f32};
use std::cmp;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Autoscroll {
@@ -106,20 +107,21 @@ impl Editor {
&mut self,
bounds: Bounds<Pixels>,
line_height: Pixels,
max_scroll_top: f32,
max_scroll_top: ScrollOffset,
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Editor>,
) -> (NeedsHorizontalAutoscroll, WasScrolled) {
let viewport_height = bounds.size.height;
let visible_lines = viewport_height / line_height;
let visible_lines = ScrollOffset::from(viewport_height / line_height);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
let original_y = scroll_position.y;
if let Some(last_bounds) = self.expect_bounds_change.take()
&& scroll_position.y != 0.
{
scroll_position.y += (bounds.top() - last_bounds.top()) / line_height;
scroll_position.y +=
ScrollOffset::from((bounds.top() - last_bounds.top()) / line_height);
if scroll_position.y < 0. {
scroll_position.y = 0.;
}
@@ -143,7 +145,7 @@ impl Editor {
if let Some(first_highlighted_row) =
self.highlighted_display_row_for_autoscroll(&display_map)
{
target_top = first_highlighted_row.as_f32();
target_top = first_highlighted_row.as_f64();
target_bottom = target_top + 1.;
} else {
let selections = self.selections.all::<Point>(cx);
@@ -154,7 +156,7 @@ impl Editor {
.head()
.to_display_point(&display_map)
.row()
.as_f32();
.as_f64();
target_bottom = selections
.last()
.unwrap()
@@ -162,7 +164,7 @@ impl Editor {
.to_display_point(&display_map)
.row()
.next_row()
.as_f32();
.as_f64();
let selections_fit = target_bottom - target_top <= visible_lines;
if matches!(
@@ -178,7 +180,7 @@ impl Editor {
.head()
.to_display_point(&display_map)
.row()
.as_f32();
.as_f64();
target_top = newest_selection_top;
target_bottom = newest_selection_top + 1.;
}
@@ -209,7 +211,7 @@ impl Editor {
}
};
if let Autoscroll::Strategy(_, Some(anchor)) = autoscroll {
target_top = anchor.to_display_point(&display_map).row().as_f32();
target_top = anchor.to_display_point(&display_map).row().as_f64();
target_bottom = target_top + 1.;
}
@@ -254,11 +256,11 @@ impl Editor {
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::TopRelative(lines) => {
scroll_position.y = target_top - lines as f32;
scroll_position.y = target_top - lines as ScrollOffset;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::BottomRelative(lines) => {
scroll_position.y = target_bottom + lines as f32;
scroll_position.y = target_bottom + lines as ScrollOffset;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
};
@@ -284,22 +286,25 @@ impl Editor {
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<gpui::Point<f32>> {
) -> Option<gpui::Point<ScrollOffset>> {
let (_, local) = autoscroll_request?;
let em_advance = ScrollOffset::from(em_advance);
let viewport_width = ScrollOffset::from(viewport_width);
let scroll_width = ScrollOffset::from(scroll_width);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(cx);
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
let mut target_left;
let mut target_right;
let mut target_right: f64;
if self
.highlighted_display_row_for_autoscroll(&display_map)
.is_none()
{
target_left = px(f32::INFINITY);
target_right = px(0.);
target_left = f64::INFINITY;
target_right = 0.;
for selection in selections {
let head = selection.head().to_display_point(&display_map);
if head.row() >= start_row
@@ -307,21 +312,22 @@ impl Editor {
{
let start_column = head.column();
let end_column = cmp::min(display_map.line_len(head.row()), head.column());
target_left = target_left.min(
target_left = target_left.min(ScrollOffset::from(
layouts[head.row().minus(start_row) as usize]
.x_for_index(start_column as usize)
+ self.gutter_dimensions.margin,
);
));
target_right = target_right.max(
layouts[head.row().minus(start_row) as usize]
.x_for_index(end_column as usize)
+ em_advance,
ScrollOffset::from(
layouts[head.row().minus(start_row) as usize]
.x_for_index(end_column as usize),
) + em_advance,
);
}
}
} else {
target_left = px(0.);
target_right = px(0.);
target_left = 0.;
target_right = 0.;
}
target_right = target_right.min(scroll_width);
+11 -11
View File
@@ -1,5 +1,5 @@
use serde::Deserialize;
use ui::{Pixels, px};
use ui::Pixels;
#[derive(Debug)]
pub enum ScrollDirection {
@@ -28,41 +28,41 @@ pub enum ScrollAmount {
}
impl ScrollAmount {
pub fn lines(&self, mut visible_line_count: f32) -> f32 {
pub fn lines(&self, mut visible_line_count: f64) -> f64 {
match self {
Self::Line(count) => *count,
Self::Line(count) => *count as f64,
Self::Page(count) => {
// for full pages subtract one to leave an anchor line
if self.is_full_page() {
visible_line_count -= 1.0
}
(visible_line_count * count).trunc()
(visible_line_count * (*count as f64)).trunc()
}
Self::Column(_count) => 0.0,
Self::PageWidth(_count) => 0.0,
}
}
pub fn columns(&self, visible_column_count: f32) -> f32 {
pub fn columns(&self, visible_column_count: f64) -> f64 {
match self {
Self::Line(_count) => 0.0,
Self::Page(_count) => 0.0,
Self::Column(count) => *count,
Self::PageWidth(count) => (visible_column_count * count).trunc(),
Self::Column(count) => *count as f64,
Self::PageWidth(count) => (visible_column_count * *count as f64).trunc(),
}
}
pub fn pixels(&self, line_height: Pixels, height: Pixels) -> Pixels {
match self {
ScrollAmount::Line(x) => px(line_height.0 * x),
ScrollAmount::Page(x) => px(height.0 * x),
ScrollAmount::Line(x) => line_height * *x,
ScrollAmount::Page(x) => height * *x,
// This function seems to only be leveraged by the popover that is
// displayed by the editor when, for example, viewing a function's
// documentation. Right now that only supports vertical scrolling,
// so I'm leaving this at 0.0 for now to try and make it clear that
// this should not have an impact on that?
ScrollAmount::Column(_) => px(0.0),
ScrollAmount::PageWidth(_) => px(0.0),
ScrollAmount::Column(_) => Pixels::ZERO,
ScrollAmount::PageWidth(_) => Pixels::ZERO,
}
}
@@ -275,7 +275,8 @@ impl EditorTestContext {
let details = editor.text_layout_details(window);
let y = pixel_position.y
+ line_height * (display_point.row().as_f32() - newest_point.row().as_f32());
+ f32::from(line_height)
* Pixels::from(display_point.row().as_f64() - newest_point.row().as_f64());
let x = pixel_position.x + snapshot.x_for_display_point(display_point, &details)
- snapshot.x_for_display_point(newest_point, &details);
Point::new(x, y)