scroll.rs

  1mod actions;
  2pub(crate) mod autoscroll;
  3pub(crate) mod scroll_amount;
  4
  5use crate::editor_settings::ScrollBeyondLastLine;
  6use crate::{
  7    Anchor, DisplayPoint, DisplayRow, Editor, EditorEvent, EditorMode, EditorSettings,
  8    InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
  9    display_map::{DisplaySnapshot, ToDisplayPoint},
 10    hover_popover::hide_hover,
 11    persistence::DB,
 12};
 13pub use autoscroll::{Autoscroll, AutoscrollStrategy};
 14use core::fmt::Debug;
 15use gpui::{Along, App, Axis, Context, Pixels, Task, Window, point, px};
 16use language::language_settings::{AllLanguageSettings, SoftWrap};
 17use language::{Bias, Point};
 18pub use scroll_amount::ScrollAmount;
 19use settings::Settings;
 20use std::{
 21    cmp::Ordering,
 22    time::{Duration, Instant},
 23};
 24use ui::scrollbars::ScrollbarAutoHide;
 25use util::ResultExt;
 26use workspace::{ItemId, WorkspaceId};
 27
 28pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
 29const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
 30
 31pub struct WasScrolled(pub(crate) bool);
 32
 33pub type ScrollOffset = f64;
 34pub type ScrollPixelOffset = f64;
 35#[derive(Clone, Copy, Debug, PartialEq)]
 36pub struct ScrollAnchor {
 37    pub offset: gpui::Point<ScrollOffset>,
 38    pub anchor: Anchor,
 39}
 40
 41impl ScrollAnchor {
 42    pub(super) fn new() -> Self {
 43        Self {
 44            offset: gpui::Point::default(),
 45            anchor: Anchor::min(),
 46        }
 47    }
 48
 49    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
 50        self.offset.apply_along(Axis::Vertical, |offset| {
 51            if self.anchor == Anchor::min() {
 52                0.
 53            } else {
 54                let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
 55                (offset + scroll_top).max(0.)
 56            }
 57        })
 58    }
 59
 60    pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
 61        self.anchor.to_point(buffer).row
 62    }
 63}
 64
 65#[derive(Clone, Copy, Debug)]
 66pub struct OngoingScroll {
 67    last_event: Instant,
 68    axis: Option<Axis>,
 69}
 70
 71impl OngoingScroll {
 72    fn new() -> Self {
 73        Self {
 74            last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
 75            axis: None,
 76        }
 77    }
 78
 79    pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
 80        const UNLOCK_PERCENT: f32 = 1.9;
 81        const UNLOCK_LOWER_BOUND: Pixels = px(6.);
 82        let mut axis = self.axis;
 83
 84        let x = delta.x.abs();
 85        let y = delta.y.abs();
 86        let duration = Instant::now().duration_since(self.last_event);
 87        if duration > SCROLL_EVENT_SEPARATION {
 88            //New ongoing scroll will start, determine axis
 89            axis = if x <= y {
 90                Some(Axis::Vertical)
 91            } else {
 92                Some(Axis::Horizontal)
 93            };
 94        } else if x.max(y) >= UNLOCK_LOWER_BOUND {
 95            //Check if the current ongoing will need to unlock
 96            match axis {
 97                Some(Axis::Vertical) => {
 98                    if x > y && x >= y * UNLOCK_PERCENT {
 99                        axis = None;
100                    }
101                }
102
103                Some(Axis::Horizontal) => {
104                    if y > x && y >= x * UNLOCK_PERCENT {
105                        axis = None;
106                    }
107                }
108
109                None => {}
110            }
111        }
112
113        match axis {
114            Some(Axis::Vertical) => {
115                *delta = point(px(0.), delta.y);
116            }
117            Some(Axis::Horizontal) => {
118                *delta = point(delta.x, px(0.));
119            }
120            None => {}
121        }
122
123        axis
124    }
125}
126
127#[derive(Copy, Clone, Default, PartialEq, Eq)]
128pub enum ScrollbarThumbState {
129    #[default]
130    Idle,
131    Hovered,
132    Dragging,
133}
134
135#[derive(PartialEq, Eq)]
136pub struct ActiveScrollbarState {
137    axis: Axis,
138    thumb_state: ScrollbarThumbState,
139}
140
141impl ActiveScrollbarState {
142    pub fn new(axis: Axis, thumb_state: ScrollbarThumbState) -> Self {
143        ActiveScrollbarState { axis, thumb_state }
144    }
145
146    pub fn thumb_state_for_axis(&self, axis: Axis) -> Option<ScrollbarThumbState> {
147        (self.axis == axis).then_some(self.thumb_state)
148    }
149}
150
151pub struct ScrollManager {
152    pub(crate) vertical_scroll_margin: ScrollOffset,
153    anchor: ScrollAnchor,
154    ongoing: OngoingScroll,
155    /// Number of sticky header lines currently being rendered for the current scroll position.
156    sticky_header_line_count: usize,
157    /// The second element indicates whether the autoscroll request is local
158    /// (true) or remote (false). Local requests are initiated by user actions,
159    /// while remote requests come from external sources.
160    autoscroll_request: Option<(Autoscroll, bool)>,
161    last_autoscroll: Option<(
162        gpui::Point<ScrollOffset>,
163        ScrollOffset,
164        ScrollOffset,
165        AutoscrollStrategy,
166    )>,
167    show_scrollbars: bool,
168    hide_scrollbar_task: Option<Task<()>>,
169    active_scrollbar: Option<ActiveScrollbarState>,
170    visible_line_count: Option<f64>,
171    visible_column_count: Option<f64>,
172    forbid_vertical_scroll: bool,
173    minimap_thumb_state: Option<ScrollbarThumbState>,
174}
175
176impl ScrollManager {
177    pub fn new(cx: &mut App) -> Self {
178        ScrollManager {
179            vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin,
180            anchor: ScrollAnchor::new(),
181            ongoing: OngoingScroll::new(),
182            sticky_header_line_count: 0,
183            autoscroll_request: None,
184            show_scrollbars: true,
185            hide_scrollbar_task: None,
186            active_scrollbar: None,
187            last_autoscroll: None,
188            visible_line_count: None,
189            visible_column_count: None,
190            forbid_vertical_scroll: false,
191            minimap_thumb_state: None,
192        }
193    }
194
195    pub fn clone_state(&mut self, other: &Self) {
196        self.anchor = other.anchor;
197        self.ongoing = other.ongoing;
198        self.sticky_header_line_count = other.sticky_header_line_count;
199    }
200
201    pub fn anchor(&self) -> ScrollAnchor {
202        self.anchor
203    }
204
205    pub fn ongoing_scroll(&self) -> OngoingScroll {
206        self.ongoing
207    }
208
209    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
210        self.ongoing.last_event = Instant::now();
211        self.ongoing.axis = axis;
212    }
213
214    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
215        self.anchor.scroll_position(snapshot)
216    }
217
218    pub fn sticky_header_line_count(&self) -> usize {
219        self.sticky_header_line_count
220    }
221
222    pub fn set_sticky_header_line_count(&mut self, count: usize) {
223        self.sticky_header_line_count = count;
224    }
225
226    fn set_scroll_position(
227        &mut self,
228        scroll_position: gpui::Point<ScrollOffset>,
229        map: &DisplaySnapshot,
230        local: bool,
231        autoscroll: bool,
232        workspace_id: Option<WorkspaceId>,
233        window: &mut Window,
234        cx: &mut Context<Editor>,
235    ) -> WasScrolled {
236        let scroll_top = scroll_position.y.max(0.);
237        let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
238            ScrollBeyondLastLine::OnePage => scroll_top,
239            ScrollBeyondLastLine::Off => {
240                if let Some(height_in_lines) = self.visible_line_count {
241                    let max_row = map.max_point().row().as_f64();
242                    scroll_top.min(max_row - height_in_lines + 1.).max(0.)
243                } else {
244                    scroll_top
245                }
246            }
247            ScrollBeyondLastLine::VerticalScrollMargin => {
248                if let Some(height_in_lines) = self.visible_line_count {
249                    let max_row = map.max_point().row().as_f64();
250                    scroll_top
251                        .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
252                        .max(0.)
253                } else {
254                    scroll_top
255                }
256            }
257        };
258
259        let scroll_top_row = DisplayRow(scroll_top as u32);
260        let scroll_top_buffer_point = map
261            .clip_point(
262                DisplayPoint::new(scroll_top_row, scroll_position.x as u32),
263                Bias::Left,
264            )
265            .to_point(map);
266        // Anchor the scroll position to the *left* of the first visible buffer point.
267        //
268        // This prevents the viewport from shifting down when blocks (e.g. expanded diff hunk
269        // deletions) are inserted *above* the first buffer character in the file.
270        let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point);
271
272        self.set_anchor(
273            ScrollAnchor {
274                anchor: top_anchor,
275                offset: point(
276                    scroll_position.x.max(0.),
277                    scroll_top - top_anchor.to_display_point(map).row().as_f64(),
278                ),
279            },
280            scroll_top_buffer_point.row,
281            local,
282            autoscroll,
283            workspace_id,
284            window,
285            cx,
286        )
287    }
288
289    fn set_anchor(
290        &mut self,
291        anchor: ScrollAnchor,
292        top_row: u32,
293        local: bool,
294        autoscroll: bool,
295        workspace_id: Option<WorkspaceId>,
296        window: &mut Window,
297        cx: &mut Context<Editor>,
298    ) -> WasScrolled {
299        let adjusted_anchor = if self.forbid_vertical_scroll {
300            ScrollAnchor {
301                offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y),
302                anchor: self.anchor.anchor,
303            }
304        } else {
305            anchor
306        };
307
308        self.autoscroll_request.take();
309        if self.anchor == adjusted_anchor {
310            return WasScrolled(false);
311        }
312
313        self.anchor = adjusted_anchor;
314        cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
315        self.show_scrollbars(window, cx);
316        if let Some(workspace_id) = workspace_id {
317            let item_id = cx.entity().entity_id().as_u64() as ItemId;
318
319            cx.foreground_executor()
320                .spawn(async move {
321                    log::debug!(
322                        "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}"
323                    );
324                    DB.save_scroll_position(
325                        item_id,
326                        workspace_id,
327                        top_row,
328                        anchor.offset.x,
329                        anchor.offset.y,
330                    )
331                    .await
332                    .log_err()
333                })
334                .detach()
335        }
336        cx.notify();
337
338        WasScrolled(true)
339    }
340
341    pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
342        if !self.show_scrollbars {
343            self.show_scrollbars = true;
344            cx.notify();
345        }
346
347        if cx.default_global::<ScrollbarAutoHide>().should_hide() {
348            self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
349                cx.background_executor()
350                    .timer(SCROLLBAR_SHOW_INTERVAL)
351                    .await;
352                editor
353                    .update(cx, |editor, cx| {
354                        editor.scroll_manager.show_scrollbars = false;
355                        cx.notify();
356                    })
357                    .log_err();
358            }));
359        } else {
360            self.hide_scrollbar_task = None;
361        }
362    }
363
364    pub fn scrollbars_visible(&self) -> bool {
365        self.show_scrollbars
366    }
367
368    pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
369        self.autoscroll_request.take()
370    }
371
372    pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
373        self.active_scrollbar.as_ref()
374    }
375
376    pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
377        self.active_scrollbar
378            .as_ref()
379            .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
380            .map(|scrollbar| scrollbar.axis)
381    }
382
383    pub fn any_scrollbar_dragged(&self) -> bool {
384        self.active_scrollbar
385            .as_ref()
386            .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
387    }
388
389    pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
390        self.update_active_scrollbar_state(
391            Some(ActiveScrollbarState::new(
392                axis,
393                ScrollbarThumbState::Hovered,
394            )),
395            cx,
396        );
397    }
398
399    pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
400        self.update_active_scrollbar_state(
401            Some(ActiveScrollbarState::new(
402                axis,
403                ScrollbarThumbState::Dragging,
404            )),
405            cx,
406        );
407    }
408
409    pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
410        self.update_active_scrollbar_state(None, cx);
411    }
412
413    fn update_active_scrollbar_state(
414        &mut self,
415        new_state: Option<ActiveScrollbarState>,
416        cx: &mut Context<Editor>,
417    ) {
418        if self.active_scrollbar != new_state {
419            self.active_scrollbar = new_state;
420            cx.notify();
421        }
422    }
423
424    pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) {
425        self.update_minimap_thumb_state(
426            Some(if hovered {
427                ScrollbarThumbState::Hovered
428            } else {
429                ScrollbarThumbState::Idle
430            }),
431            cx,
432        );
433    }
434
435    pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) {
436        self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx);
437    }
438
439    pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
440        self.update_minimap_thumb_state(None, cx);
441    }
442
443    pub fn is_dragging_minimap(&self) -> bool {
444        self.minimap_thumb_state
445            .is_some_and(|state| state == ScrollbarThumbState::Dragging)
446    }
447
448    fn update_minimap_thumb_state(
449        &mut self,
450        thumb_state: Option<ScrollbarThumbState>,
451        cx: &mut Context<Editor>,
452    ) {
453        if self.minimap_thumb_state != thumb_state {
454            self.minimap_thumb_state = thumb_state;
455            cx.notify();
456        }
457    }
458
459    pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> {
460        self.minimap_thumb_state
461    }
462
463    pub fn clamp_scroll_left(&mut self, max: f64) -> bool {
464        if max < self.anchor.offset.x {
465            self.anchor.offset.x = max;
466            true
467        } else {
468            false
469        }
470    }
471
472    pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
473        self.forbid_vertical_scroll = forbid;
474    }
475
476    pub fn forbid_vertical_scroll(&self) -> bool {
477        self.forbid_vertical_scroll
478    }
479}
480
481impl Editor {
482    pub fn vertical_scroll_margin(&self) -> usize {
483        self.scroll_manager.vertical_scroll_margin as usize
484    }
485
486    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
487        self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
488        cx.notify();
489    }
490
491    pub fn visible_line_count(&self) -> Option<f64> {
492        self.scroll_manager.visible_line_count
493    }
494
495    pub fn visible_row_count(&self) -> Option<u32> {
496        self.visible_line_count()
497            .map(|line_count| line_count as u32 - 1)
498    }
499
500    pub fn visible_column_count(&self) -> Option<f64> {
501        self.scroll_manager.visible_column_count
502    }
503
504    pub(crate) fn set_visible_line_count(
505        &mut self,
506        lines: f64,
507        window: &mut Window,
508        cx: &mut Context<Self>,
509    ) {
510        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
511        self.scroll_manager.visible_line_count = Some(lines);
512        if opened_first_time {
513            self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
514                editor
515                    .update_in(cx, |editor, window, cx| {
516                        editor.register_visible_buffers(cx);
517                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
518                        editor.update_lsp_data(None, window, cx);
519                        editor.colorize_brackets(false, cx);
520                    })
521                    .ok();
522            });
523        }
524    }
525
526    pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
527        self.scroll_manager.visible_column_count = Some(columns);
528    }
529
530    pub fn apply_scroll_delta(
531        &mut self,
532        scroll_delta: gpui::Point<f32>,
533        window: &mut Window,
534        cx: &mut Context<Self>,
535    ) {
536        let mut delta = scroll_delta;
537        if self.scroll_manager.forbid_vertical_scroll {
538            delta.y = 0.0;
539        }
540        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
541        let position =
542            self.scroll_manager.anchor.scroll_position(&display_map) + delta.map(f64::from);
543        self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
544    }
545
546    pub fn set_scroll_position(
547        &mut self,
548        scroll_position: gpui::Point<ScrollOffset>,
549        window: &mut Window,
550        cx: &mut Context<Self>,
551    ) -> WasScrolled {
552        let mut position = scroll_position;
553        if self.scroll_manager.forbid_vertical_scroll {
554            let current_position = self.scroll_position(cx);
555            position.y = current_position.y;
556        }
557        self.set_scroll_position_internal(position, true, false, window, cx)
558    }
559
560    /// Scrolls so that `row` is at the top of the editor view.
561    pub fn set_scroll_top_row(
562        &mut self,
563        row: DisplayRow,
564        window: &mut Window,
565        cx: &mut Context<Editor>,
566    ) {
567        let snapshot = self.snapshot(window, cx).display_snapshot;
568        let new_screen_top = DisplayPoint::new(row, 0);
569        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
570        let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
571
572        self.set_scroll_anchor(
573            ScrollAnchor {
574                anchor: new_anchor,
575                offset: Default::default(),
576            },
577            window,
578            cx,
579        );
580    }
581
582    pub(crate) fn set_scroll_position_internal(
583        &mut self,
584        scroll_position: gpui::Point<ScrollOffset>,
585        local: bool,
586        autoscroll: bool,
587        window: &mut Window,
588        cx: &mut Context<Self>,
589    ) -> WasScrolled {
590        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
591        self.set_scroll_position_taking_display_map(
592            scroll_position,
593            local,
594            autoscroll,
595            map,
596            window,
597            cx,
598        )
599    }
600
601    fn set_scroll_position_taking_display_map(
602        &mut self,
603        scroll_position: gpui::Point<ScrollOffset>,
604        local: bool,
605        autoscroll: bool,
606        display_map: DisplaySnapshot,
607        window: &mut Window,
608        cx: &mut Context<Self>,
609    ) -> WasScrolled {
610        hide_hover(self, cx);
611        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
612
613        self.edit_prediction_preview
614            .set_previous_scroll_position(None);
615
616        let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
617            let current_position = self.scroll_manager.anchor.scroll_position(&display_map);
618            gpui::Point::new(scroll_position.x, current_position.y)
619        } else {
620            scroll_position
621        };
622
623        self.scroll_manager.set_scroll_position(
624            adjusted_position,
625            &display_map,
626            local,
627            autoscroll,
628            workspace_id,
629            window,
630            cx,
631        )
632    }
633
634    pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
635        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
636        self.scroll_manager.anchor.scroll_position(&display_map)
637    }
638
639    pub fn set_scroll_anchor(
640        &mut self,
641        scroll_anchor: ScrollAnchor,
642        window: &mut Window,
643        cx: &mut Context<Self>,
644    ) {
645        hide_hover(self, cx);
646        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
647        let top_row = scroll_anchor
648            .anchor
649            .to_point(&self.buffer().read(cx).snapshot(cx))
650            .row;
651        self.scroll_manager.set_anchor(
652            scroll_anchor,
653            top_row,
654            true,
655            false,
656            workspace_id,
657            window,
658            cx,
659        );
660    }
661
662    pub(crate) fn set_scroll_anchor_remote(
663        &mut self,
664        scroll_anchor: ScrollAnchor,
665        window: &mut Window,
666        cx: &mut Context<Self>,
667    ) {
668        hide_hover(self, cx);
669        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
670        let snapshot = &self.buffer().read(cx).snapshot(cx);
671        if !scroll_anchor.anchor.is_valid(snapshot) {
672            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
673            return;
674        }
675        let top_row = scroll_anchor.anchor.to_point(snapshot).row;
676        self.scroll_manager.set_anchor(
677            scroll_anchor,
678            top_row,
679            false,
680            false,
681            workspace_id,
682            window,
683            cx,
684        );
685    }
686
687    pub fn scroll_screen(
688        &mut self,
689        amount: &ScrollAmount,
690        window: &mut Window,
691        cx: &mut Context<Self>,
692    ) {
693        if matches!(self.mode, EditorMode::SingleLine) {
694            cx.propagate();
695            return;
696        }
697
698        if self.take_rename(true, window, cx).is_some() {
699            return;
700        }
701
702        let mut current_position = self.scroll_position(cx);
703        let Some(visible_line_count) = self.visible_line_count() else {
704            return;
705        };
706        let Some(mut visible_column_count) = self.visible_column_count() else {
707            return;
708        };
709
710        // If the user has a preferred line length, and has the editor
711        // configured to wrap at the preferred line length, or bounded to it,
712        // use that value over the visible column count. This was mostly done so
713        // that tests could actually be written for vim's `z l`, `z h`, `z
714        // shift-l` and `z shift-h` commands, as there wasn't a good way to
715        // configure the editor to only display a certain number of columns. If
716        // that ever happens, this could probably be removed.
717        let settings = AllLanguageSettings::get_global(cx);
718        if matches!(
719            settings.defaults.soft_wrap,
720            SoftWrap::PreferredLineLength | SoftWrap::Bounded
721        ) && (settings.defaults.preferred_line_length as f64) < visible_column_count
722        {
723            visible_column_count = settings.defaults.preferred_line_length as f64;
724        }
725
726        // If the scroll position is currently at the left edge of the document
727        // (x == 0.0) and the intent is to scroll right, the gutter's margin
728        // should first be added to the current position, otherwise the cursor
729        // will end at the column position minus the margin, which looks off.
730        if current_position.x == 0.0
731            && amount.columns(visible_column_count) > 0.
732            && let Some(last_position_map) = &self.last_position_map
733        {
734            current_position.x +=
735                f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
736        }
737        let new_position = current_position
738            + point(
739                amount.columns(visible_column_count),
740                amount.lines(visible_line_count),
741            );
742        self.set_scroll_position(new_position, window, cx);
743    }
744
745    /// Returns an ordering. The newest selection is:
746    ///     Ordering::Equal => on screen
747    ///     Ordering::Less => above or to the left of the screen
748    ///     Ordering::Greater => below or to the right of the screen
749    pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
750        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
751        let newest_head = self
752            .selections
753            .newest_anchor()
754            .head()
755            .to_display_point(&snapshot);
756        let screen_top = self
757            .scroll_manager
758            .anchor
759            .anchor
760            .to_display_point(&snapshot);
761
762        if screen_top > newest_head {
763            return Ordering::Less;
764        }
765
766        if let (Some(visible_lines), Some(visible_columns)) =
767            (self.visible_line_count(), self.visible_column_count())
768            && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
769            && newest_head.column() <= screen_top.column() + visible_columns as u32
770        {
771            return Ordering::Equal;
772        }
773
774        Ordering::Greater
775    }
776
777    pub fn read_scroll_position_from_db(
778        &mut self,
779        item_id: u64,
780        workspace_id: WorkspaceId,
781        window: &mut Window,
782        cx: &mut Context<Editor>,
783    ) {
784        let scroll_position = DB.get_scroll_position(item_id, workspace_id);
785        if let Ok(Some((top_row, x, y))) = scroll_position {
786            let top_anchor = self
787                .buffer()
788                .read(cx)
789                .snapshot(cx)
790                .anchor_before(Point::new(top_row, 0));
791            let scroll_anchor = ScrollAnchor {
792                offset: gpui::Point::new(x, y),
793                anchor: top_anchor,
794            };
795            self.set_scroll_anchor(scroll_anchor, window, cx);
796        }
797    }
798}