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