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, EditorEvent, EditorMode, InlayHintRefreshReason,
 10    MultiBufferSnapshot, ToPoint,
 11};
 12use gpui::{point, px, AppContext, Entity, Pixels, Styled, Task, ViewContext};
 13use language::{Bias, Point};
 14use std::{
 15    cmp::Ordering,
 16    time::{Duration, Instant},
 17};
 18use util::ResultExt;
 19use workspace::{ItemId, 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: gpui::Point::default(),
 43            anchor: Anchor::min(),
 44        }
 45    }
 46
 47    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
 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.y = scroll_top + scroll_position.y;
 52        } else {
 53            scroll_position.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 gpui::Point<Pixels>) -> Option<Axis> {
 84        const UNLOCK_PERCENT: f32 = 1.9;
 85        const UNLOCK_LOWER_BOUND: Pixels = px(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) => {
119                *delta = point(px(0.), delta.y);
120            }
121            Some(Axis::Horizontal) => {
122                *delta = point(delta.x, px(0.));
123            }
124            None => {}
125        }
126
127        axis
128    }
129}
130
131pub struct ScrollManager {
132    vertical_scroll_margin: f32,
133    anchor: ScrollAnchor,
134    ongoing: OngoingScroll,
135    autoscroll_request: Option<(Autoscroll, bool)>,
136    last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
137    show_scrollbars: bool,
138    hide_scrollbar_task: Option<Task<()>>,
139    dragging_scrollbar: bool,
140    visible_line_count: Option<f32>,
141}
142
143impl ScrollManager {
144    pub fn new() -> Self {
145        ScrollManager {
146            vertical_scroll_margin: 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        }
156    }
157
158    pub fn clone_state(&mut self, other: &Self) {
159        self.anchor = other.anchor;
160        self.ongoing = other.ongoing;
161    }
162
163    pub fn anchor(&self) -> ScrollAnchor {
164        self.anchor
165    }
166
167    pub fn ongoing_scroll(&self) -> OngoingScroll {
168        self.ongoing
169    }
170
171    pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
172        self.ongoing.last_event = Instant::now();
173        self.ongoing.axis = axis;
174    }
175
176    pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
177        self.anchor.scroll_position(snapshot)
178    }
179
180    fn set_scroll_position(
181        &mut self,
182        scroll_position: gpui::Point<f32>,
183        map: &DisplaySnapshot,
184        local: bool,
185        autoscroll: bool,
186        workspace_id: Option<i64>,
187        cx: &mut ViewContext<Editor>,
188    ) {
189        let (new_anchor, top_row) = if scroll_position.y <= 0. {
190            (
191                ScrollAnchor {
192                    anchor: Anchor::min(),
193                    offset: scroll_position.max(&gpui::Point::default()),
194                },
195                0,
196            )
197        } else {
198            let scroll_top_buffer_point =
199                DisplayPoint::new(scroll_position.y as u32, 0).to_point(&map);
200            let top_anchor = map
201                .buffer_snapshot
202                .anchor_at(scroll_top_buffer_point, Bias::Right);
203
204            (
205                ScrollAnchor {
206                    anchor: top_anchor,
207                    offset: point(
208                        scroll_position.x,
209                        scroll_position.y - top_anchor.to_display_point(&map).row() as f32,
210                    ),
211                },
212                scroll_top_buffer_point.row,
213            )
214        };
215
216        self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx);
217    }
218
219    fn set_anchor(
220        &mut self,
221        anchor: ScrollAnchor,
222        top_row: u32,
223        local: bool,
224        autoscroll: bool,
225        workspace_id: Option<i64>,
226        cx: &mut ViewContext<Editor>,
227    ) {
228        self.anchor = anchor;
229        cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
230        self.show_scrollbar(cx);
231        self.autoscroll_request.take();
232        if let Some(workspace_id) = workspace_id {
233            let item_id = cx.view().entity_id().as_u64() as ItemId;
234
235            cx.foreground_executor()
236                .spawn(async move {
237                    DB.save_scroll_position(
238                        item_id,
239                        workspace_id,
240                        top_row,
241                        anchor.offset.x,
242                        anchor.offset.y,
243                    )
244                    .await
245                    .log_err()
246                })
247                .detach()
248        }
249        cx.notify();
250    }
251
252    pub fn show_scrollbar(&mut self, cx: &mut ViewContext<Editor>) {
253        if !self.show_scrollbars {
254            self.show_scrollbars = true;
255            cx.notify();
256        }
257
258        if cx.default_global::<ScrollbarAutoHide>().0 {
259            self.hide_scrollbar_task = Some(cx.spawn(|editor, mut cx| async move {
260                cx.background_executor()
261                    .timer(SCROLLBAR_SHOW_INTERVAL)
262                    .await;
263                editor
264                    .update(&mut cx, |editor, cx| {
265                        editor.scroll_manager.show_scrollbars = false;
266                        cx.notify();
267                    })
268                    .log_err();
269            }));
270        } else {
271            self.hide_scrollbar_task = None;
272        }
273    }
274
275    pub fn scrollbars_visible(&self) -> bool {
276        self.show_scrollbars
277    }
278
279    pub fn has_autoscroll_request(&self) -> bool {
280        self.autoscroll_request.is_some()
281    }
282
283    pub fn is_dragging_scrollbar(&self) -> bool {
284        self.dragging_scrollbar
285    }
286
287    pub fn set_is_dragging_scrollbar(&mut self, dragging: bool, cx: &mut ViewContext<Editor>) {
288        if dragging != self.dragging_scrollbar {
289            self.dragging_scrollbar = dragging;
290            cx.notify();
291        }
292    }
293
294    pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
295        if max < self.anchor.offset.x {
296            self.anchor.offset.x = max;
297            true
298        } else {
299            false
300        }
301    }
302}
303
304impl Editor {
305    pub fn vertical_scroll_margin(&mut self) -> usize {
306        self.scroll_manager.vertical_scroll_margin as usize
307    }
308
309    pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
310        self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
311        cx.notify();
312    }
313
314    pub fn visible_line_count(&self) -> Option<f32> {
315        self.scroll_manager.visible_line_count
316    }
317
318    pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext<Self>) {
319        let opened_first_time = self.scroll_manager.visible_line_count.is_none();
320        self.scroll_manager.visible_line_count = Some(lines);
321        if opened_first_time {
322            cx.spawn(|editor, mut cx| async move {
323                editor
324                    .update(&mut cx, |editor, cx| {
325                        editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
326                    })
327                    .ok()
328            })
329            .detach()
330        }
331    }
332
333    pub fn set_scroll_position(
334        &mut self,
335        scroll_position: gpui::Point<f32>,
336        cx: &mut ViewContext<Self>,
337    ) {
338        self.set_scroll_position_internal(scroll_position, true, false, cx);
339    }
340
341    pub(crate) fn set_scroll_position_internal(
342        &mut self,
343        scroll_position: gpui::Point<f32>,
344        local: bool,
345        autoscroll: bool,
346        cx: &mut ViewContext<Self>,
347    ) {
348        let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
349
350        hide_hover(self, cx);
351        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
352        self.scroll_manager.set_scroll_position(
353            scroll_position,
354            &map,
355            local,
356            autoscroll,
357            workspace_id,
358            cx,
359        );
360
361        self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
362    }
363
364    pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> gpui::Point<f32> {
365        let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
366        self.scroll_manager.anchor.scroll_position(&display_map)
367    }
368
369    pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
370        hide_hover(self, cx);
371        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
372        let top_row = scroll_anchor
373            .anchor
374            .to_point(&self.buffer().read(cx).snapshot(cx))
375            .row;
376        self.scroll_manager
377            .set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx);
378    }
379
380    pub(crate) fn set_scroll_anchor_remote(
381        &mut self,
382        scroll_anchor: ScrollAnchor,
383        cx: &mut ViewContext<Self>,
384    ) {
385        hide_hover(self, cx);
386        let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
387        let top_row = scroll_anchor
388            .anchor
389            .to_point(&self.buffer().read(cx).snapshot(cx))
390            .row;
391        self.scroll_manager
392            .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
393    }
394
395    pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
396        if matches!(self.mode, EditorMode::SingleLine) {
397            cx.propagate();
398            return;
399        }
400
401        if self.take_rename(true, cx).is_some() {
402            return;
403        }
404
405        let cur_position = self.scroll_position(cx);
406        let new_pos = cur_position + point(0., amount.lines(self));
407        self.set_scroll_position(new_pos, cx);
408    }
409
410    /// Returns an ordering. The newest selection is:
411    ///     Ordering::Equal => on screen
412    ///     Ordering::Less => above the screen
413    ///     Ordering::Greater => below the screen
414    pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
415        let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
416        let newest_head = self
417            .selections
418            .newest_anchor()
419            .head()
420            .to_display_point(&snapshot);
421        let screen_top = self
422            .scroll_manager
423            .anchor
424            .anchor
425            .to_display_point(&snapshot);
426
427        if screen_top > newest_head {
428            return Ordering::Less;
429        }
430
431        if let Some(visible_lines) = self.visible_line_count() {
432            if newest_head.row() < screen_top.row() + visible_lines as u32 {
433                return Ordering::Equal;
434            }
435        }
436
437        Ordering::Greater
438    }
439
440    pub fn read_scroll_position_from_db(
441        &mut self,
442        item_id: u64,
443        workspace_id: WorkspaceId,
444        cx: &mut ViewContext<Editor>,
445    ) {
446        let scroll_position = DB.get_scroll_position(item_id, workspace_id);
447        if let Ok(Some((top_row, x, y))) = scroll_position {
448            let top_anchor = self
449                .buffer()
450                .read(cx)
451                .snapshot(cx)
452                .anchor_at(Point::new(top_row as u32, 0), Bias::Left);
453            let scroll_anchor = ScrollAnchor {
454                offset: gpui::Point::new(x, y),
455                anchor: top_anchor,
456            };
457            self.set_scroll_anchor(scroll_anchor, cx);
458        }
459    }
460}