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::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        local: bool,
376        autoscroll: bool,
377        workspace_id: Option<WorkspaceId>,
378        window: &mut Window,
379        cx: &mut Context<Editor>,
380    ) -> WasScrolled {
381        let scroll_top = scroll_position.y.max(0.);
382        let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
383            ScrollBeyondLastLine::OnePage => scroll_top,
384            ScrollBeyondLastLine::Off => {
385                if let Some(height_in_lines) = self.visible_line_count {
386                    let max_row = map.max_point().row().as_f64();
387                    scroll_top.min(max_row - height_in_lines + 1.).max(0.)
388                } else {
389                    scroll_top
390                }
391            }
392            ScrollBeyondLastLine::VerticalScrollMargin => {
393                if let Some(height_in_lines) = self.visible_line_count {
394                    let max_row = map.max_point().row().as_f64();
395                    scroll_top
396                        .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
397                        .max(0.)
398                } else {
399                    scroll_top
400                }
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 fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
643        self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
644        cx.notify();
645    }
646
647    pub fn visible_line_count(&self) -> Option<f64> {
648        self.scroll_manager.visible_line_count
649    }
650
651    pub fn visible_row_count(&self) -> Option<u32> {
652        self.visible_line_count()
653            .map(|line_count| line_count as u32 - 1)
654    }
655
656    pub fn visible_column_count(&self) -> Option<f64> {
657        self.scroll_manager.visible_column_count
658    }
659
660    pub(crate) fn set_visible_line_count(
661        &mut self,
662        lines: f64,
663        window: &mut Window,
664        cx: &mut Context<Self>,
665    ) {
666        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
667        self.scroll_manager.visible_line_count = Some(lines);
668        if opened_first_time {
669            self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
670                editor
671                    .update_in(cx, |editor, window, cx| {
672                        editor.register_visible_buffers(cx);
673                        editor.colorize_brackets(false, cx);
674                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
675                        editor.update_lsp_data(None, window, cx);
676                    })
677                    .ok();
678            });
679        }
680    }
681
682    pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
683        self.scroll_manager.visible_column_count = Some(columns);
684    }
685
686    pub fn apply_scroll_delta(
687        &mut self,
688        scroll_delta: gpui::Point<f32>,
689        window: &mut Window,
690        cx: &mut Context<Self>,
691    ) {
692        let mut delta = scroll_delta;
693        if self.scroll_manager.forbid_vertical_scroll {
694            delta.y = 0.0;
695        }
696        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
697        let position = self.scroll_manager.scroll_position(&display_map, cx) + delta.map(f64::from);
698        self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
699    }
700
701    pub fn set_scroll_position(
702        &mut self,
703        scroll_position: gpui::Point<ScrollOffset>,
704        window: &mut Window,
705        cx: &mut Context<Self>,
706    ) -> WasScrolled {
707        let mut position = scroll_position;
708        if self.scroll_manager.forbid_vertical_scroll {
709            let current_position = self.scroll_position(cx);
710            position.y = current_position.y;
711        }
712        self.set_scroll_position_internal(position, true, false, window, cx)
713    }
714
715    /// Scrolls so that `row` is at the top of the editor view.
716    pub fn set_scroll_top_row(
717        &mut self,
718        row: DisplayRow,
719        window: &mut Window,
720        cx: &mut Context<Editor>,
721    ) {
722        let snapshot = self.snapshot(window, cx).display_snapshot;
723        let new_screen_top = DisplayPoint::new(row, 0);
724        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
725        let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
726
727        self.set_scroll_anchor(
728            ScrollAnchor {
729                anchor: new_anchor,
730                offset: Default::default(),
731            },
732            window,
733            cx,
734        );
735    }
736
737    pub(crate) fn set_scroll_position_internal(
738        &mut self,
739        scroll_position: gpui::Point<ScrollOffset>,
740        local: bool,
741        autoscroll: bool,
742        window: &mut Window,
743        cx: &mut Context<Self>,
744    ) -> WasScrolled {
745        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
746        let was_scrolled = self.set_scroll_position_taking_display_map(
747            scroll_position,
748            local,
749            autoscroll,
750            map,
751            window,
752            cx,
753        );
754
755        was_scrolled
756    }
757
758    fn set_scroll_position_taking_display_map(
759        &mut self,
760        scroll_position: gpui::Point<ScrollOffset>,
761        local: bool,
762        autoscroll: bool,
763        display_map: DisplaySnapshot,
764        window: &mut Window,
765        cx: &mut Context<Self>,
766    ) -> WasScrolled {
767        hide_hover(self, cx);
768        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
769
770        self.edit_prediction_preview
771            .set_previous_scroll_position(None);
772
773        let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
774            let current_position = self.scroll_manager.scroll_position(&display_map, cx);
775            gpui::Point::new(scroll_position.x, current_position.y)
776        } else {
777            scroll_position
778        };
779
780        self.scroll_manager.set_scroll_position(
781            adjusted_position,
782            &display_map,
783            local,
784            autoscroll,
785            workspace_id,
786            window,
787            cx,
788        )
789    }
790
791    pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
792        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
793        self.scroll_manager.scroll_position(&display_map, cx)
794    }
795
796    pub fn set_scroll_anchor(
797        &mut self,
798        scroll_anchor: ScrollAnchor,
799        window: &mut Window,
800        cx: &mut Context<Self>,
801    ) {
802        hide_hover(self, cx);
803        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
804        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
805        let top_row = scroll_anchor
806            .anchor
807            .to_point(&self.buffer().read(cx).snapshot(cx))
808            .row;
809        self.scroll_manager.set_anchor(
810            scroll_anchor,
811            &display_map,
812            top_row,
813            true,
814            false,
815            workspace_id,
816            window,
817            cx,
818        );
819    }
820
821    pub(crate) fn set_scroll_anchor_remote(
822        &mut self,
823        scroll_anchor: ScrollAnchor,
824        window: &mut Window,
825        cx: &mut Context<Self>,
826    ) {
827        hide_hover(self, cx);
828        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
829        let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
830        if !scroll_anchor.anchor.is_valid(&buffer_snapshot) {
831            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
832            return;
833        }
834        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
835        let top_row = scroll_anchor.anchor.to_point(&buffer_snapshot).row;
836        self.scroll_manager.set_anchor(
837            scroll_anchor,
838            &display_map,
839            top_row,
840            false,
841            false,
842            workspace_id,
843            window,
844            cx,
845        );
846    }
847
848    pub fn scroll_screen(
849        &mut self,
850        amount: &ScrollAmount,
851        window: &mut Window,
852        cx: &mut Context<Self>,
853    ) {
854        if matches!(self.mode, EditorMode::SingleLine) {
855            cx.propagate();
856            return;
857        }
858
859        if self.take_rename(true, window, cx).is_some() {
860            return;
861        }
862
863        let mut current_position = self.scroll_position(cx);
864        let Some(visible_line_count) = self.visible_line_count() else {
865            return;
866        };
867        let Some(mut visible_column_count) = self.visible_column_count() else {
868            return;
869        };
870
871        // If the user has a preferred line length, and has the editor
872        // configured to wrap at the preferred line length, or bounded to it,
873        // use that value over the visible column count. This was mostly done so
874        // that tests could actually be written for vim's `z l`, `z h`, `z
875        // shift-l` and `z shift-h` commands, as there wasn't a good way to
876        // configure the editor to only display a certain number of columns. If
877        // that ever happens, this could probably be removed.
878        let settings = AllLanguageSettings::get_global(cx);
879        if matches!(
880            settings.defaults.soft_wrap,
881            SoftWrap::PreferredLineLength | SoftWrap::Bounded
882        ) && (settings.defaults.preferred_line_length as f64) < visible_column_count
883        {
884            visible_column_count = settings.defaults.preferred_line_length as f64;
885        }
886
887        // If the scroll position is currently at the left edge of the document
888        // (x == 0.0) and the intent is to scroll right, the gutter's margin
889        // should first be added to the current position, otherwise the cursor
890        // will end at the column position minus the margin, which looks off.
891        if current_position.x == 0.0
892            && amount.columns(visible_column_count) > 0.
893            && let Some(last_position_map) = &self.last_position_map
894        {
895            current_position.x +=
896                f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
897        }
898        let new_position = current_position
899            + point(
900                amount.columns(visible_column_count),
901                amount.lines(visible_line_count),
902            );
903        self.set_scroll_position(new_position, window, cx);
904    }
905
906    /// Returns an ordering. The newest selection is:
907    ///     Ordering::Equal => on screen
908    ///     Ordering::Less => above or to the left of the screen
909    ///     Ordering::Greater => below or to the right of the screen
910    pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
911        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
912        let newest_head = self
913            .selections
914            .newest_anchor()
915            .head()
916            .to_display_point(&snapshot);
917        let screen_top = self.scroll_manager.scroll_top_display_point(&snapshot, cx);
918
919        if screen_top > newest_head {
920            return Ordering::Less;
921        }
922
923        if let (Some(visible_lines), Some(visible_columns)) =
924            (self.visible_line_count(), self.visible_column_count())
925            && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
926            && newest_head.column() <= screen_top.column() + visible_columns as u32
927        {
928            return Ordering::Equal;
929        }
930
931        Ordering::Greater
932    }
933
934    pub fn read_scroll_position_from_db(
935        &mut self,
936        item_id: u64,
937        workspace_id: WorkspaceId,
938        window: &mut Window,
939        cx: &mut Context<Editor>,
940    ) {
941        let scroll_position = EditorDb::global(cx).get_scroll_position(item_id, workspace_id);
942        if let Ok(Some((top_row, x, y))) = scroll_position {
943            let top_anchor = self
944                .buffer()
945                .read(cx)
946                .snapshot(cx)
947                .anchor_before(Point::new(top_row, 0));
948            let scroll_anchor = ScrollAnchor {
949                offset: gpui::Point::new(x, y),
950                anchor: top_anchor,
951            };
952            self.set_scroll_anchor(scroll_anchor, window, cx);
953        }
954    }
955}