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::{
 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            self._save_scroll_position_task = cx.background_executor().spawn(async move {
471                executor.timer(Duration::from_millis(10)).await;
472                log::debug!(
473                    "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}"
474                );
475                DB.save_scroll_position(
476                    item_id,
477                    workspace_id,
478                    top_row,
479                    anchor.offset.x,
480                    anchor.offset.y,
481                )
482                .await
483                .log_err();
484            });
485        }
486        cx.notify();
487
488        WasScrolled(true)
489    }
490
491    pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
492        if !self.show_scrollbars {
493            self.show_scrollbars = true;
494            cx.notify();
495        }
496
497        if cx.default_global::<ScrollbarAutoHide>().should_hide() {
498            self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
499                cx.background_executor()
500                    .timer(SCROLLBAR_SHOW_INTERVAL)
501                    .await;
502                editor
503                    .update(cx, |editor, cx| {
504                        editor.scroll_manager.show_scrollbars = false;
505                        cx.notify();
506                    })
507                    .log_err();
508            }));
509        } else {
510            self.hide_scrollbar_task = None;
511        }
512    }
513
514    pub fn scrollbars_visible(&self) -> bool {
515        self.show_scrollbars
516    }
517
518    pub fn has_autoscroll_request(&self) -> bool {
519        self.autoscroll_request.is_some()
520    }
521
522    pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
523        self.autoscroll_request.take()
524    }
525
526    pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
527        self.active_scrollbar.as_ref()
528    }
529
530    pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
531        self.active_scrollbar
532            .as_ref()
533            .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
534            .map(|scrollbar| scrollbar.axis)
535    }
536
537    pub fn any_scrollbar_dragged(&self) -> bool {
538        self.active_scrollbar
539            .as_ref()
540            .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
541    }
542
543    pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
544        self.update_active_scrollbar_state(
545            Some(ActiveScrollbarState::new(
546                axis,
547                ScrollbarThumbState::Hovered,
548            )),
549            cx,
550        );
551    }
552
553    pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
554        self.update_active_scrollbar_state(
555            Some(ActiveScrollbarState::new(
556                axis,
557                ScrollbarThumbState::Dragging,
558            )),
559            cx,
560        );
561    }
562
563    pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
564        self.update_active_scrollbar_state(None, cx);
565    }
566
567    fn update_active_scrollbar_state(
568        &mut self,
569        new_state: Option<ActiveScrollbarState>,
570        cx: &mut Context<Editor>,
571    ) {
572        if self.active_scrollbar != new_state {
573            self.active_scrollbar = new_state;
574            cx.notify();
575        }
576    }
577
578    pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) {
579        self.update_minimap_thumb_state(
580            Some(if hovered {
581                ScrollbarThumbState::Hovered
582            } else {
583                ScrollbarThumbState::Idle
584            }),
585            cx,
586        );
587    }
588
589    pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) {
590        self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx);
591    }
592
593    pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
594        self.update_minimap_thumb_state(None, cx);
595    }
596
597    pub fn is_dragging_minimap(&self) -> bool {
598        self.minimap_thumb_state
599            .is_some_and(|state| state == ScrollbarThumbState::Dragging)
600    }
601
602    fn update_minimap_thumb_state(
603        &mut self,
604        thumb_state: Option<ScrollbarThumbState>,
605        cx: &mut Context<Editor>,
606    ) {
607        if self.minimap_thumb_state != thumb_state {
608            self.minimap_thumb_state = thumb_state;
609            cx.notify();
610        }
611    }
612
613    pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> {
614        self.minimap_thumb_state
615    }
616
617    pub fn clamp_scroll_left(&mut self, max: f64, cx: &App) -> bool {
618        let current_x = self.anchor.read(cx).scroll_anchor.offset.x;
619        self.scroll_max_x = Some(max);
620        current_x > max
621    }
622
623    pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
624        self.forbid_vertical_scroll = forbid;
625    }
626
627    pub fn forbid_vertical_scroll(&self) -> bool {
628        self.forbid_vertical_scroll
629    }
630}
631
632impl Editor {
633    pub fn has_autoscroll_request(&self) -> bool {
634        self.scroll_manager.has_autoscroll_request()
635    }
636
637    pub fn vertical_scroll_margin(&self) -> usize {
638        self.scroll_manager.vertical_scroll_margin as usize
639    }
640
641    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
642        self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
643        cx.notify();
644    }
645
646    pub fn visible_line_count(&self) -> Option<f64> {
647        self.scroll_manager.visible_line_count
648    }
649
650    pub fn visible_row_count(&self) -> Option<u32> {
651        self.visible_line_count()
652            .map(|line_count| line_count as u32 - 1)
653    }
654
655    pub fn visible_column_count(&self) -> Option<f64> {
656        self.scroll_manager.visible_column_count
657    }
658
659    pub(crate) fn set_visible_line_count(
660        &mut self,
661        lines: f64,
662        window: &mut Window,
663        cx: &mut Context<Self>,
664    ) {
665        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
666        self.scroll_manager.visible_line_count = Some(lines);
667        if opened_first_time {
668            self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
669                editor
670                    .update_in(cx, |editor, window, cx| {
671                        editor.register_visible_buffers(cx);
672                        editor.colorize_brackets(false, cx);
673                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
674                        editor.update_lsp_data(None, window, cx);
675                    })
676                    .ok();
677            });
678        }
679    }
680
681    pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
682        self.scroll_manager.visible_column_count = Some(columns);
683    }
684
685    pub fn apply_scroll_delta(
686        &mut self,
687        scroll_delta: gpui::Point<f32>,
688        window: &mut Window,
689        cx: &mut Context<Self>,
690    ) {
691        let mut delta = scroll_delta;
692        if self.scroll_manager.forbid_vertical_scroll {
693            delta.y = 0.0;
694        }
695        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
696        let position = self.scroll_manager.scroll_position(&display_map, cx) + delta.map(f64::from);
697        self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
698    }
699
700    pub fn set_scroll_position(
701        &mut self,
702        scroll_position: gpui::Point<ScrollOffset>,
703        window: &mut Window,
704        cx: &mut Context<Self>,
705    ) -> WasScrolled {
706        let mut position = scroll_position;
707        if self.scroll_manager.forbid_vertical_scroll {
708            let current_position = self.scroll_position(cx);
709            position.y = current_position.y;
710        }
711        self.set_scroll_position_internal(position, true, false, window, cx)
712    }
713
714    /// Scrolls so that `row` is at the top of the editor view.
715    pub fn set_scroll_top_row(
716        &mut self,
717        row: DisplayRow,
718        window: &mut Window,
719        cx: &mut Context<Editor>,
720    ) {
721        let snapshot = self.snapshot(window, cx).display_snapshot;
722        let new_screen_top = DisplayPoint::new(row, 0);
723        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
724        let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
725
726        self.set_scroll_anchor(
727            ScrollAnchor {
728                anchor: new_anchor,
729                offset: Default::default(),
730            },
731            window,
732            cx,
733        );
734    }
735
736    pub(crate) fn set_scroll_position_internal(
737        &mut self,
738        scroll_position: gpui::Point<ScrollOffset>,
739        local: bool,
740        autoscroll: bool,
741        window: &mut Window,
742        cx: &mut Context<Self>,
743    ) -> WasScrolled {
744        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
745        let was_scrolled = self.set_scroll_position_taking_display_map(
746            scroll_position,
747            local,
748            autoscroll,
749            map,
750            window,
751            cx,
752        );
753
754        was_scrolled
755    }
756
757    fn set_scroll_position_taking_display_map(
758        &mut self,
759        scroll_position: gpui::Point<ScrollOffset>,
760        local: bool,
761        autoscroll: bool,
762        display_map: DisplaySnapshot,
763        window: &mut Window,
764        cx: &mut Context<Self>,
765    ) -> WasScrolled {
766        hide_hover(self, cx);
767        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
768
769        self.edit_prediction_preview
770            .set_previous_scroll_position(None);
771
772        let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
773            let current_position = self.scroll_manager.scroll_position(&display_map, cx);
774            gpui::Point::new(scroll_position.x, current_position.y)
775        } else {
776            scroll_position
777        };
778
779        self.scroll_manager.set_scroll_position(
780            adjusted_position,
781            &display_map,
782            local,
783            autoscroll,
784            workspace_id,
785            window,
786            cx,
787        )
788    }
789
790    pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
791        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
792        self.scroll_manager.scroll_position(&display_map, cx)
793    }
794
795    pub fn set_scroll_anchor(
796        &mut self,
797        scroll_anchor: ScrollAnchor,
798        window: &mut Window,
799        cx: &mut Context<Self>,
800    ) {
801        hide_hover(self, cx);
802        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
803        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
804        let top_row = scroll_anchor
805            .anchor
806            .to_point(&self.buffer().read(cx).snapshot(cx))
807            .row;
808        self.scroll_manager.set_anchor(
809            scroll_anchor,
810            &display_map,
811            top_row,
812            true,
813            false,
814            workspace_id,
815            window,
816            cx,
817        );
818    }
819
820    pub(crate) fn set_scroll_anchor_remote(
821        &mut self,
822        scroll_anchor: ScrollAnchor,
823        window: &mut Window,
824        cx: &mut Context<Self>,
825    ) {
826        hide_hover(self, cx);
827        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
828        let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
829        if !scroll_anchor.anchor.is_valid(&buffer_snapshot) {
830            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
831            return;
832        }
833        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
834        let top_row = scroll_anchor.anchor.to_point(&buffer_snapshot).row;
835        self.scroll_manager.set_anchor(
836            scroll_anchor,
837            &display_map,
838            top_row,
839            false,
840            false,
841            workspace_id,
842            window,
843            cx,
844        );
845    }
846
847    pub fn scroll_screen(
848        &mut self,
849        amount: &ScrollAmount,
850        window: &mut Window,
851        cx: &mut Context<Self>,
852    ) {
853        if matches!(self.mode, EditorMode::SingleLine) {
854            cx.propagate();
855            return;
856        }
857
858        if self.take_rename(true, window, cx).is_some() {
859            return;
860        }
861
862        let mut current_position = self.scroll_position(cx);
863        let Some(visible_line_count) = self.visible_line_count() else {
864            return;
865        };
866        let Some(mut visible_column_count) = self.visible_column_count() else {
867            return;
868        };
869
870        // If the user has a preferred line length, and has the editor
871        // configured to wrap at the preferred line length, or bounded to it,
872        // use that value over the visible column count. This was mostly done so
873        // that tests could actually be written for vim's `z l`, `z h`, `z
874        // shift-l` and `z shift-h` commands, as there wasn't a good way to
875        // configure the editor to only display a certain number of columns. If
876        // that ever happens, this could probably be removed.
877        let settings = AllLanguageSettings::get_global(cx);
878        if matches!(
879            settings.defaults.soft_wrap,
880            SoftWrap::PreferredLineLength | SoftWrap::Bounded
881        ) && (settings.defaults.preferred_line_length as f64) < visible_column_count
882        {
883            visible_column_count = settings.defaults.preferred_line_length as f64;
884        }
885
886        // If the scroll position is currently at the left edge of the document
887        // (x == 0.0) and the intent is to scroll right, the gutter's margin
888        // should first be added to the current position, otherwise the cursor
889        // will end at the column position minus the margin, which looks off.
890        if current_position.x == 0.0
891            && amount.columns(visible_column_count) > 0.
892            && let Some(last_position_map) = &self.last_position_map
893        {
894            current_position.x +=
895                f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
896        }
897        let new_position = current_position
898            + point(
899                amount.columns(visible_column_count),
900                amount.lines(visible_line_count),
901            );
902        self.set_scroll_position(new_position, window, cx);
903    }
904
905    /// Returns an ordering. The newest selection is:
906    ///     Ordering::Equal => on screen
907    ///     Ordering::Less => above or to the left of the screen
908    ///     Ordering::Greater => below or to the right of the screen
909    pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
910        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
911        let newest_head = self
912            .selections
913            .newest_anchor()
914            .head()
915            .to_display_point(&snapshot);
916        let screen_top = self.scroll_manager.scroll_top_display_point(&snapshot, cx);
917
918        if screen_top > newest_head {
919            return Ordering::Less;
920        }
921
922        if let (Some(visible_lines), Some(visible_columns)) =
923            (self.visible_line_count(), self.visible_column_count())
924            && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
925            && newest_head.column() <= screen_top.column() + visible_columns as u32
926        {
927            return Ordering::Equal;
928        }
929
930        Ordering::Greater
931    }
932
933    pub fn read_scroll_position_from_db(
934        &mut self,
935        item_id: u64,
936        workspace_id: WorkspaceId,
937        window: &mut Window,
938        cx: &mut Context<Editor>,
939    ) {
940        let scroll_position = DB.get_scroll_position(item_id, workspace_id);
941        if let Ok(Some((top_row, x, y))) = scroll_position {
942            let top_anchor = self
943                .buffer()
944                .read(cx)
945                .snapshot(cx)
946                .anchor_before(Point::new(top_row, 0));
947            let scroll_anchor = ScrollAnchor {
948                offset: gpui::Point::new(x, y),
949                anchor: top_anchor,
950            };
951            self.set_scroll_anchor(scroll_anchor, window, cx);
952        }
953    }
954}