scroll.rs

  1mod actions;
  2pub(crate) mod autoscroll;
  3pub(crate) mod scroll_amount;
  4
  5use crate::{
  6    display_map::{DisplaySnapshot, ToDisplayPoint},
  7    hover_popover::hide_hover,
  8    persistence::DB,
  9    Anchor, DisplayPoint, DisplayRow, Editor, EditorEvent, EditorMode, EditorSettings,
 10    InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint,
 11};
 12pub use autoscroll::{Autoscroll, AutoscrollStrategy};
 13use gpui::{point, px, AppContext, Entity, Global, Pixels, Task, ViewContext, WindowContext};
 14use language::{Bias, Point};
 15pub use scroll_amount::ScrollAmount;
 16use settings::Settings;
 17use std::{
 18    cmp::Ordering,
 19    time::{Duration, Instant},
 20};
 21use util::ResultExt;
 22use workspace::{ItemId, WorkspaceId};
 23
 24pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
 25const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
 26
 27#[derive(Default)]
 28pub struct ScrollbarAutoHide(pub bool);
 29
 30impl Global for ScrollbarAutoHide {}
 31
 32#[derive(Clone, Copy, Debug, PartialEq)]
 33pub struct ScrollAnchor {
 34    pub offset: gpui::Point<f32>,
 35    pub anchor: Anchor,
 36}
 37
 38impl ScrollAnchor {
 39    fn new() -> Self {
 40        Self {
 41            offset: gpui::Point::default(),
 42            anchor: Anchor::min(),
 43        }
 44    }
 45
 46    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
 47        let mut scroll_position = self.offset;
 48        if self.anchor == Anchor::min() {
 49            scroll_position.y = 0.;
 50        } else {
 51            let scroll_top = self.anchor.to_display_point(snapshot).row().as_f32();
 52            scroll_position.y = scroll_top + scroll_position.y;
 53        }
 54        scroll_position
 55    }
 56
 57    pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
 58        self.anchor.to_point(buffer).row
 59    }
 60}
 61
 62#[derive(Copy, Clone, PartialEq, Eq, Debug)]
 63pub enum Axis {
 64    Vertical,
 65    Horizontal,
 66}
 67
 68#[derive(Clone, Copy, Debug)]
 69pub struct OngoingScroll {
 70    last_event: Instant,
 71    axis: Option<Axis>,
 72}
 73
 74impl OngoingScroll {
 75    fn new() -> Self {
 76        Self {
 77            last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
 78            axis: None,
 79        }
 80    }
 81
 82    pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
 83        const UNLOCK_PERCENT: f32 = 1.9;
 84        const UNLOCK_LOWER_BOUND: Pixels = px(6.);
 85        let mut axis = self.axis;
 86
 87        let x = delta.x.abs();
 88        let y = delta.y.abs();
 89        let duration = Instant::now().duration_since(self.last_event);
 90        if duration > SCROLL_EVENT_SEPARATION {
 91            //New ongoing scroll will start, determine axis
 92            axis = if x <= y {
 93                Some(Axis::Vertical)
 94            } else {
 95                Some(Axis::Horizontal)
 96            };
 97        } else if x.max(y) >= UNLOCK_LOWER_BOUND {
 98            //Check if the current ongoing will need to unlock
 99            match axis {
100                Some(Axis::Vertical) => {
101                    if x > y && x >= y * UNLOCK_PERCENT {
102                        axis = None;
103                    }
104                }
105
106                Some(Axis::Horizontal) => {
107                    if y > x && y >= x * UNLOCK_PERCENT {
108                        axis = None;
109                    }
110                }
111
112                None => {}
113            }
114        }
115
116        match axis {
117            Some(Axis::Vertical) => {
118                *delta = point(px(0.), delta.y);
119            }
120            Some(Axis::Horizontal) => {
121                *delta = point(delta.x, px(0.));
122            }
123            None => {}
124        }
125
126        axis
127    }
128}
129
130pub struct ScrollManager {
131    pub(crate) vertical_scroll_margin: f32,
132    anchor: ScrollAnchor,
133    ongoing: OngoingScroll,
134    autoscroll_request: Option<(Autoscroll, bool)>,
135    last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
136    show_scrollbars: bool,
137    hide_scrollbar_task: Option<Task<()>>,
138    dragging_scrollbar: bool,
139    visible_line_count: Option<f32>,
140    forbid_vertical_scroll: bool,
141}
142
143impl ScrollManager {
144    pub fn new(cx: &mut WindowContext) -> Self {
145        ScrollManager {
146            vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin,
147            anchor: ScrollAnchor::new(),
148            ongoing: OngoingScroll::new(),
149            autoscroll_request: None,
150            show_scrollbars: true,
151            hide_scrollbar_task: None,
152            dragging_scrollbar: false,
153            last_autoscroll: None,
154            visible_line_count: None,
155            forbid_vertical_scroll: false,
156        }
157    }
158
159    pub fn clone_state(&mut self, other: &Self) {
160        self.anchor = other.anchor;
161        self.ongoing = other.ongoing;
162    }
163
164    pub fn anchor(&self) -> ScrollAnchor {
165        self.anchor
166    }
167
168    pub fn ongoing_scroll(&self) -> OngoingScroll {
169        self.ongoing
170    }
171
172    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
173        self.ongoing.last_event = Instant::now();
174        self.ongoing.axis = axis;
175    }
176
177    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
178        self.anchor.scroll_position(snapshot)
179    }
180
181    fn set_scroll_position(
182        &mut self,
183        scroll_position: gpui::Point<f32>,
184        map: &DisplaySnapshot,
185        local: bool,
186        autoscroll: bool,
187        workspace_id: Option<WorkspaceId>,
188        cx: &mut ViewContext<Editor>,
189    ) {
190        if self.forbid_vertical_scroll {
191            return;
192        }
193        let (new_anchor, top_row) = if scroll_position.y <= 0. {
194            (
195                ScrollAnchor {
196                    anchor: Anchor::min(),
197                    offset: scroll_position.max(&gpui::Point::default()),
198                },
199                0,
200            )
201        } else {
202            let scroll_top_buffer_point =
203                DisplayPoint::new(DisplayRow(scroll_position.y as u32), 0).to_point(&map);
204            let top_anchor = map
205                .buffer_snapshot
206                .anchor_at(scroll_top_buffer_point, Bias::Right);
207
208            (
209                ScrollAnchor {
210                    anchor: top_anchor,
211                    offset: point(
212                        scroll_position.x.max(0.),
213                        scroll_position.y - top_anchor.to_display_point(&map).row().as_f32(),
214                    ),
215                },
216                scroll_top_buffer_point.row,
217            )
218        };
219
220        self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx);
221    }
222
223    fn set_anchor(
224        &mut self,
225        anchor: ScrollAnchor,
226        top_row: u32,
227        local: bool,
228        autoscroll: bool,
229        workspace_id: Option<WorkspaceId>,
230        cx: &mut ViewContext<Editor>,
231    ) {
232        if self.forbid_vertical_scroll {
233            return;
234        }
235        self.anchor = anchor;
236        cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
237        self.show_scrollbar(cx);
238        self.autoscroll_request.take();
239        if let Some(workspace_id) = workspace_id {
240            let item_id = cx.view().entity_id().as_u64() as ItemId;
241
242            cx.foreground_executor()
243                .spawn(async move {
244                    DB.save_scroll_position(
245                        item_id,
246                        workspace_id,
247                        top_row,
248                        anchor.offset.x,
249                        anchor.offset.y,
250                    )
251                    .await
252                    .log_err()
253                })
254                .detach()
255        }
256        cx.notify();
257    }
258
259    pub fn show_scrollbar(&mut self, cx: &mut ViewContext<Editor>) {
260        if !self.show_scrollbars {
261            self.show_scrollbars = true;
262            cx.notify();
263        }
264
265        if cx.default_global::<ScrollbarAutoHide>().0 {
266            self.hide_scrollbar_task = Some(cx.spawn(|editor, mut cx| async move {
267                cx.background_executor()
268                    .timer(SCROLLBAR_SHOW_INTERVAL)
269                    .await;
270                editor
271                    .update(&mut cx, |editor, cx| {
272                        editor.scroll_manager.show_scrollbars = false;
273                        cx.notify();
274                    })
275                    .log_err();
276            }));
277        } else {
278            self.hide_scrollbar_task = None;
279        }
280    }
281
282    pub fn scrollbars_visible(&self) -> bool {
283        self.show_scrollbars
284    }
285
286    pub fn autoscroll_requested(&self) -> bool {
287        self.autoscroll_request.is_some()
288    }
289
290    pub fn is_dragging_scrollbar(&self) -> bool {
291        self.dragging_scrollbar
292    }
293
294    pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
295        if dragging != self.dragging_scrollbar {
296            self.dragging_scrollbar = dragging;
297            cx.notify();
298        }
299    }
300
301    pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
302        if max < self.anchor.offset.x {
303            self.anchor.offset.x = max;
304            true
305        } else {
306            false
307        }
308    }
309
310    pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
311        self.forbid_vertical_scroll = forbid;
312    }
313
314    pub fn forbid_vertical_scroll(&self) -> bool {
315        self.forbid_vertical_scroll
316    }
317}
318
319impl Editor {
320    pub fn vertical_scroll_margin(&self) -> usize {
321        self.scroll_manager.vertical_scroll_margin as usize
322    }
323
324    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
325        self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
326        cx.notify();
327    }
328
329    pub fn visible_line_count(&self) -> Option<f32> {
330        self.scroll_manager.visible_line_count
331    }
332
333    pub fn visible_row_count(&self) -> Option<u32> {
334        self.visible_line_count()
335            .map(|line_count| line_count as u32 - 1)
336    }
337
338    pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
339        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
340        self.scroll_manager.visible_line_count = Some(lines);
341        if opened_first_time {
342            cx.spawn(|editor, mut cx| async move {
343                editor
344                    .update(&mut cx, |editor, cx| {
345                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
346                    })
347                    .ok()
348            })
349            .detach()
350        }
351    }
352
353    pub fn apply_scroll_delta(
354        &mut self,
355        scroll_delta: gpui::Point<f32>,
356        cx: &mut ViewContext<Self>,
357    ) {
358        if self.scroll_manager.forbid_vertical_scroll {
359            return;
360        }
361        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
362        let position = self.scroll_manager.anchor.scroll_position(&display_map) + scroll_delta;
363        self.set_scroll_position_taking_display_map(position, true, false, display_map, cx);
364    }
365
366    pub fn set_scroll_position(
367        &mut self,
368        scroll_position: gpui::Point<f32>,
369        cx: &mut ViewContext<Self>,
370    ) {
371        if self.scroll_manager.forbid_vertical_scroll {
372            return;
373        }
374        self.set_scroll_position_internal(scroll_position, true, false, cx);
375    }
376
377    pub(crate) fn set_scroll_position_internal(
378        &mut self,
379        scroll_position: gpui::Point<f32>,
380        local: bool,
381        autoscroll: bool,
382        cx: &mut ViewContext<Self>,
383    ) {
384        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
385        self.set_scroll_position_taking_display_map(scroll_position, local, autoscroll, map, cx);
386    }
387
388    fn set_scroll_position_taking_display_map(
389        &mut self,
390        scroll_position: gpui::Point<f32>,
391        local: bool,
392        autoscroll: bool,
393        display_map: DisplaySnapshot,
394        cx: &mut ViewContext<Self>,
395    ) {
396        hide_hover(self, cx);
397        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
398
399        self.scroll_manager.set_scroll_position(
400            scroll_position,
401            &display_map,
402            local,
403            autoscroll,
404            workspace_id,
405            cx,
406        );
407
408        self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
409    }
410
411    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<f32> {
412        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
413        self.scroll_manager.anchor.scroll_position(&display_map)
414    }
415
416    pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
417        hide_hover(self, cx);
418        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
419        let top_row = scroll_anchor
420            .anchor
421            .to_point(&self.buffer().read(cx).snapshot(cx))
422            .row;
423        self.scroll_manager
424            .set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx);
425    }
426
427    pub(crate) fn set_scroll_anchor_remote(
428        &mut self,
429        scroll_anchor: ScrollAnchor,
430        cx: &mut ViewContext<Self>,
431    ) {
432        hide_hover(self, cx);
433        let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
434        let snapshot = &self.buffer().read(cx).snapshot(cx);
435        if !scroll_anchor.anchor.is_valid(snapshot) {
436            log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
437            return;
438        }
439        let top_row = scroll_anchor.anchor.to_point(snapshot).row;
440        self.scroll_manager
441            .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
442    }
443
444    pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
445        if matches!(self.mode, EditorMode::SingleLine) {
446            cx.propagate();
447            return;
448        }
449
450        if self.take_rename(true, cx).is_some() {
451            return;
452        }
453
454        let cur_position = self.scroll_position(cx);
455        let new_pos = cur_position + point(0., amount.lines(self));
456        self.set_scroll_position(new_pos, cx);
457    }
458
459    /// Returns an ordering. The newest selection is:
460    ///     Ordering::Equal => on screen
461    ///     Ordering::Less => above the screen
462    ///     Ordering::Greater => below the screen
463    pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
464        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
465        let newest_head = self
466            .selections
467            .newest_anchor()
468            .head()
469            .to_display_point(&snapshot);
470        let screen_top = self
471            .scroll_manager
472            .anchor
473            .anchor
474            .to_display_point(&snapshot);
475
476        if screen_top > newest_head {
477            return Ordering::Less;
478        }
479
480        if let Some(visible_lines) = self.visible_line_count() {
481            if newest_head.row() < DisplayRow(screen_top.row().0 + visible_lines as u32) {
482                return Ordering::Equal;
483            }
484        }
485
486        Ordering::Greater
487    }
488
489    pub fn read_scroll_position_from_db(
490        &mut self,
491        item_id: u64,
492        workspace_id: WorkspaceId,
493        cx: &mut ViewContext<Editor>,
494    ) {
495        let scroll_position = DB.get_scroll_position(item_id, workspace_id);
496        if let Ok(Some((top_row, x, y))) = scroll_position {
497            let top_anchor = self
498                .buffer()
499                .read(cx)
500                .snapshot(cx)
501                .anchor_at(Point::new(top_row, 0), Bias::Left);
502            let scroll_anchor = ScrollAnchor {
503                offset: gpui::Point::new(x, y),
504                anchor: top_anchor,
505            };
506            self.set_scroll_anchor(scroll_anchor, cx);
507        }
508    }
509}