scroll.rs

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