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    MultiBufferSnapshot, RowExt, SizingBehavior, ToPoint,
  9    display_map::{DisplaySnapshot, ToDisplayPoint},
 10    hover_popover::hide_hover,
 11    persistence::EditorDb,
 12};
 13pub use autoscroll::{Autoscroll, AutoscrollStrategy};
 14use core::fmt::Debug;
 15use gpui::{
 16    Along, App, AppContext as _, Axis, Context, Entity, EntityId, Pixels, Task, Window, point, px,
 17};
 18use language::language_settings::{AllLanguageSettings, SoftWrap};
 19use language::{Bias, Point};
 20pub use scroll_amount::ScrollAmount;
 21use settings::Settings;
 22use std::{
 23    cmp::Ordering,
 24    time::{Duration, Instant},
 25};
 26use ui::scrollbars::ScrollbarAutoHide;
 27use util::ResultExt;
 28use workspace::{ItemId, WorkspaceId};
 29
 30pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
 31const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
 32
 33pub struct WasScrolled(pub(crate) bool);
 34
 35pub type ScrollOffset = f64;
 36pub type ScrollPixelOffset = f64;
 37#[derive(Clone, Copy, Debug, PartialEq)]
 38pub struct ScrollAnchor {
 39    pub offset: gpui::Point<ScrollOffset>,
 40    pub anchor: Anchor,
 41}
 42
 43impl ScrollAnchor {
 44    pub(super) fn new() -> Self {
 45        Self {
 46            offset: gpui::Point::default(),
 47            anchor: Anchor::Min,
 48        }
 49    }
 50
 51    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
 52        self.offset.apply_along(Axis::Vertical, |offset| {
 53            if self.anchor == Anchor::Min {
 54                0.
 55            } else {
 56                let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
 57                (offset + scroll_top).max(0.)
 58            }
 59        })
 60    }
 61
 62    pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
 63        self.anchor.to_point(buffer).row
 64    }
 65}
 66
 67#[derive(Clone, Copy, Debug)]
 68pub struct OngoingScroll {
 69    last_event: Instant,
 70    axis: Option<Axis>,
 71}
 72
 73/// In the split diff view, the two sides share a ScrollAnchor using this struct.
 74/// Either side can set a ScrollAnchor that points to its own multibuffer, and we store the ID of the display map
 75/// that the last-written anchor came from so that we know how to resolve it to a DisplayPoint.
 76///
 77/// For normal editors, this just acts as a wrapper around a ScrollAnchor.
 78#[derive(Clone, Copy, Debug)]
 79pub struct SharedScrollAnchor {
 80    pub scroll_anchor: ScrollAnchor,
 81    pub display_map_id: Option<EntityId>,
 82}
 83
 84impl SharedScrollAnchor {
 85    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
 86        let snapshot = if let Some(display_map_id) = self.display_map_id
 87            && display_map_id != snapshot.display_map_id
 88        {
 89            let companion_snapshot = snapshot.companion_snapshot().unwrap();
 90            assert_eq!(companion_snapshot.display_map_id, display_map_id);
 91            companion_snapshot
 92        } else {
 93            snapshot
 94        };
 95
 96        self.scroll_anchor.scroll_position(snapshot)
 97    }
 98
 99    pub fn scroll_top_display_point(&self, snapshot: &DisplaySnapshot) -> DisplayPoint {
100        let snapshot = if let Some(display_map_id) = self.display_map_id
101            && display_map_id != snapshot.display_map_id
102        {
103            let companion_snapshot = snapshot.companion_snapshot().unwrap();
104            assert_eq!(companion_snapshot.display_map_id, display_map_id);
105            companion_snapshot
106        } else {
107            snapshot
108        };
109
110        self.scroll_anchor.anchor.to_display_point(snapshot)
111    }
112}
113
114impl OngoingScroll {
115    fn new() -> Self {
116        Self {
117            last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
118            axis: None,
119        }
120    }
121
122    pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
123        const UNLOCK_PERCENT: f32 = 1.9;
124        const UNLOCK_LOWER_BOUND: Pixels = px(6.);
125        let mut axis = self.axis;
126
127        let x = delta.x.abs();
128        let y = delta.y.abs();
129        let duration = Instant::now().duration_since(self.last_event);
130        if duration > SCROLL_EVENT_SEPARATION {
131            //New ongoing scroll will start, determine axis
132            axis = if x <= y {
133                Some(Axis::Vertical)
134            } else {
135                Some(Axis::Horizontal)
136            };
137        } else if x.max(y) >= UNLOCK_LOWER_BOUND {
138            //Check if the current ongoing will need to unlock
139            match axis {
140                Some(Axis::Vertical) => {
141                    if x > y && x >= y * UNLOCK_PERCENT {
142                        axis = None;
143                    }
144                }
145
146                Some(Axis::Horizontal) => {
147                    if y > x && y >= x * UNLOCK_PERCENT {
148                        axis = None;
149                    }
150                }
151
152                None => {}
153            }
154        }
155
156        match axis {
157            Some(Axis::Vertical) => {
158                *delta = point(px(0.), delta.y);
159            }
160            Some(Axis::Horizontal) => {
161                *delta = point(delta.x, px(0.));
162            }
163            None => {}
164        }
165
166        axis
167    }
168}
169
170#[derive(Copy, Clone, Default, PartialEq, Eq)]
171pub enum ScrollbarThumbState {
172    #[default]
173    Idle,
174    Hovered,
175    Dragging,
176}
177
178#[derive(PartialEq, Eq)]
179pub struct ActiveScrollbarState {
180    axis: Axis,
181    thumb_state: ScrollbarThumbState,
182}
183
184impl ActiveScrollbarState {
185    pub fn new(axis: Axis, thumb_state: ScrollbarThumbState) -> Self {
186        ActiveScrollbarState { axis, thumb_state }
187    }
188
189    pub fn thumb_state_for_axis(&self, axis: Axis) -> Option<ScrollbarThumbState> {
190        (self.axis == axis).then_some(self.thumb_state)
191    }
192}
193
194pub struct ScrollManager {
195    pub(crate) vertical_scroll_margin: ScrollOffset,
196    anchor: Entity<SharedScrollAnchor>,
197    /// Value to be used for clamping the x component of the SharedScrollAnchor's offset.
198    ///
199    /// We store this outside the SharedScrollAnchor so that the two sides of a split diff can share
200    /// a horizontal scroll offset that may be out of range for one of the editors (when one side is wider than the other).
201    /// Each side separately clamps the x component using its own scroll_max_x when reading from the SharedScrollAnchor.
202    scroll_max_x: Option<f64>,
203    ongoing: OngoingScroll,
204    /// Number of sticky header lines currently being rendered for the current scroll position.
205    sticky_header_line_count: usize,
206    /// The second element indicates whether the autoscroll request is local
207    /// (true) or remote (false). Local requests are initiated by user actions,
208    /// while remote requests come from external sources.
209    autoscroll_request: Option<(Autoscroll, bool)>,
210    last_autoscroll: Option<(
211        gpui::Point<ScrollOffset>,
212        ScrollOffset,
213        ScrollOffset,
214        AutoscrollStrategy,
215    )>,
216    show_scrollbars: bool,
217    hide_scrollbar_task: Option<Task<()>>,
218    active_scrollbar: Option<ActiveScrollbarState>,
219    visible_line_count: Option<f64>,
220    visible_column_count: Option<f64>,
221    forbid_vertical_scroll: bool,
222    minimap_thumb_state: Option<ScrollbarThumbState>,
223    _save_scroll_position_task: Task<()>,
224}
225
226impl ScrollManager {
227    pub fn new(cx: &mut Context<Editor>) -> Self {
228        let anchor = cx.new(|_| SharedScrollAnchor {
229            scroll_anchor: ScrollAnchor::new(),
230            display_map_id: None,
231        });
232        ScrollManager {
233            vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin,
234            anchor,
235            scroll_max_x: None,
236            ongoing: OngoingScroll::new(),
237            sticky_header_line_count: 0,
238            autoscroll_request: None,
239            show_scrollbars: true,
240            hide_scrollbar_task: None,
241            active_scrollbar: None,
242            last_autoscroll: None,
243            visible_line_count: None,
244            visible_column_count: None,
245            forbid_vertical_scroll: false,
246            minimap_thumb_state: None,
247            _save_scroll_position_task: Task::ready(()),
248        }
249    }
250
251    pub fn set_native_display_map_id(
252        &mut self,
253        display_map_id: EntityId,
254        cx: &mut Context<Editor>,
255    ) {
256        self.anchor.update(cx, |shared, _| {
257            if shared.display_map_id.is_none() {
258                shared.display_map_id = Some(display_map_id);
259            }
260        });
261    }
262
263    pub fn clone_state(
264        &mut self,
265        other: &Self,
266        other_snapshot: &DisplaySnapshot,
267        my_snapshot: &DisplaySnapshot,
268        cx: &mut Context<Editor>,
269    ) {
270        let native_anchor = other.native_anchor(other_snapshot, cx);
271        self.anchor.update(cx, |this, _| {
272            this.scroll_anchor = native_anchor;
273            this.display_map_id = Some(my_snapshot.display_map_id);
274        });
275        self.ongoing = other.ongoing;
276        self.sticky_header_line_count = other.sticky_header_line_count;
277    }
278
279    pub fn offset(&self, cx: &App) -> gpui::Point<f64> {
280        let mut offset = self.anchor.read(cx).scroll_anchor.offset;
281        if let Some(max_x) = self.scroll_max_x {
282            offset.x = offset.x.min(max_x);
283        }
284        offset
285    }
286
287    /// Get a ScrollAnchor whose `anchor` field is guaranteed to point into the multibuffer for the provided snapshot.
288    ///
289    /// For normal editors, this just retrieves the internal ScrollAnchor and is lossless. When the editor is part of a split diff,
290    /// we may need to translate the anchor to point to the "native" multibuffer first. That translation is lossy,
291    /// so this method should be used sparingly---if you just need a scroll position or display point, call the appropriate helper method instead,
292    /// since they can losslessly handle the case where the ScrollAnchor was last set from the other side.
293    pub fn native_anchor(&self, snapshot: &DisplaySnapshot, cx: &App) -> ScrollAnchor {
294        let shared = self.anchor.read(cx);
295
296        let mut result = if let Some(display_map_id) = shared.display_map_id
297            && display_map_id != snapshot.display_map_id
298        {
299            let companion_snapshot = snapshot.companion_snapshot().unwrap();
300            assert_eq!(companion_snapshot.display_map_id, display_map_id);
301            let mut display_point = shared
302                .scroll_anchor
303                .anchor
304                .to_display_point(companion_snapshot);
305            *display_point.column_mut() = 0;
306            let buffer_point = snapshot.display_point_to_point(display_point, Bias::Left);
307            let anchor = snapshot.buffer_snapshot().anchor_before(buffer_point);
308            ScrollAnchor {
309                anchor,
310                offset: shared.scroll_anchor.offset,
311            }
312        } else {
313            shared.scroll_anchor
314        };
315
316        if let Some(max_x) = self.scroll_max_x {
317            result.offset.x = result.offset.x.min(max_x);
318        }
319        result
320    }
321
322    pub fn shared_scroll_anchor(&self, cx: &App) -> SharedScrollAnchor {
323        let mut shared = *self.anchor.read(cx);
324        if let Some(max_x) = self.scroll_max_x {
325            shared.scroll_anchor.offset.x = shared.scroll_anchor.offset.x.min(max_x);
326        }
327        shared
328    }
329
330    pub fn scroll_top_display_point(&self, snapshot: &DisplaySnapshot, cx: &App) -> DisplayPoint {
331        self.anchor.read(cx).scroll_top_display_point(snapshot)
332    }
333
334    pub fn scroll_anchor_entity(&self) -> Entity<SharedScrollAnchor> {
335        self.anchor.clone()
336    }
337
338    pub fn set_shared_scroll_anchor(&mut self, entity: Entity<SharedScrollAnchor>) {
339        self.anchor = entity;
340    }
341
342    pub fn ongoing_scroll(&self) -> OngoingScroll {
343        self.ongoing
344    }
345
346    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
347        self.ongoing.last_event = Instant::now();
348        self.ongoing.axis = axis;
349    }
350
351    pub fn scroll_position(
352        &self,
353        snapshot: &DisplaySnapshot,
354        cx: &App,
355    ) -> gpui::Point<ScrollOffset> {
356        let mut pos = self.anchor.read(cx).scroll_position(snapshot);
357        if let Some(max_x) = self.scroll_max_x {
358            pos.x = pos.x.min(max_x);
359        }
360        pos
361    }
362
363    pub fn sticky_header_line_count(&self) -> usize {
364        self.sticky_header_line_count
365    }
366
367    pub fn set_sticky_header_line_count(&mut self, count: usize) {
368        self.sticky_header_line_count = count;
369    }
370
371    fn set_scroll_position(
372        &mut self,
373        scroll_position: gpui::Point<ScrollOffset>,
374        map: &DisplaySnapshot,
375        scroll_beyond_last_line: ScrollBeyondLastLine,
376        local: bool,
377        autoscroll: bool,
378        workspace_id: Option<WorkspaceId>,
379        window: &mut Window,
380        cx: &mut Context<Editor>,
381    ) -> WasScrolled {
382        let scroll_top = scroll_position.y.max(0.);
383        let scroll_top = match scroll_beyond_last_line {
384            ScrollBeyondLastLine::OnePage => scroll_top,
385            ScrollBeyondLastLine::Off => {
386                if let Some(height_in_lines) = self.visible_line_count {
387                    let max_row = map.max_point().row().as_f64();
388                    scroll_top.min(max_row - height_in_lines + 1.).max(0.)
389                } else {
390                    scroll_top
391                }
392            }
393            ScrollBeyondLastLine::VerticalScrollMargin => {
394                if let Some(height_in_lines) = self.visible_line_count {
395                    let max_row = map.max_point().row().as_f64();
396                    scroll_top
397                        .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
398                        .max(0.)
399                } else {
400                    scroll_top
401                }
402            }
403        };
404        let scroll_top_row = DisplayRow(scroll_top as u32);
405        let scroll_top_buffer_point = map
406            .clip_point(
407                DisplayPoint::new(scroll_top_row, scroll_position.x as u32),
408                Bias::Left,
409            )
410            .to_point(map);
411        let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point);
412
413        self.set_anchor(
414            ScrollAnchor {
415                anchor: top_anchor,
416                offset: point(
417                    scroll_position.x.max(0.),
418                    scroll_top - top_anchor.to_display_point(map).row().as_f64(),
419                ),
420            },
421            map,
422            scroll_top_buffer_point.row,
423            local,
424            autoscroll,
425            workspace_id,
426            window,
427            cx,
428        )
429    }
430
431    fn set_anchor(
432        &mut self,
433        anchor: ScrollAnchor,
434        display_map: &DisplaySnapshot,
435        top_row: u32,
436        local: bool,
437        autoscroll: bool,
438        workspace_id: Option<WorkspaceId>,
439        window: &mut Window,
440        cx: &mut Context<Editor>,
441    ) -> WasScrolled {
442        let adjusted_anchor = if self.forbid_vertical_scroll {
443            let current = self.anchor.read(cx);
444            ScrollAnchor {
445                offset: gpui::Point::new(anchor.offset.x, current.scroll_anchor.offset.y),
446                anchor: current.scroll_anchor.anchor,
447            }
448        } else {
449            anchor
450        };
451
452        self.scroll_max_x.take();
453        self.autoscroll_request.take();
454
455        let current = self.anchor.read(cx);
456        if current.scroll_anchor == adjusted_anchor {
457            return WasScrolled(false);
458        }
459
460        self.anchor.update(cx, |shared, _| {
461            shared.scroll_anchor = adjusted_anchor;
462            shared.display_map_id = Some(display_map.display_map_id);
463        });
464        cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
465        self.show_scrollbars(window, cx);
466        if let Some(workspace_id) = workspace_id {
467            let item_id = cx.entity().entity_id().as_u64() as ItemId;
468            let executor = cx.background_executor().clone();
469
470            let db = EditorDb::global(cx);
471            self._save_scroll_position_task = cx.background_executor().spawn(async move {
472                executor.timer(Duration::from_millis(10)).await;
473                log::debug!(
474                    "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}"
475                );
476                db.save_scroll_position(
477                    item_id,
478                    workspace_id,
479                    top_row,
480                    anchor.offset.x,
481                    anchor.offset.y,
482                )
483                .await
484                .log_err();
485            });
486        }
487        cx.notify();
488
489        WasScrolled(true)
490    }
491
492    pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
493        if !self.show_scrollbars {
494            self.show_scrollbars = true;
495            cx.notify();
496        }
497
498        if cx.default_global::<ScrollbarAutoHide>().should_hide() {
499            self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
500                cx.background_executor()
501                    .timer(SCROLLBAR_SHOW_INTERVAL)
502                    .await;
503                editor
504                    .update(cx, |editor, cx| {
505                        editor.scroll_manager.show_scrollbars = false;
506                        cx.notify();
507                    })
508                    .log_err();
509            }));
510        } else {
511            self.hide_scrollbar_task = None;
512        }
513    }
514
515    pub fn scrollbars_visible(&self) -> bool {
516        self.show_scrollbars
517    }
518
519    pub fn has_autoscroll_request(&self) -> bool {
520        self.autoscroll_request.is_some()
521    }
522
523    pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
524        self.autoscroll_request.take()
525    }
526
527    pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
528        self.active_scrollbar.as_ref()
529    }
530
531    pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
532        self.active_scrollbar
533            .as_ref()
534            .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
535            .map(|scrollbar| scrollbar.axis)
536    }
537
538    pub fn any_scrollbar_dragged(&self) -> bool {
539        self.active_scrollbar
540            .as_ref()
541            .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
542    }
543
544    pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
545        self.update_active_scrollbar_state(
546            Some(ActiveScrollbarState::new(
547                axis,
548                ScrollbarThumbState::Hovered,
549            )),
550            cx,
551        );
552    }
553
554    pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
555        self.update_active_scrollbar_state(
556            Some(ActiveScrollbarState::new(
557                axis,
558                ScrollbarThumbState::Dragging,
559            )),
560            cx,
561        );
562    }
563
564    pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
565        self.update_active_scrollbar_state(None, cx);
566    }
567
568    fn update_active_scrollbar_state(
569        &mut self,
570        new_state: Option<ActiveScrollbarState>,
571        cx: &mut Context<Editor>,
572    ) {
573        if self.active_scrollbar != new_state {
574            self.active_scrollbar = new_state;
575            cx.notify();
576        }
577    }
578
579    pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) {
580        self.update_minimap_thumb_state(
581            Some(if hovered {
582                ScrollbarThumbState::Hovered
583            } else {
584                ScrollbarThumbState::Idle
585            }),
586            cx,
587        );
588    }
589
590    pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) {
591        self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx);
592    }
593
594    pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
595        self.update_minimap_thumb_state(None, cx);
596    }
597
598    pub fn is_dragging_minimap(&self) -> bool {
599        self.minimap_thumb_state
600            .is_some_and(|state| state == ScrollbarThumbState::Dragging)
601    }
602
603    fn update_minimap_thumb_state(
604        &mut self,
605        thumb_state: Option<ScrollbarThumbState>,
606        cx: &mut Context<Editor>,
607    ) {
608        if self.minimap_thumb_state != thumb_state {
609            self.minimap_thumb_state = thumb_state;
610            cx.notify();
611        }
612    }
613
614    pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> {
615        self.minimap_thumb_state
616    }
617
618    pub fn clamp_scroll_left(&mut self, max: f64, cx: &App) -> bool {
619        let current_x = self.anchor.read(cx).scroll_anchor.offset.x;
620        self.scroll_max_x = Some(max);
621        current_x > max
622    }
623
624    pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
625        self.forbid_vertical_scroll = forbid;
626    }
627
628    pub fn forbid_vertical_scroll(&self) -> bool {
629        self.forbid_vertical_scroll
630    }
631}
632
633impl Editor {
634    pub fn has_autoscroll_request(&self) -> bool {
635        self.scroll_manager.has_autoscroll_request()
636    }
637
638    pub fn vertical_scroll_margin(&self) -> usize {
639        self.scroll_manager.vertical_scroll_margin as usize
640    }
641
642    pub(crate) fn scroll_beyond_last_line(&self, cx: &App) -> ScrollBeyondLastLine {
643        match self.mode {
644            EditorMode::Minimap { .. }
645            | EditorMode::Full {
646                sizing_behavior: SizingBehavior::Default,
647                ..
648            } => EditorSettings::get_global(cx).scroll_beyond_last_line,
649
650            EditorMode::Full { .. } | EditorMode::SingleLine | EditorMode::AutoHeight { .. } => {
651                ScrollBeyondLastLine::Off
652            }
653        }
654    }
655
656    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
657        self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
658        cx.notify();
659    }
660
661    pub fn visible_line_count(&self) -> Option<f64> {
662        self.scroll_manager.visible_line_count
663    }
664
665    pub fn visible_row_count(&self) -> Option<u32> {
666        self.visible_line_count()
667            .map(|line_count| line_count as u32 - 1)
668    }
669
670    pub fn visible_column_count(&self) -> Option<f64> {
671        self.scroll_manager.visible_column_count
672    }
673
674    pub(crate) fn set_visible_line_count(
675        &mut self,
676        lines: f64,
677        window: &mut Window,
678        cx: &mut Context<Self>,
679    ) {
680        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
681        self.scroll_manager.visible_line_count = Some(lines);
682        if opened_first_time {
683            self.update_data_on_scroll(false, window, cx);
684        }
685    }
686
687    pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
688        self.scroll_manager.visible_column_count = Some(columns);
689    }
690
691    pub fn apply_scroll_delta(
692        &mut self,
693        scroll_delta: gpui::Point<f32>,
694        window: &mut Window,
695        cx: &mut Context<Self>,
696    ) {
697        let mut delta = scroll_delta;
698        if self.scroll_manager.forbid_vertical_scroll {
699            delta.y = 0.0;
700        }
701        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
702        let position = self.scroll_manager.scroll_position(&display_map, cx) + delta.map(f64::from);
703        self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
704    }
705
706    pub fn set_scroll_position(
707        &mut self,
708        scroll_position: gpui::Point<ScrollOffset>,
709        window: &mut Window,
710        cx: &mut Context<Self>,
711    ) -> WasScrolled {
712        let mut position = scroll_position;
713        if self.scroll_manager.forbid_vertical_scroll {
714            let current_position = self.scroll_position(cx);
715            position.y = current_position.y;
716        }
717        self.set_scroll_position_internal(position, true, false, window, cx)
718    }
719
720    /// Scrolls so that `row` is at the top of the editor view.
721    pub fn set_scroll_top_row(
722        &mut self,
723        row: DisplayRow,
724        window: &mut Window,
725        cx: &mut Context<Editor>,
726    ) {
727        let snapshot = self.snapshot(window, cx).display_snapshot;
728        let new_screen_top = DisplayPoint::new(row, 0);
729        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
730        let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
731
732        self.set_scroll_anchor(
733            ScrollAnchor {
734                anchor: new_anchor,
735                offset: Default::default(),
736            },
737            window,
738            cx,
739        );
740    }
741
742    pub(crate) fn set_scroll_position_internal(
743        &mut self,
744        scroll_position: gpui::Point<ScrollOffset>,
745        local: bool,
746        autoscroll: bool,
747        window: &mut Window,
748        cx: &mut Context<Self>,
749    ) -> WasScrolled {
750        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
751        let was_scrolled = self.set_scroll_position_taking_display_map(
752            scroll_position,
753            local,
754            autoscroll,
755            map,
756            window,
757            cx,
758        );
759
760        was_scrolled
761    }
762
763    fn set_scroll_position_taking_display_map(
764        &mut self,
765        scroll_position: gpui::Point<ScrollOffset>,
766        local: bool,
767        autoscroll: bool,
768        display_map: DisplaySnapshot,
769        window: &mut Window,
770        cx: &mut Context<Self>,
771    ) -> WasScrolled {
772        hide_hover(self, cx);
773        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
774
775        self.edit_prediction_preview
776            .set_previous_scroll_position(None);
777
778        let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
779            let current_position = self.scroll_manager.scroll_position(&display_map, cx);
780            gpui::Point::new(scroll_position.x, current_position.y)
781        } else {
782            scroll_position
783        };
784        let scroll_beyond_last_line = self.scroll_beyond_last_line(cx);
785        self.scroll_manager.set_scroll_position(
786            adjusted_position,
787            &display_map,
788            scroll_beyond_last_line,
789            local,
790            autoscroll,
791            workspace_id,
792            window,
793            cx,
794        )
795    }
796
797    pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
798        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
799        self.scroll_manager.scroll_position(&display_map, cx)
800    }
801
802    pub fn set_scroll_anchor(
803        &mut self,
804        scroll_anchor: ScrollAnchor,
805        window: &mut Window,
806        cx: &mut Context<Self>,
807    ) {
808        hide_hover(self, cx);
809        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
810        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
811        let top_row = scroll_anchor
812            .anchor
813            .to_point(&self.buffer().read(cx).snapshot(cx))
814            .row;
815        self.scroll_manager.set_anchor(
816            scroll_anchor,
817            &display_map,
818            top_row,
819            true,
820            false,
821            workspace_id,
822            window,
823            cx,
824        );
825    }
826
827    pub(crate) fn set_scroll_anchor_remote(
828        &mut self,
829        scroll_anchor: ScrollAnchor,
830        window: &mut Window,
831        cx: &mut Context<Self>,
832    ) {
833        hide_hover(self, cx);
834        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
835        let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
836        if !scroll_anchor.anchor.is_valid(&buffer_snapshot) {
837            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
838            return;
839        }
840        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
841        let top_row = scroll_anchor.anchor.to_point(&buffer_snapshot).row;
842        self.scroll_manager.set_anchor(
843            scroll_anchor,
844            &display_map,
845            top_row,
846            false,
847            false,
848            workspace_id,
849            window,
850            cx,
851        );
852    }
853
854    pub fn scroll_screen(
855        &mut self,
856        amount: &ScrollAmount,
857        window: &mut Window,
858        cx: &mut Context<Self>,
859    ) {
860        if matches!(self.mode, EditorMode::SingleLine) {
861            cx.propagate();
862            return;
863        }
864
865        if self.take_rename(true, window, cx).is_some() {
866            return;
867        }
868
869        let mut current_position = self.scroll_position(cx);
870        let Some(visible_line_count) = self.visible_line_count() else {
871            return;
872        };
873        let Some(mut visible_column_count) = self.visible_column_count() else {
874            return;
875        };
876
877        // If the user has a preferred line length, and has the editor
878        // configured to wrap at the preferred line length, or bounded to it,
879        // use that value over the visible column count. This was mostly done so
880        // that tests could actually be written for vim's `z l`, `z h`, `z
881        // shift-l` and `z shift-h` commands, as there wasn't a good way to
882        // configure the editor to only display a certain number of columns. If
883        // that ever happens, this could probably be removed.
884        let settings = AllLanguageSettings::get_global(cx);
885        if matches!(
886            settings.defaults.soft_wrap,
887            SoftWrap::PreferredLineLength | SoftWrap::Bounded
888        ) && (settings.defaults.preferred_line_length as f64) < visible_column_count
889        {
890            visible_column_count = settings.defaults.preferred_line_length as f64;
891        }
892
893        // If the scroll position is currently at the left edge of the document
894        // (x == 0.0) and the intent is to scroll right, the gutter's margin
895        // should first be added to the current position, otherwise the cursor
896        // will end at the column position minus the margin, which looks off.
897        if current_position.x == 0.0
898            && amount.columns(visible_column_count) > 0.
899            && let Some(last_position_map) = &self.last_position_map
900        {
901            current_position.x +=
902                f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
903        }
904        let new_position = current_position
905            + point(
906                amount.columns(visible_column_count),
907                amount.lines(visible_line_count),
908            );
909        self.set_scroll_position(new_position, window, cx);
910    }
911
912    /// Returns an ordering. The newest selection is:
913    ///     Ordering::Equal => on screen
914    ///     Ordering::Less => above or to the left of the screen
915    ///     Ordering::Greater => below or to the right of the screen
916    pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
917        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
918        let newest_head = self
919            .selections
920            .newest_anchor()
921            .head()
922            .to_display_point(&snapshot);
923        let screen_top = self.scroll_manager.scroll_top_display_point(&snapshot, cx);
924
925        if screen_top > newest_head {
926            return Ordering::Less;
927        }
928
929        if let (Some(visible_lines), Some(visible_columns)) =
930            (self.visible_line_count(), self.visible_column_count())
931            && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
932            && newest_head.column() <= screen_top.column() + visible_columns as u32
933        {
934            return Ordering::Equal;
935        }
936
937        Ordering::Greater
938    }
939
940    pub fn read_scroll_position_from_db(
941        &mut self,
942        item_id: u64,
943        workspace_id: WorkspaceId,
944        window: &mut Window,
945        cx: &mut Context<Editor>,
946    ) {
947        let scroll_position = EditorDb::global(cx).get_scroll_position(item_id, workspace_id);
948        if let Ok(Some((top_row, x, y))) = scroll_position {
949            let top_anchor = self
950                .buffer()
951                .read(cx)
952                .snapshot(cx)
953                .anchor_before(Point::new(top_row, 0));
954            let scroll_anchor = ScrollAnchor {
955                offset: gpui::Point::new(x, y),
956                anchor: top_anchor,
957            };
958            self.set_scroll_anchor(scroll_anchor, window, cx);
959        }
960    }
961}