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 has_autoscroll_request(&self) -> bool {
369        self.autoscroll_request.is_some()
370    }
371
372    pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
373        self.autoscroll_request.take()
374    }
375
376    pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
377        self.active_scrollbar.as_ref()
378    }
379
380    pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
381        self.active_scrollbar
382            .as_ref()
383            .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
384            .map(|scrollbar| scrollbar.axis)
385    }
386
387    pub fn any_scrollbar_dragged(&self) -> bool {
388        self.active_scrollbar
389            .as_ref()
390            .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
391    }
392
393    pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
394        self.update_active_scrollbar_state(
395            Some(ActiveScrollbarState::new(
396                axis,
397                ScrollbarThumbState::Hovered,
398            )),
399            cx,
400        );
401    }
402
403    pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
404        self.update_active_scrollbar_state(
405            Some(ActiveScrollbarState::new(
406                axis,
407                ScrollbarThumbState::Dragging,
408            )),
409            cx,
410        );
411    }
412
413    pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
414        self.update_active_scrollbar_state(None, cx);
415    }
416
417    fn update_active_scrollbar_state(
418        &mut self,
419        new_state: Option<ActiveScrollbarState>,
420        cx: &mut Context<Editor>,
421    ) {
422        if self.active_scrollbar != new_state {
423            self.active_scrollbar = new_state;
424            cx.notify();
425        }
426    }
427
428    pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) {
429        self.update_minimap_thumb_state(
430            Some(if hovered {
431                ScrollbarThumbState::Hovered
432            } else {
433                ScrollbarThumbState::Idle
434            }),
435            cx,
436        );
437    }
438
439    pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) {
440        self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx);
441    }
442
443    pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
444        self.update_minimap_thumb_state(None, cx);
445    }
446
447    pub fn is_dragging_minimap(&self) -> bool {
448        self.minimap_thumb_state
449            .is_some_and(|state| state == ScrollbarThumbState::Dragging)
450    }
451
452    fn update_minimap_thumb_state(
453        &mut self,
454        thumb_state: Option<ScrollbarThumbState>,
455        cx: &mut Context<Editor>,
456    ) {
457        if self.minimap_thumb_state != thumb_state {
458            self.minimap_thumb_state = thumb_state;
459            cx.notify();
460        }
461    }
462
463    pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> {
464        self.minimap_thumb_state
465    }
466
467    pub fn clamp_scroll_left(&mut self, max: f64) -> bool {
468        if max < self.anchor.offset.x {
469            self.anchor.offset.x = max;
470            true
471        } else {
472            false
473        }
474    }
475
476    pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
477        self.forbid_vertical_scroll = forbid;
478    }
479
480    pub fn forbid_vertical_scroll(&self) -> bool {
481        self.forbid_vertical_scroll
482    }
483}
484
485impl Editor {
486    pub fn vertical_scroll_margin(&self) -> usize {
487        self.scroll_manager.vertical_scroll_margin as usize
488    }
489
490    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
491        self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
492        cx.notify();
493    }
494
495    pub fn visible_line_count(&self) -> Option<f64> {
496        self.scroll_manager.visible_line_count
497    }
498
499    pub fn visible_row_count(&self) -> Option<u32> {
500        self.visible_line_count()
501            .map(|line_count| line_count as u32 - 1)
502    }
503
504    pub fn visible_column_count(&self) -> Option<f64> {
505        self.scroll_manager.visible_column_count
506    }
507
508    pub(crate) fn set_visible_line_count(
509        &mut self,
510        lines: f64,
511        window: &mut Window,
512        cx: &mut Context<Self>,
513    ) {
514        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
515        self.scroll_manager.visible_line_count = Some(lines);
516        if opened_first_time {
517            self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
518                editor
519                    .update_in(cx, |editor, window, cx| {
520                        editor.register_visible_buffers(cx);
521                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
522                        editor.update_lsp_data(None, window, cx);
523                        editor.colorize_brackets(false, cx);
524                    })
525                    .ok();
526            });
527        }
528    }
529
530    pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
531        self.scroll_manager.visible_column_count = Some(columns);
532    }
533
534    pub fn apply_scroll_delta(
535        &mut self,
536        scroll_delta: gpui::Point<f32>,
537        window: &mut Window,
538        cx: &mut Context<Self>,
539    ) {
540        let mut delta = scroll_delta;
541        if self.scroll_manager.forbid_vertical_scroll {
542            delta.y = 0.0;
543        }
544        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
545        let position =
546            self.scroll_manager.anchor.scroll_position(&display_map) + delta.map(f64::from);
547        self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
548    }
549
550    pub fn set_scroll_position(
551        &mut self,
552        scroll_position: gpui::Point<ScrollOffset>,
553        window: &mut Window,
554        cx: &mut Context<Self>,
555    ) -> WasScrolled {
556        let mut position = scroll_position;
557        if self.scroll_manager.forbid_vertical_scroll {
558            let current_position = self.scroll_position(cx);
559            position.y = current_position.y;
560        }
561        self.set_scroll_position_internal(position, true, false, window, cx)
562    }
563
564    /// Scrolls so that `row` is at the top of the editor view.
565    pub fn set_scroll_top_row(
566        &mut self,
567        row: DisplayRow,
568        window: &mut Window,
569        cx: &mut Context<Editor>,
570    ) {
571        let snapshot = self.snapshot(window, cx).display_snapshot;
572        let new_screen_top = DisplayPoint::new(row, 0);
573        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
574        let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
575
576        self.set_scroll_anchor(
577            ScrollAnchor {
578                anchor: new_anchor,
579                offset: Default::default(),
580            },
581            window,
582            cx,
583        );
584    }
585
586    pub(crate) fn set_scroll_position_internal(
587        &mut self,
588        scroll_position: gpui::Point<ScrollOffset>,
589        local: bool,
590        autoscroll: bool,
591        window: &mut Window,
592        cx: &mut Context<Self>,
593    ) -> WasScrolled {
594        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
595        let was_scrolled = self.set_scroll_position_taking_display_map(
596            scroll_position,
597            local,
598            autoscroll,
599            map,
600            window,
601            cx,
602        );
603
604        if local && was_scrolled.0 {
605            if let Some(companion) = self.scroll_companion.as_ref().and_then(|c| c.upgrade()) {
606                companion.update(cx, |companion_editor, cx| {
607                    companion_editor.set_scroll_position_internal(
608                        scroll_position,
609                        false,
610                        false,
611                        window,
612                        cx,
613                    );
614                });
615            }
616        }
617
618        was_scrolled
619    }
620
621    fn set_scroll_position_taking_display_map(
622        &mut self,
623        scroll_position: gpui::Point<ScrollOffset>,
624        local: bool,
625        autoscroll: bool,
626        display_map: DisplaySnapshot,
627        window: &mut Window,
628        cx: &mut Context<Self>,
629    ) -> WasScrolled {
630        hide_hover(self, cx);
631        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
632
633        self.edit_prediction_preview
634            .set_previous_scroll_position(None);
635
636        let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
637            let current_position = self.scroll_manager.anchor.scroll_position(&display_map);
638            gpui::Point::new(scroll_position.x, current_position.y)
639        } else {
640            scroll_position
641        };
642
643        self.scroll_manager.set_scroll_position(
644            adjusted_position,
645            &display_map,
646            local,
647            autoscroll,
648            workspace_id,
649            window,
650            cx,
651        )
652    }
653
654    pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
655        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
656        self.scroll_manager.anchor.scroll_position(&display_map)
657    }
658
659    pub fn set_scroll_anchor(
660        &mut self,
661        scroll_anchor: ScrollAnchor,
662        window: &mut Window,
663        cx: &mut Context<Self>,
664    ) {
665        hide_hover(self, cx);
666        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
667        let top_row = scroll_anchor
668            .anchor
669            .to_point(&self.buffer().read(cx).snapshot(cx))
670            .row;
671        self.scroll_manager.set_anchor(
672            scroll_anchor,
673            top_row,
674            true,
675            false,
676            workspace_id,
677            window,
678            cx,
679        );
680    }
681
682    pub(crate) fn set_scroll_anchor_remote(
683        &mut self,
684        scroll_anchor: ScrollAnchor,
685        window: &mut Window,
686        cx: &mut Context<Self>,
687    ) {
688        hide_hover(self, cx);
689        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
690        let snapshot = &self.buffer().read(cx).snapshot(cx);
691        if !scroll_anchor.anchor.is_valid(snapshot) {
692            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
693            return;
694        }
695        let top_row = scroll_anchor.anchor.to_point(snapshot).row;
696        self.scroll_manager.set_anchor(
697            scroll_anchor,
698            top_row,
699            false,
700            false,
701            workspace_id,
702            window,
703            cx,
704        );
705    }
706
707    pub fn scroll_screen(
708        &mut self,
709        amount: &ScrollAmount,
710        window: &mut Window,
711        cx: &mut Context<Self>,
712    ) {
713        if matches!(self.mode, EditorMode::SingleLine) {
714            cx.propagate();
715            return;
716        }
717
718        if self.take_rename(true, window, cx).is_some() {
719            return;
720        }
721
722        let mut current_position = self.scroll_position(cx);
723        let Some(visible_line_count) = self.visible_line_count() else {
724            return;
725        };
726        let Some(mut visible_column_count) = self.visible_column_count() else {
727            return;
728        };
729
730        // If the user has a preferred line length, and has the editor
731        // configured to wrap at the preferred line length, or bounded to it,
732        // use that value over the visible column count. This was mostly done so
733        // that tests could actually be written for vim's `z l`, `z h`, `z
734        // shift-l` and `z shift-h` commands, as there wasn't a good way to
735        // configure the editor to only display a certain number of columns. If
736        // that ever happens, this could probably be removed.
737        let settings = AllLanguageSettings::get_global(cx);
738        if matches!(
739            settings.defaults.soft_wrap,
740            SoftWrap::PreferredLineLength | SoftWrap::Bounded
741        ) && (settings.defaults.preferred_line_length as f64) < visible_column_count
742        {
743            visible_column_count = settings.defaults.preferred_line_length as f64;
744        }
745
746        // If the scroll position is currently at the left edge of the document
747        // (x == 0.0) and the intent is to scroll right, the gutter's margin
748        // should first be added to the current position, otherwise the cursor
749        // will end at the column position minus the margin, which looks off.
750        if current_position.x == 0.0
751            && amount.columns(visible_column_count) > 0.
752            && let Some(last_position_map) = &self.last_position_map
753        {
754            current_position.x +=
755                f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
756        }
757        let new_position = current_position
758            + point(
759                amount.columns(visible_column_count),
760                amount.lines(visible_line_count),
761            );
762        self.set_scroll_position(new_position, window, cx);
763    }
764
765    /// Returns an ordering. The newest selection is:
766    ///     Ordering::Equal => on screen
767    ///     Ordering::Less => above or to the left of the screen
768    ///     Ordering::Greater => below or to the right of the screen
769    pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
770        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
771        let newest_head = self
772            .selections
773            .newest_anchor()
774            .head()
775            .to_display_point(&snapshot);
776        let screen_top = self
777            .scroll_manager
778            .anchor
779            .anchor
780            .to_display_point(&snapshot);
781
782        if screen_top > newest_head {
783            return Ordering::Less;
784        }
785
786        if let (Some(visible_lines), Some(visible_columns)) =
787            (self.visible_line_count(), self.visible_column_count())
788            && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
789            && newest_head.column() <= screen_top.column() + visible_columns as u32
790        {
791            return Ordering::Equal;
792        }
793
794        Ordering::Greater
795    }
796
797    pub fn read_scroll_position_from_db(
798        &mut self,
799        item_id: u64,
800        workspace_id: WorkspaceId,
801        window: &mut Window,
802        cx: &mut Context<Editor>,
803    ) {
804        let scroll_position = DB.get_scroll_position(item_id, workspace_id);
805        if let Ok(Some((top_row, x, y))) = scroll_position {
806            let top_anchor = self
807                .buffer()
808                .read(cx)
809                .snapshot(cx)
810                .anchor_before(Point::new(top_row, 0));
811            let scroll_anchor = ScrollAnchor {
812                offset: gpui::Point::new(x, y),
813                anchor: top_anchor,
814            };
815            self.set_scroll_anchor(scroll_anchor, window, cx);
816        }
817    }
818}