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    /// The second element indicates whether the autoscroll request is local
205    /// (true) or remote (false). Local requests are initiated by user actions,
206    /// while remote requests come from external sources.
207    autoscroll_request: Option<(Autoscroll, bool)>,
208    last_autoscroll: Option<(
209        gpui::Point<ScrollOffset>,
210        ScrollOffset,
211        ScrollOffset,
212        AutoscrollStrategy,
213    )>,
214    show_scrollbars: bool,
215    hide_scrollbar_task: Option<Task<()>>,
216    active_scrollbar: Option<ActiveScrollbarState>,
217    visible_line_count: Option<f64>,
218    visible_column_count: Option<f64>,
219    forbid_vertical_scroll: bool,
220    minimap_thumb_state: Option<ScrollbarThumbState>,
221    _save_scroll_position_task: Task<()>,
222}
223
224impl ScrollManager {
225    pub fn new(cx: &mut Context<Editor>) -> Self {
226        let anchor = cx.new(|_| SharedScrollAnchor {
227            scroll_anchor: ScrollAnchor::new(),
228            display_map_id: None,
229        });
230        ScrollManager {
231            vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin,
232            anchor,
233            scroll_max_x: None,
234            ongoing: OngoingScroll::new(),
235            autoscroll_request: None,
236            show_scrollbars: true,
237            hide_scrollbar_task: None,
238            active_scrollbar: None,
239            last_autoscroll: None,
240            visible_line_count: None,
241            visible_column_count: None,
242            forbid_vertical_scroll: false,
243            minimap_thumb_state: None,
244            _save_scroll_position_task: Task::ready(()),
245        }
246    }
247
248    pub fn set_native_display_map_id(
249        &mut self,
250        display_map_id: EntityId,
251        cx: &mut Context<Editor>,
252    ) {
253        self.anchor.update(cx, |shared, _| {
254            if shared.display_map_id.is_none() {
255                shared.display_map_id = Some(display_map_id);
256            }
257        });
258    }
259
260    pub fn clone_state(
261        &mut self,
262        other: &Self,
263        other_snapshot: &DisplaySnapshot,
264        my_snapshot: &DisplaySnapshot,
265        cx: &mut Context<Editor>,
266    ) {
267        let native_anchor = other.native_anchor(other_snapshot, cx);
268        self.anchor.update(cx, |this, _| {
269            this.scroll_anchor = native_anchor;
270            this.display_map_id = Some(my_snapshot.display_map_id);
271        });
272        self.ongoing = other.ongoing;
273    }
274
275    pub fn offset(&self, cx: &App) -> gpui::Point<f64> {
276        let mut offset = self.anchor.read(cx).scroll_anchor.offset;
277        if let Some(max_x) = self.scroll_max_x {
278            offset.x = offset.x.min(max_x);
279        }
280        offset
281    }
282
283    /// Get a ScrollAnchor whose `anchor` field is guaranteed to point into the multibuffer for the provided snapshot.
284    ///
285    /// For normal editors, this just retrieves the internal ScrollAnchor and is lossless. When the editor is part of a split diff,
286    /// we may need to translate the anchor to point to the "native" multibuffer first. That translation is lossy,
287    /// so this method should be used sparingly---if you just need a scroll position or display point, call the appropriate helper method instead,
288    /// since they can losslessly handle the case where the ScrollAnchor was last set from the other side.
289    pub fn native_anchor(&self, snapshot: &DisplaySnapshot, cx: &App) -> ScrollAnchor {
290        let shared = self.anchor.read(cx);
291
292        let mut result = if let Some(display_map_id) = shared.display_map_id
293            && display_map_id != snapshot.display_map_id
294        {
295            let companion_snapshot = snapshot.companion_snapshot().unwrap();
296            assert_eq!(companion_snapshot.display_map_id, display_map_id);
297            let mut display_point = shared
298                .scroll_anchor
299                .anchor
300                .to_display_point(companion_snapshot);
301            *display_point.column_mut() = 0;
302            let buffer_point = snapshot.display_point_to_point(display_point, Bias::Left);
303            let anchor = snapshot.buffer_snapshot().anchor_before(buffer_point);
304            ScrollAnchor {
305                anchor,
306                offset: shared.scroll_anchor.offset,
307            }
308        } else {
309            shared.scroll_anchor
310        };
311
312        if let Some(max_x) = self.scroll_max_x {
313            result.offset.x = result.offset.x.min(max_x);
314        }
315        result
316    }
317
318    pub fn shared_scroll_anchor(&self, cx: &App) -> SharedScrollAnchor {
319        let mut shared = *self.anchor.read(cx);
320        if let Some(max_x) = self.scroll_max_x {
321            shared.scroll_anchor.offset.x = shared.scroll_anchor.offset.x.min(max_x);
322        }
323        shared
324    }
325
326    pub fn scroll_top_display_point(&self, snapshot: &DisplaySnapshot, cx: &App) -> DisplayPoint {
327        self.anchor.read(cx).scroll_top_display_point(snapshot)
328    }
329
330    pub fn scroll_anchor_entity(&self) -> Entity<SharedScrollAnchor> {
331        self.anchor.clone()
332    }
333
334    pub fn set_shared_scroll_anchor(&mut self, entity: Entity<SharedScrollAnchor>) {
335        self.anchor = entity;
336    }
337
338    pub fn ongoing_scroll(&self) -> OngoingScroll {
339        self.ongoing
340    }
341
342    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
343        self.ongoing.last_event = Instant::now();
344        self.ongoing.axis = axis;
345    }
346
347    pub fn scroll_position(
348        &self,
349        snapshot: &DisplaySnapshot,
350        cx: &App,
351    ) -> gpui::Point<ScrollOffset> {
352        let mut pos = self.anchor.read(cx).scroll_position(snapshot);
353        if let Some(max_x) = self.scroll_max_x {
354            pos.x = pos.x.min(max_x);
355        }
356        pos
357    }
358
359    fn set_scroll_position(
360        &mut self,
361        scroll_position: gpui::Point<ScrollOffset>,
362        map: &DisplaySnapshot,
363        scroll_beyond_last_line: ScrollBeyondLastLine,
364        local: bool,
365        autoscroll: bool,
366        workspace_id: Option<WorkspaceId>,
367        window: &mut Window,
368        cx: &mut Context<Editor>,
369    ) -> WasScrolled {
370        let scroll_top = scroll_position.y.max(0.);
371        let scroll_top = match scroll_beyond_last_line {
372            ScrollBeyondLastLine::OnePage => scroll_top,
373            ScrollBeyondLastLine::Off => {
374                if let Some(height_in_lines) = self.visible_line_count {
375                    let max_row = map.max_point().row().as_f64();
376                    scroll_top.min(max_row - height_in_lines + 1.).max(0.)
377                } else {
378                    scroll_top
379                }
380            }
381            ScrollBeyondLastLine::VerticalScrollMargin => {
382                if let Some(height_in_lines) = self.visible_line_count {
383                    let max_row = map.max_point().row().as_f64();
384                    scroll_top
385                        .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
386                        .max(0.)
387                } else {
388                    scroll_top
389                }
390            }
391        };
392        let scroll_top_row = DisplayRow(scroll_top as u32);
393        let scroll_top_buffer_point = map
394            .clip_point(
395                DisplayPoint::new(scroll_top_row, scroll_position.x as u32),
396                Bias::Left,
397            )
398            .to_point(map);
399        let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point);
400
401        self.set_anchor(
402            ScrollAnchor {
403                anchor: top_anchor,
404                offset: point(
405                    scroll_position.x.max(0.),
406                    scroll_top - top_anchor.to_display_point(map).row().as_f64(),
407                ),
408            },
409            map,
410            scroll_top_buffer_point.row,
411            local,
412            autoscroll,
413            workspace_id,
414            window,
415            cx,
416        )
417    }
418
419    fn set_anchor(
420        &mut self,
421        anchor: ScrollAnchor,
422        display_map: &DisplaySnapshot,
423        top_row: u32,
424        local: bool,
425        autoscroll: bool,
426        workspace_id: Option<WorkspaceId>,
427        window: &mut Window,
428        cx: &mut Context<Editor>,
429    ) -> WasScrolled {
430        let adjusted_anchor = if self.forbid_vertical_scroll {
431            let current = self.anchor.read(cx);
432            ScrollAnchor {
433                offset: gpui::Point::new(anchor.offset.x, current.scroll_anchor.offset.y),
434                anchor: current.scroll_anchor.anchor,
435            }
436        } else {
437            anchor
438        };
439
440        self.scroll_max_x.take();
441        self.autoscroll_request.take();
442
443        let current = self.anchor.read(cx);
444        if current.scroll_anchor == adjusted_anchor {
445            return WasScrolled(false);
446        }
447
448        self.anchor.update(cx, |shared, _| {
449            shared.scroll_anchor = adjusted_anchor;
450            shared.display_map_id = Some(display_map.display_map_id);
451        });
452        cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
453        self.show_scrollbars(window, cx);
454        if let Some(workspace_id) = workspace_id {
455            let item_id = cx.entity().entity_id().as_u64() as ItemId;
456            let executor = cx.background_executor().clone();
457
458            let db = EditorDb::global(cx);
459            self._save_scroll_position_task = cx.background_executor().spawn(async move {
460                executor.timer(Duration::from_millis(10)).await;
461                log::debug!(
462                    "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}"
463                );
464                db.save_scroll_position(
465                    item_id,
466                    workspace_id,
467                    top_row,
468                    anchor.offset.x,
469                    anchor.offset.y,
470                )
471                .await
472                .log_err();
473            });
474        }
475        cx.notify();
476
477        WasScrolled(true)
478    }
479
480    pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
481        if !self.show_scrollbars {
482            self.show_scrollbars = true;
483            cx.notify();
484        }
485
486        if cx.default_global::<ScrollbarAutoHide>().should_hide() {
487            self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
488                cx.background_executor()
489                    .timer(SCROLLBAR_SHOW_INTERVAL)
490                    .await;
491                editor
492                    .update(cx, |editor, cx| {
493                        editor.scroll_manager.show_scrollbars = false;
494                        cx.notify();
495                    })
496                    .log_err();
497            }));
498        } else {
499            self.hide_scrollbar_task = None;
500        }
501    }
502
503    pub fn scrollbars_visible(&self) -> bool {
504        self.show_scrollbars
505    }
506
507    pub fn has_autoscroll_request(&self) -> bool {
508        self.autoscroll_request.is_some()
509    }
510
511    pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
512        self.autoscroll_request.take()
513    }
514
515    pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
516        self.active_scrollbar.as_ref()
517    }
518
519    pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
520        self.active_scrollbar
521            .as_ref()
522            .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
523            .map(|scrollbar| scrollbar.axis)
524    }
525
526    pub fn any_scrollbar_dragged(&self) -> bool {
527        self.active_scrollbar
528            .as_ref()
529            .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
530    }
531
532    pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
533        self.update_active_scrollbar_state(
534            Some(ActiveScrollbarState::new(
535                axis,
536                ScrollbarThumbState::Hovered,
537            )),
538            cx,
539        );
540    }
541
542    pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
543        self.update_active_scrollbar_state(
544            Some(ActiveScrollbarState::new(
545                axis,
546                ScrollbarThumbState::Dragging,
547            )),
548            cx,
549        );
550    }
551
552    pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
553        self.update_active_scrollbar_state(None, cx);
554    }
555
556    fn update_active_scrollbar_state(
557        &mut self,
558        new_state: Option<ActiveScrollbarState>,
559        cx: &mut Context<Editor>,
560    ) {
561        if self.active_scrollbar != new_state {
562            self.active_scrollbar = new_state;
563            cx.notify();
564        }
565    }
566
567    pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) {
568        self.update_minimap_thumb_state(
569            Some(if hovered {
570                ScrollbarThumbState::Hovered
571            } else {
572                ScrollbarThumbState::Idle
573            }),
574            cx,
575        );
576    }
577
578    pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) {
579        self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx);
580    }
581
582    pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
583        self.update_minimap_thumb_state(None, cx);
584    }
585
586    pub fn is_dragging_minimap(&self) -> bool {
587        self.minimap_thumb_state
588            .is_some_and(|state| state == ScrollbarThumbState::Dragging)
589    }
590
591    fn update_minimap_thumb_state(
592        &mut self,
593        thumb_state: Option<ScrollbarThumbState>,
594        cx: &mut Context<Editor>,
595    ) {
596        if self.minimap_thumb_state != thumb_state {
597            self.minimap_thumb_state = thumb_state;
598            cx.notify();
599        }
600    }
601
602    pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> {
603        self.minimap_thumb_state
604    }
605
606    pub fn clamp_scroll_left(&mut self, max: f64, cx: &App) -> bool {
607        let current_x = self.anchor.read(cx).scroll_anchor.offset.x;
608        self.scroll_max_x = Some(max);
609        current_x > max
610    }
611
612    pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
613        self.forbid_vertical_scroll = forbid;
614    }
615
616    pub fn forbid_vertical_scroll(&self) -> bool {
617        self.forbid_vertical_scroll
618    }
619}
620
621impl Editor {
622    pub fn has_autoscroll_request(&self) -> bool {
623        self.scroll_manager.has_autoscroll_request()
624    }
625
626    pub fn vertical_scroll_margin(&self) -> usize {
627        self.scroll_manager.vertical_scroll_margin as usize
628    }
629
630    pub(crate) fn scroll_beyond_last_line(&self, cx: &App) -> ScrollBeyondLastLine {
631        match self.mode {
632            EditorMode::Minimap { .. }
633            | EditorMode::Full {
634                sizing_behavior: SizingBehavior::Default,
635                ..
636            } => EditorSettings::get_global(cx).scroll_beyond_last_line,
637
638            EditorMode::Full { .. } | EditorMode::SingleLine | EditorMode::AutoHeight { .. } => {
639                ScrollBeyondLastLine::Off
640            }
641        }
642    }
643
644    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
645        self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
646        cx.notify();
647    }
648
649    pub fn visible_line_count(&self) -> Option<f64> {
650        self.scroll_manager.visible_line_count
651    }
652
653    pub fn visible_row_count(&self) -> Option<u32> {
654        self.visible_line_count()
655            .map(|line_count| line_count as u32 - 1)
656    }
657
658    pub fn visible_column_count(&self) -> Option<f64> {
659        self.scroll_manager.visible_column_count
660    }
661
662    pub(crate) fn set_visible_line_count(
663        &mut self,
664        lines: f64,
665        window: &mut Window,
666        cx: &mut Context<Self>,
667    ) {
668        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
669        self.scroll_manager.visible_line_count = Some(lines);
670        if opened_first_time {
671            self.update_data_on_scroll(false, window, cx);
672        }
673    }
674
675    pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
676        self.scroll_manager.visible_column_count = Some(columns);
677    }
678
679    pub fn apply_scroll_delta(
680        &mut self,
681        scroll_delta: gpui::Point<f32>,
682        window: &mut Window,
683        cx: &mut Context<Self>,
684    ) {
685        let mut delta = scroll_delta;
686        if self.scroll_manager.forbid_vertical_scroll {
687            delta.y = 0.0;
688        }
689        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
690        let position = self.scroll_manager.scroll_position(&display_map, cx) + delta.map(f64::from);
691        self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
692    }
693
694    pub fn set_scroll_position(
695        &mut self,
696        scroll_position: gpui::Point<ScrollOffset>,
697        window: &mut Window,
698        cx: &mut Context<Self>,
699    ) -> WasScrolled {
700        let mut position = scroll_position;
701        if self.scroll_manager.forbid_vertical_scroll {
702            let current_position = self.scroll_position(cx);
703            position.y = current_position.y;
704        }
705        self.set_scroll_position_internal(position, true, false, window, cx)
706    }
707
708    /// Scrolls so that `row` is at the top of the editor view.
709    pub fn set_scroll_top_row(
710        &mut self,
711        row: DisplayRow,
712        window: &mut Window,
713        cx: &mut Context<Editor>,
714    ) {
715        let snapshot = self.snapshot(window, cx).display_snapshot;
716        let new_screen_top = DisplayPoint::new(row, 0);
717        let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
718        let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
719
720        self.set_scroll_anchor(
721            ScrollAnchor {
722                anchor: new_anchor,
723                offset: Default::default(),
724            },
725            window,
726            cx,
727        );
728    }
729
730    pub(crate) fn set_scroll_position_internal(
731        &mut self,
732        scroll_position: gpui::Point<ScrollOffset>,
733        local: bool,
734        autoscroll: bool,
735        window: &mut Window,
736        cx: &mut Context<Self>,
737    ) -> WasScrolled {
738        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
739        let was_scrolled = self.set_scroll_position_taking_display_map(
740            scroll_position,
741            local,
742            autoscroll,
743            map,
744            window,
745            cx,
746        );
747
748        was_scrolled
749    }
750
751    fn set_scroll_position_taking_display_map(
752        &mut self,
753        scroll_position: gpui::Point<ScrollOffset>,
754        local: bool,
755        autoscroll: bool,
756        display_map: DisplaySnapshot,
757        window: &mut Window,
758        cx: &mut Context<Self>,
759    ) -> WasScrolled {
760        hide_hover(self, cx);
761        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
762
763        self.edit_prediction_preview
764            .set_previous_scroll_position(None);
765
766        let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
767            let current_position = self.scroll_manager.scroll_position(&display_map, cx);
768            gpui::Point::new(scroll_position.x, current_position.y)
769        } else {
770            scroll_position
771        };
772        let scroll_beyond_last_line = self.scroll_beyond_last_line(cx);
773        self.scroll_manager.set_scroll_position(
774            adjusted_position,
775            &display_map,
776            scroll_beyond_last_line,
777            local,
778            autoscroll,
779            workspace_id,
780            window,
781            cx,
782        )
783    }
784
785    pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
786        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
787        self.scroll_manager.scroll_position(&display_map, cx)
788    }
789
790    pub fn set_scroll_anchor(
791        &mut self,
792        scroll_anchor: ScrollAnchor,
793        window: &mut Window,
794        cx: &mut Context<Self>,
795    ) {
796        hide_hover(self, cx);
797        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
798        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
799        let top_row = scroll_anchor
800            .anchor
801            .to_point(&self.buffer().read(cx).snapshot(cx))
802            .row;
803        self.scroll_manager.set_anchor(
804            scroll_anchor,
805            &display_map,
806            top_row,
807            true,
808            false,
809            workspace_id,
810            window,
811            cx,
812        );
813    }
814
815    pub(crate) fn set_scroll_anchor_remote(
816        &mut self,
817        scroll_anchor: ScrollAnchor,
818        window: &mut Window,
819        cx: &mut Context<Self>,
820    ) {
821        hide_hover(self, cx);
822        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
823        let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
824        if !scroll_anchor.anchor.is_valid(&buffer_snapshot) {
825            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
826            return;
827        }
828        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
829        let top_row = scroll_anchor.anchor.to_point(&buffer_snapshot).row;
830        self.scroll_manager.set_anchor(
831            scroll_anchor,
832            &display_map,
833            top_row,
834            false,
835            false,
836            workspace_id,
837            window,
838            cx,
839        );
840    }
841
842    pub fn scroll_screen(
843        &mut self,
844        amount: &ScrollAmount,
845        window: &mut Window,
846        cx: &mut Context<Self>,
847    ) {
848        if matches!(self.mode, EditorMode::SingleLine) {
849            cx.propagate();
850            return;
851        }
852
853        if self.take_rename(true, window, cx).is_some() {
854            return;
855        }
856
857        let mut current_position = self.scroll_position(cx);
858        let Some(visible_line_count) = self.visible_line_count() else {
859            return;
860        };
861        let Some(mut visible_column_count) = self.visible_column_count() else {
862            return;
863        };
864
865        // If the user has a preferred line length, and has the editor
866        // configured to wrap at the preferred line length, or bounded to it,
867        // use that value over the visible column count. This was mostly done so
868        // that tests could actually be written for vim's `z l`, `z h`, `z
869        // shift-l` and `z shift-h` commands, as there wasn't a good way to
870        // configure the editor to only display a certain number of columns. If
871        // that ever happens, this could probably be removed.
872        let settings = AllLanguageSettings::get_global(cx);
873        if matches!(settings.defaults.soft_wrap, SoftWrap::Bounded)
874            && (settings.defaults.preferred_line_length as f64) < visible_column_count
875        {
876            visible_column_count = settings.defaults.preferred_line_length as f64;
877        }
878
879        // If the scroll position is currently at the left edge of the document
880        // (x == 0.0) and the intent is to scroll right, the gutter's margin
881        // should first be added to the current position, otherwise the cursor
882        // will end at the column position minus the margin, which looks off.
883        if current_position.x == 0.0
884            && amount.columns(visible_column_count) > 0.
885            && let Some(last_position_map) = &self.last_position_map
886        {
887            current_position.x +=
888                f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
889        }
890        let new_position = current_position
891            + point(
892                amount.columns(visible_column_count),
893                amount.lines(visible_line_count),
894            );
895        self.set_scroll_position(new_position, window, cx);
896    }
897
898    /// Returns an ordering. The newest selection is:
899    ///     Ordering::Equal => on screen
900    ///     Ordering::Less => above or to the left of the screen
901    ///     Ordering::Greater => below or to the right of the screen
902    pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
903        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
904        let newest_head = self
905            .selections
906            .newest_anchor()
907            .head()
908            .to_display_point(&snapshot);
909        let screen_top = self.scroll_manager.scroll_top_display_point(&snapshot, cx);
910
911        if screen_top > newest_head {
912            return Ordering::Less;
913        }
914
915        if let (Some(visible_lines), Some(visible_columns)) =
916            (self.visible_line_count(), self.visible_column_count())
917            && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
918            && newest_head.column() <= screen_top.column() + visible_columns as u32
919        {
920            return Ordering::Equal;
921        }
922
923        Ordering::Greater
924    }
925
926    pub fn read_scroll_position_from_db(
927        &mut self,
928        item_id: u64,
929        workspace_id: WorkspaceId,
930        window: &mut Window,
931        cx: &mut Context<Editor>,
932    ) {
933        let scroll_position = EditorDb::global(cx).get_scroll_position(item_id, workspace_id);
934        if let Ok(Some((top_row, x, y))) = scroll_position {
935            let top_anchor = self
936                .buffer()
937                .read(cx)
938                .snapshot(cx)
939                .anchor_before(Point::new(top_row, 0));
940            let scroll_anchor = ScrollAnchor {
941                offset: gpui::Point::new(x, y),
942                anchor: top_anchor,
943            };
944            self.set_scroll_anchor(scroll_anchor, window, cx);
945        }
946    }
947}