From 7a5f1b54466da89346605f5cc202ff341e7f9219 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 27 Jul 2021 16:39:57 -0600 Subject: [PATCH] Preserve logical scroll position when wrap width changes --- zed/src/editor.rs | 80 +++++++++++++++++++++++++++------------ zed/src/editor/element.rs | 58 ++++++++++++++++------------ 2 files changed, 89 insertions(+), 49 deletions(-) diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 2dce03764d7a2d89eb1cb50a7525cc366e437159..f97c0fdfe08b33e12718c8585f5f873fc950c666 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -252,7 +252,9 @@ pub fn init(cx: &mut MutableAppContext) { ), ]); - cx.add_action("buffer:scroll", Editor::scroll); + cx.add_action("buffer:scroll", |this: &mut Editor, scroll_position, cx| { + this.set_scroll_position(*scroll_position, cx) + }); cx.add_action("buffer:select", Editor::select); cx.add_action("buffer:cancel", Editor::cancel); cx.add_action("buffer:insert", Editor::insert); @@ -375,6 +377,7 @@ pub struct Editor { add_selections_state: Option, select_larger_syntax_node_stack: Vec>, scroll_position: Vector2F, + scroll_top_anchor: Anchor, autoscroll_requested: bool, settings: watch::Receiver, focused: bool, @@ -387,10 +390,11 @@ pub struct Editor { pub struct Snapshot { pub display_snapshot: DisplayMapSnapshot, pub gutter_visible: bool, - pub scroll_position: Vector2F, pub theme: Arc, pub font_family: FamilyId, pub font_size: f32, + scroll_position: Vector2F, + scroll_top_anchor: Anchor, } struct AddSelectionsState { @@ -446,6 +450,7 @@ impl Editor { add_selections_state: None, select_larger_syntax_node_stack: Vec::new(), scroll_position: Vector2F::zero(), + scroll_top_anchor: Anchor::min(), autoscroll_requested: false, settings, focused: false, @@ -467,21 +472,26 @@ impl Editor { display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)), gutter_visible: !self.single_line, scroll_position: self.scroll_position, + scroll_top_anchor: self.scroll_top_anchor.clone(), theme: settings.theme.clone(), font_family: settings.buffer_font_family, font_size: settings.buffer_font_size, } } - fn scroll(&mut self, scroll_position: &Vector2F, cx: &mut ViewContext) { - self.scroll_position = *scroll_position; + fn set_scroll_position(&mut self, mut scroll_position: Vector2F, cx: &mut ViewContext) { + let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let scroll_top_buffer_offset = + DisplayPoint::new(scroll_position.y() as u32, 0).to_buffer_offset(&map, Bias::Right); + self.scroll_top_anchor = self + .buffer + .read(cx) + .anchor_at(scroll_top_buffer_offset, Bias::Right); + scroll_position.set_y(scroll_position.y().fract()); + self.scroll_position = scroll_position; cx.notify(); } - pub fn scroll_position(&self) -> Vector2F { - self.scroll_position - } - pub fn clamp_scroll_left(&mut self, max: f32) -> bool { if max < self.scroll_position.x() { self.scroll_position.set_x(max); @@ -495,12 +505,16 @@ impl Editor { &mut self, viewport_height: f32, line_height: f32, - cx: &mut MutableAppContext, + cx: &mut ViewContext, ) -> bool { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let scroll_top = self.scroll_position.y(); - self.scroll_position - .set_y(scroll_top.min(display_map.max_point().row().saturating_sub(1) as f32)); + let mut scroll_position = + compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor); + let max_scroll_top = display_map.max_point().row().saturating_sub(1) as f32; + if scroll_position.y() > max_scroll_top { + scroll_position.set_y(max_scroll_top); + self.set_scroll_position(scroll_position, cx); + } if self.autoscroll_requested { self.autoscroll_requested = false; @@ -534,13 +548,15 @@ impl Editor { let target_top = (first_cursor_top - margin).max(0.0); let target_bottom = last_cursor_bottom + margin; - let start_row = self.scroll_position.y(); + let start_row = scroll_position.y(); let end_row = start_row + visible_lines; if target_top < start_row { - self.scroll_position.set_y(target_top); + scroll_position.set_y(target_top); + self.set_scroll_position(scroll_position, cx); } else if target_bottom >= end_row { - self.scroll_position.set_y(target_bottom - visible_lines); + scroll_position.set_y(target_bottom - visible_lines); + self.set_scroll_position(scroll_position, cx); } true @@ -641,8 +657,7 @@ impl Editor { return; } - self.scroll_position = scroll_position; - + self.set_scroll_position(scroll_position, cx); cx.notify(); } @@ -2156,12 +2171,6 @@ impl Editor { } } - pub fn line_len(&self, display_row: u32, cx: &mut MutableAppContext) -> u32 { - self.display_map - .update(cx, |map, cx| map.snapshot(cx)) - .line_len(display_row) - } - pub fn longest_row(&self, cx: &mut MutableAppContext) -> u32 { self.display_map .update(cx, |map, cx| map.snapshot(cx)) @@ -2267,6 +2276,14 @@ impl Editor { } impl Snapshot { + pub fn scroll_position(&self) -> Vector2F { + compute_scroll_position( + &self.display_snapshot, + self.scroll_position, + &self.scroll_top_anchor, + ) + } + pub fn max_point(&self) -> DisplayPoint { self.display_snapshot.max_point() } @@ -2275,6 +2292,10 @@ impl Snapshot { self.display_snapshot.longest_row() } + pub fn line_len(&self, display_row: u32) -> u32 { + self.display_snapshot.line_len(display_row) + } + pub fn font_ascent(&self, font_cache: &FontCache) -> f32 { let font_id = font_cache.default_font(self.font_family); let ascent = font_cache.metric(font_id, |m| m.ascent); @@ -2327,7 +2348,7 @@ impl Snapshot { ) -> Result> { let font_id = font_cache.select_font(self.font_family, &FontProperties::new())?; - let start_row = self.scroll_position.y() as usize; + let start_row = self.scroll_position().y() as usize; let end_row = cmp::min( self.display_snapshot.max_point().row() as usize, start_row + (viewport_height / self.line_height(font_cache)).ceil() as usize, @@ -2450,6 +2471,16 @@ impl Snapshot { } } +fn compute_scroll_position( + snapshot: &DisplayMapSnapshot, + mut scroll_position: Vector2F, + scroll_top_anchor: &Anchor, +) -> Vector2F { + let scroll_top = scroll_top_anchor.to_display_point(snapshot).row() as f32; + scroll_position.set_y(scroll_top + scroll_position.y()); + scroll_position +} + pub enum Event { Activate, Edited, @@ -2552,6 +2583,7 @@ impl workspace::ItemView for Editor { { let mut clone = Editor::for_buffer(self.buffer.clone(), self.settings.clone(), cx); clone.scroll_position = self.scroll_position; + clone.scroll_top_anchor = self.scroll_top_anchor.clone(); Some(clone) } diff --git a/zed/src/editor/element.rs b/zed/src/editor/element.rs index 73f42571e5e22ceb3f5472f507624b576dd299ee..e619ddf7ac770c2b21eb70d214d1d0e4a32b19c2 100644 --- a/zed/src/editor/element.rs +++ b/zed/src/editor/element.rs @@ -41,6 +41,10 @@ impl EditorElement { self.view.upgrade(cx).unwrap().update(cx, f) } + fn snapshot(&self, cx: &mut MutableAppContext) -> Snapshot { + self.update_view(cx, |view, cx| view.snapshot(cx)) + } + fn mouse_down( &self, position: Vector2F, @@ -50,9 +54,8 @@ impl EditorElement { cx: &mut EventContext, ) -> bool { if paint.text_bounds.contains_point(position) { - let position = self.update_view(cx.app, |view, cx| { - paint.point_for_position(view, layout, position, cx) - }); + let snapshot = self.snapshot(cx.app); + let position = paint.point_for_position(&snapshot, layout, position); cx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd }); true } else { @@ -108,15 +111,19 @@ impl EditorElement { let font_cache = cx.font_cache.clone(); let text_layout_cache = cx.text_layout_cache.clone(); - let action = self.update_view(cx.app, |view, cx| SelectAction::Update { - position: paint.point_for_position(view, layout, position, cx), - scroll_position: (view.scroll_position() + scroll_delta).clamp( - Vector2F::zero(), - layout.scroll_max(&font_cache, &text_layout_cache), - ), - }); + let snapshot = self.snapshot(cx.app); + let position = paint.point_for_position(&snapshot, layout, position); - cx.dispatch_action("buffer:select", action); + cx.dispatch_action( + "buffer:select", + SelectAction::Update { + position, + scroll_position: (snapshot.scroll_position() + scroll_delta).clamp( + Vector2F::zero(), + layout.scroll_max(&font_cache, &text_layout_cache), + ), + }, + ); true } else { false @@ -155,7 +162,7 @@ impl EditorElement { return false; } - let view = self.view(cx.app); + let snapshot = self.snapshot(cx.app); let font_cache = &cx.font_cache; let layout_cache = &cx.text_layout_cache; let max_glyph_width = layout.em_width; @@ -163,8 +170,9 @@ impl EditorElement { delta *= vec2f(max_glyph_width, layout.line_height); } - let x = (view.scroll_position().x() * max_glyph_width - delta.x()) / max_glyph_width; - let y = (view.scroll_position().y() * layout.line_height - delta.y()) / layout.line_height; + let scroll_position = snapshot.scroll_position(); + let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width; + let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height; let scroll_position = vec2f(x, y).clamp( Vector2F::zero(), layout.scroll_max(font_cache, layout_cache), @@ -176,7 +184,7 @@ impl EditorElement { } fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, cx: &mut PaintContext) { - let scroll_top = layout.snapshot.scroll_position.y() * layout.line_height; + let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; cx.scene.push_layer(Some(rect)); cx.scene.push_quad(Quad { @@ -204,7 +212,7 @@ impl EditorElement { fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, cx: &mut PaintContext) { let view = self.view(cx.app); - let scroll_position = layout.snapshot.scroll_position; + let scroll_position = layout.snapshot.scroll_position(); let start_row = scroll_position.y() as u32; let scroll_top = scroll_position.y() * layout.line_height; let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen @@ -338,7 +346,7 @@ impl Element for EditorElement { let font_cache = &cx.font_cache; let layout_cache = &cx.text_layout_cache; - let snapshot = self.update_view(cx.app, |view, cx| view.snapshot(cx)); + let snapshot = self.snapshot(cx.app); let line_height = snapshot.line_height(font_cache); let gutter_padding; @@ -393,8 +401,9 @@ impl Element for EditorElement { Vec::new() }; - let start_row = snapshot.scroll_position.y() as u32; - let scroll_top = snapshot.scroll_position.y() * line_height; + let scroll_position = snapshot.scroll_position(); + let start_row = scroll_position.y() as u32; + 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 let mut max_visible_line_width = 0.0; @@ -451,7 +460,7 @@ impl Element for EditorElement { let autoscrolled; if autoscroll_horizontally { autoscrolled = view.autoscroll_horizontally( - view.scroll_position().y() as u32, + start_row, layout.text_size.x(), layout.scroll_width(font_cache, layout_cache), layout.snapshot.em_width(font_cache), @@ -592,23 +601,22 @@ pub struct PaintState { impl PaintState { fn point_for_position( &self, - view: &Editor, + snapshot: &Snapshot, layout: &LayoutState, position: Vector2F, - cx: &mut MutableAppContext, ) -> DisplayPoint { - let scroll_position = view.scroll_position(); + let scroll_position = snapshot.scroll_position(); let position = position - self.text_bounds.origin(); let y = position.y().max(0.0).min(layout.size.y()); let row = ((y / layout.line_height) + scroll_position.y()) as u32; - let row = cmp::min(row, view.max_point(cx).row()); + let row = cmp::min(row, snapshot.max_point().row()); let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize]; let x = position.x() + (scroll_position.x() * layout.em_width); let column = if x >= 0.0 { line.index_for_x(x) .map(|ix| ix as u32) - .unwrap_or(view.line_len(row, cx)) + .unwrap_or(snapshot.line_len(row)) } else { 0 };