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