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, ToPoint,
9 display_map::{DisplaySnapshot, ToDisplayPoint},
10 hover_popover::hide_hover,
11 persistence::DB,
12};
13pub use autoscroll::{Autoscroll, AutoscrollStrategy};
14use core::fmt::Debug;
15use gpui::{Along, App, Axis, Context, Pixels, Task, Window, point, px};
16use language::language_settings::{AllLanguageSettings, SoftWrap};
17use language::{Bias, Point};
18pub use scroll_amount::ScrollAmount;
19use settings::Settings;
20use std::{
21 cmp::Ordering,
22 time::{Duration, Instant},
23};
24use ui::scrollbars::ScrollbarAutoHide;
25use util::ResultExt;
26use workspace::{ItemId, WorkspaceId};
27
28pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
29const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
30
31pub struct WasScrolled(pub(crate) bool);
32
33pub type ScrollOffset = f64;
34pub type ScrollPixelOffset = f64;
35#[derive(Clone, Copy, Debug, PartialEq)]
36pub struct ScrollAnchor {
37 pub offset: gpui::Point<ScrollOffset>,
38 pub anchor: Anchor,
39}
40
41impl ScrollAnchor {
42 pub(super) fn new() -> Self {
43 Self {
44 offset: gpui::Point::default(),
45 anchor: Anchor::min(),
46 }
47 }
48
49 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
50 self.offset.apply_along(Axis::Vertical, |offset| {
51 if self.anchor == Anchor::min() {
52 0.
53 } else {
54 let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
55 (offset + scroll_top).max(0.)
56 }
57 })
58 }
59
60 pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
61 self.anchor.to_point(buffer).row
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
66pub struct OngoingScroll {
67 last_event: Instant,
68 axis: Option<Axis>,
69}
70
71impl OngoingScroll {
72 fn new() -> Self {
73 Self {
74 last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
75 axis: None,
76 }
77 }
78
79 pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
80 const UNLOCK_PERCENT: f32 = 1.9;
81 const UNLOCK_LOWER_BOUND: Pixels = px(6.);
82 let mut axis = self.axis;
83
84 let x = delta.x.abs();
85 let y = delta.y.abs();
86 let duration = Instant::now().duration_since(self.last_event);
87 if duration > SCROLL_EVENT_SEPARATION {
88 //New ongoing scroll will start, determine axis
89 axis = if x <= y {
90 Some(Axis::Vertical)
91 } else {
92 Some(Axis::Horizontal)
93 };
94 } else if x.max(y) >= UNLOCK_LOWER_BOUND {
95 //Check if the current ongoing will need to unlock
96 match axis {
97 Some(Axis::Vertical) => {
98 if x > y && x >= y * UNLOCK_PERCENT {
99 axis = None;
100 }
101 }
102
103 Some(Axis::Horizontal) => {
104 if y > x && y >= x * UNLOCK_PERCENT {
105 axis = None;
106 }
107 }
108
109 None => {}
110 }
111 }
112
113 match axis {
114 Some(Axis::Vertical) => {
115 *delta = point(px(0.), delta.y);
116 }
117 Some(Axis::Horizontal) => {
118 *delta = point(delta.x, px(0.));
119 }
120 None => {}
121 }
122
123 axis
124 }
125}
126
127#[derive(Copy, Clone, Default, PartialEq, Eq)]
128pub enum ScrollbarThumbState {
129 #[default]
130 Idle,
131 Hovered,
132 Dragging,
133}
134
135#[derive(PartialEq, Eq)]
136pub struct ActiveScrollbarState {
137 axis: Axis,
138 thumb_state: ScrollbarThumbState,
139}
140
141impl ActiveScrollbarState {
142 pub fn new(axis: Axis, thumb_state: ScrollbarThumbState) -> Self {
143 ActiveScrollbarState { axis, thumb_state }
144 }
145
146 pub fn thumb_state_for_axis(&self, axis: Axis) -> Option<ScrollbarThumbState> {
147 (self.axis == axis).then_some(self.thumb_state)
148 }
149}
150
151pub struct ScrollManager {
152 pub(crate) vertical_scroll_margin: ScrollOffset,
153 anchor: ScrollAnchor,
154 ongoing: OngoingScroll,
155 /// The second element indicates whether the autoscroll request is local
156 /// (true) or remote (false). Local requests are initiated by user actions,
157 /// while remote requests come from external sources.
158 autoscroll_request: Option<(Autoscroll, bool)>,
159 last_autoscroll: Option<(
160 gpui::Point<ScrollOffset>,
161 ScrollOffset,
162 ScrollOffset,
163 AutoscrollStrategy,
164 )>,
165 show_scrollbars: bool,
166 hide_scrollbar_task: Option<Task<()>>,
167 active_scrollbar: Option<ActiveScrollbarState>,
168 visible_line_count: Option<f64>,
169 visible_column_count: Option<f64>,
170 forbid_vertical_scroll: bool,
171 minimap_thumb_state: Option<ScrollbarThumbState>,
172}
173
174impl ScrollManager {
175 pub fn new(cx: &mut App) -> Self {
176 ScrollManager {
177 vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin,
178 anchor: ScrollAnchor::new(),
179 ongoing: OngoingScroll::new(),
180 autoscroll_request: None,
181 show_scrollbars: true,
182 hide_scrollbar_task: None,
183 active_scrollbar: None,
184 last_autoscroll: None,
185 visible_line_count: None,
186 visible_column_count: None,
187 forbid_vertical_scroll: false,
188 minimap_thumb_state: None,
189 }
190 }
191
192 pub fn clone_state(&mut self, other: &Self) {
193 self.anchor = other.anchor;
194 self.ongoing = other.ongoing;
195 }
196
197 pub fn anchor(&self) -> ScrollAnchor {
198 self.anchor
199 }
200
201 pub fn ongoing_scroll(&self) -> OngoingScroll {
202 self.ongoing
203 }
204
205 pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
206 self.ongoing.last_event = Instant::now();
207 self.ongoing.axis = axis;
208 }
209
210 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
211 self.anchor.scroll_position(snapshot)
212 }
213
214 fn set_scroll_position(
215 &mut self,
216 scroll_position: gpui::Point<ScrollOffset>,
217 map: &DisplaySnapshot,
218 local: bool,
219 autoscroll: bool,
220 workspace_id: Option<WorkspaceId>,
221 window: &mut Window,
222 cx: &mut Context<Editor>,
223 ) -> WasScrolled {
224 let scroll_top = scroll_position.y.max(0.);
225 let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
226 ScrollBeyondLastLine::OnePage => scroll_top,
227 ScrollBeyondLastLine::Off => {
228 if let Some(height_in_lines) = self.visible_line_count {
229 let max_row = map.max_point().row().as_f64();
230 scroll_top.min(max_row - height_in_lines + 1.).max(0.)
231 } else {
232 scroll_top
233 }
234 }
235 ScrollBeyondLastLine::VerticalScrollMargin => {
236 if let Some(height_in_lines) = self.visible_line_count {
237 let max_row = map.max_point().row().as_f64();
238 scroll_top
239 .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
240 .max(0.)
241 } else {
242 scroll_top
243 }
244 }
245 };
246
247 let scroll_top_row = DisplayRow(scroll_top as u32);
248 let scroll_top_buffer_point = map
249 .clip_point(
250 DisplayPoint::new(scroll_top_row, scroll_position.x as u32),
251 Bias::Left,
252 )
253 .to_point(map);
254 // Anchor the scroll position to the *left* of the first visible buffer point.
255 //
256 // This prevents the viewport from shifting down when blocks (e.g. expanded diff hunk
257 // deletions) are inserted *above* the first buffer character in the file.
258 let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point);
259
260 self.set_anchor(
261 ScrollAnchor {
262 anchor: top_anchor,
263 offset: point(
264 scroll_position.x.max(0.),
265 scroll_top - top_anchor.to_display_point(map).row().as_f64(),
266 ),
267 },
268 scroll_top_buffer_point.row,
269 local,
270 autoscroll,
271 workspace_id,
272 window,
273 cx,
274 )
275 }
276
277 fn set_anchor(
278 &mut self,
279 anchor: ScrollAnchor,
280 top_row: u32,
281 local: bool,
282 autoscroll: bool,
283 workspace_id: Option<WorkspaceId>,
284 window: &mut Window,
285 cx: &mut Context<Editor>,
286 ) -> WasScrolled {
287 let adjusted_anchor = if self.forbid_vertical_scroll {
288 ScrollAnchor {
289 offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y),
290 anchor: self.anchor.anchor,
291 }
292 } else {
293 anchor
294 };
295
296 self.autoscroll_request.take();
297 if self.anchor == adjusted_anchor {
298 return WasScrolled(false);
299 }
300
301 self.anchor = adjusted_anchor;
302 cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
303 self.show_scrollbars(window, cx);
304 if let Some(workspace_id) = workspace_id {
305 let item_id = cx.entity().entity_id().as_u64() as ItemId;
306
307 cx.foreground_executor()
308 .spawn(async move {
309 log::debug!(
310 "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}"
311 );
312 DB.save_scroll_position(
313 item_id,
314 workspace_id,
315 top_row,
316 anchor.offset.x,
317 anchor.offset.y,
318 )
319 .await
320 .log_err()
321 })
322 .detach()
323 }
324 cx.notify();
325
326 WasScrolled(true)
327 }
328
329 pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
330 if !self.show_scrollbars {
331 self.show_scrollbars = true;
332 cx.notify();
333 }
334
335 if cx.default_global::<ScrollbarAutoHide>().should_hide() {
336 self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
337 cx.background_executor()
338 .timer(SCROLLBAR_SHOW_INTERVAL)
339 .await;
340 editor
341 .update(cx, |editor, cx| {
342 editor.scroll_manager.show_scrollbars = false;
343 cx.notify();
344 })
345 .log_err();
346 }));
347 } else {
348 self.hide_scrollbar_task = None;
349 }
350 }
351
352 pub fn scrollbars_visible(&self) -> bool {
353 self.show_scrollbars
354 }
355
356 pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
357 self.autoscroll_request.take()
358 }
359
360 pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
361 self.active_scrollbar.as_ref()
362 }
363
364 pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
365 self.active_scrollbar
366 .as_ref()
367 .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
368 .map(|scrollbar| scrollbar.axis)
369 }
370
371 pub fn any_scrollbar_dragged(&self) -> bool {
372 self.active_scrollbar
373 .as_ref()
374 .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
375 }
376
377 pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
378 self.update_active_scrollbar_state(
379 Some(ActiveScrollbarState::new(
380 axis,
381 ScrollbarThumbState::Hovered,
382 )),
383 cx,
384 );
385 }
386
387 pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
388 self.update_active_scrollbar_state(
389 Some(ActiveScrollbarState::new(
390 axis,
391 ScrollbarThumbState::Dragging,
392 )),
393 cx,
394 );
395 }
396
397 pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
398 self.update_active_scrollbar_state(None, cx);
399 }
400
401 fn update_active_scrollbar_state(
402 &mut self,
403 new_state: Option<ActiveScrollbarState>,
404 cx: &mut Context<Editor>,
405 ) {
406 if self.active_scrollbar != new_state {
407 self.active_scrollbar = new_state;
408 cx.notify();
409 }
410 }
411
412 pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) {
413 self.update_minimap_thumb_state(
414 Some(if hovered {
415 ScrollbarThumbState::Hovered
416 } else {
417 ScrollbarThumbState::Idle
418 }),
419 cx,
420 );
421 }
422
423 pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) {
424 self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx);
425 }
426
427 pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
428 self.update_minimap_thumb_state(None, cx);
429 }
430
431 pub fn is_dragging_minimap(&self) -> bool {
432 self.minimap_thumb_state
433 .is_some_and(|state| state == ScrollbarThumbState::Dragging)
434 }
435
436 fn update_minimap_thumb_state(
437 &mut self,
438 thumb_state: Option<ScrollbarThumbState>,
439 cx: &mut Context<Editor>,
440 ) {
441 if self.minimap_thumb_state != thumb_state {
442 self.minimap_thumb_state = thumb_state;
443 cx.notify();
444 }
445 }
446
447 pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> {
448 self.minimap_thumb_state
449 }
450
451 pub fn clamp_scroll_left(&mut self, max: f64) -> bool {
452 if max < self.anchor.offset.x {
453 self.anchor.offset.x = max;
454 true
455 } else {
456 false
457 }
458 }
459
460 pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
461 self.forbid_vertical_scroll = forbid;
462 }
463
464 pub fn forbid_vertical_scroll(&self) -> bool {
465 self.forbid_vertical_scroll
466 }
467}
468
469impl Editor {
470 pub fn vertical_scroll_margin(&self) -> usize {
471 self.scroll_manager.vertical_scroll_margin as usize
472 }
473
474 pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
475 self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
476 cx.notify();
477 }
478
479 pub fn visible_line_count(&self) -> Option<f64> {
480 self.scroll_manager.visible_line_count
481 }
482
483 pub fn visible_row_count(&self) -> Option<u32> {
484 self.visible_line_count()
485 .map(|line_count| line_count as u32 - 1)
486 }
487
488 pub fn visible_column_count(&self) -> Option<f64> {
489 self.scroll_manager.visible_column_count
490 }
491
492 pub(crate) fn set_visible_line_count(
493 &mut self,
494 lines: f64,
495 window: &mut Window,
496 cx: &mut Context<Self>,
497 ) {
498 let opened_first_time = self.scroll_manager.visible_line_count.is_none();
499 self.scroll_manager.visible_line_count = Some(lines);
500 if opened_first_time {
501 self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
502 editor
503 .update_in(cx, |editor, window, cx| {
504 editor.register_visible_buffers(cx);
505 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
506 editor.update_lsp_data(None, window, cx);
507 editor.colorize_brackets(false, cx);
508 })
509 .ok();
510 });
511 }
512 }
513
514 pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
515 self.scroll_manager.visible_column_count = Some(columns);
516 }
517
518 pub fn apply_scroll_delta(
519 &mut self,
520 scroll_delta: gpui::Point<f32>,
521 window: &mut Window,
522 cx: &mut Context<Self>,
523 ) {
524 let mut delta = scroll_delta;
525 if self.scroll_manager.forbid_vertical_scroll {
526 delta.y = 0.0;
527 }
528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
529 let position =
530 self.scroll_manager.anchor.scroll_position(&display_map) + delta.map(f64::from);
531 self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
532 }
533
534 pub fn set_scroll_position(
535 &mut self,
536 scroll_position: gpui::Point<ScrollOffset>,
537 window: &mut Window,
538 cx: &mut Context<Self>,
539 ) -> WasScrolled {
540 let mut position = scroll_position;
541 if self.scroll_manager.forbid_vertical_scroll {
542 let current_position = self.scroll_position(cx);
543 position.y = current_position.y;
544 }
545 self.set_scroll_position_internal(position, true, false, window, cx)
546 }
547
548 /// Scrolls so that `row` is at the top of the editor view.
549 pub fn set_scroll_top_row(
550 &mut self,
551 row: DisplayRow,
552 window: &mut Window,
553 cx: &mut Context<Editor>,
554 ) {
555 let snapshot = self.snapshot(window, cx).display_snapshot;
556 let new_screen_top = DisplayPoint::new(row, 0);
557 let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
558 let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
559
560 self.set_scroll_anchor(
561 ScrollAnchor {
562 anchor: new_anchor,
563 offset: Default::default(),
564 },
565 window,
566 cx,
567 );
568 }
569
570 pub(crate) fn set_scroll_position_internal(
571 &mut self,
572 scroll_position: gpui::Point<ScrollOffset>,
573 local: bool,
574 autoscroll: bool,
575 window: &mut Window,
576 cx: &mut Context<Self>,
577 ) -> WasScrolled {
578 let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
579 self.set_scroll_position_taking_display_map(
580 scroll_position,
581 local,
582 autoscroll,
583 map,
584 window,
585 cx,
586 )
587 }
588
589 fn set_scroll_position_taking_display_map(
590 &mut self,
591 scroll_position: gpui::Point<ScrollOffset>,
592 local: bool,
593 autoscroll: bool,
594 display_map: DisplaySnapshot,
595 window: &mut Window,
596 cx: &mut Context<Self>,
597 ) -> WasScrolled {
598 hide_hover(self, cx);
599 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
600
601 self.edit_prediction_preview
602 .set_previous_scroll_position(None);
603
604 let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
605 let current_position = self.scroll_manager.anchor.scroll_position(&display_map);
606 gpui::Point::new(scroll_position.x, current_position.y)
607 } else {
608 scroll_position
609 };
610
611 self.scroll_manager.set_scroll_position(
612 adjusted_position,
613 &display_map,
614 local,
615 autoscroll,
616 workspace_id,
617 window,
618 cx,
619 )
620 }
621
622 pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
623 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
624 self.scroll_manager.anchor.scroll_position(&display_map)
625 }
626
627 pub fn set_scroll_anchor(
628 &mut self,
629 scroll_anchor: ScrollAnchor,
630 window: &mut Window,
631 cx: &mut Context<Self>,
632 ) {
633 hide_hover(self, cx);
634 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
635 let top_row = scroll_anchor
636 .anchor
637 .to_point(&self.buffer().read(cx).snapshot(cx))
638 .row;
639 self.scroll_manager.set_anchor(
640 scroll_anchor,
641 top_row,
642 true,
643 false,
644 workspace_id,
645 window,
646 cx,
647 );
648 }
649
650 pub(crate) fn set_scroll_anchor_remote(
651 &mut self,
652 scroll_anchor: ScrollAnchor,
653 window: &mut Window,
654 cx: &mut Context<Self>,
655 ) {
656 hide_hover(self, cx);
657 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
658 let snapshot = &self.buffer().read(cx).snapshot(cx);
659 if !scroll_anchor.anchor.is_valid(snapshot) {
660 log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
661 return;
662 }
663 let top_row = scroll_anchor.anchor.to_point(snapshot).row;
664 self.scroll_manager.set_anchor(
665 scroll_anchor,
666 top_row,
667 false,
668 false,
669 workspace_id,
670 window,
671 cx,
672 );
673 }
674
675 pub fn scroll_screen(
676 &mut self,
677 amount: &ScrollAmount,
678 window: &mut Window,
679 cx: &mut Context<Self>,
680 ) {
681 if matches!(self.mode, EditorMode::SingleLine) {
682 cx.propagate();
683 return;
684 }
685
686 if self.take_rename(true, window, cx).is_some() {
687 return;
688 }
689
690 let mut current_position = self.scroll_position(cx);
691 let Some(visible_line_count) = self.visible_line_count() else {
692 return;
693 };
694 let Some(mut visible_column_count) = self.visible_column_count() else {
695 return;
696 };
697
698 // If the user has a preferred line length, and has the editor
699 // configured to wrap at the preferred line length, or bounded to it,
700 // use that value over the visible column count. This was mostly done so
701 // that tests could actually be written for vim's `z l`, `z h`, `z
702 // shift-l` and `z shift-h` commands, as there wasn't a good way to
703 // configure the editor to only display a certain number of columns. If
704 // that ever happens, this could probably be removed.
705 let settings = AllLanguageSettings::get_global(cx);
706 if matches!(
707 settings.defaults.soft_wrap,
708 SoftWrap::PreferredLineLength | SoftWrap::Bounded
709 ) && (settings.defaults.preferred_line_length as f64) < visible_column_count
710 {
711 visible_column_count = settings.defaults.preferred_line_length as f64;
712 }
713
714 // If the scroll position is currently at the left edge of the document
715 // (x == 0.0) and the intent is to scroll right, the gutter's margin
716 // should first be added to the current position, otherwise the cursor
717 // will end at the column position minus the margin, which looks off.
718 if current_position.x == 0.0
719 && amount.columns(visible_column_count) > 0.
720 && let Some(last_position_map) = &self.last_position_map
721 {
722 current_position.x +=
723 f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
724 }
725 let new_position = current_position
726 + point(
727 amount.columns(visible_column_count),
728 amount.lines(visible_line_count),
729 );
730 self.set_scroll_position(new_position, window, cx);
731 }
732
733 /// Returns an ordering. The newest selection is:
734 /// Ordering::Equal => on screen
735 /// Ordering::Less => above or to the left of the screen
736 /// Ordering::Greater => below or to the right of the screen
737 pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
738 let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
739 let newest_head = self
740 .selections
741 .newest_anchor()
742 .head()
743 .to_display_point(&snapshot);
744 let screen_top = self
745 .scroll_manager
746 .anchor
747 .anchor
748 .to_display_point(&snapshot);
749
750 if screen_top > newest_head {
751 return Ordering::Less;
752 }
753
754 if let (Some(visible_lines), Some(visible_columns)) =
755 (self.visible_line_count(), self.visible_column_count())
756 && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
757 && newest_head.column() <= screen_top.column() + visible_columns as u32
758 {
759 return Ordering::Equal;
760 }
761
762 Ordering::Greater
763 }
764
765 pub fn read_scroll_position_from_db(
766 &mut self,
767 item_id: u64,
768 workspace_id: WorkspaceId,
769 window: &mut Window,
770 cx: &mut Context<Editor>,
771 ) {
772 let scroll_position = DB.get_scroll_position(item_id, workspace_id);
773 if let Ok(Some((top_row, x, y))) = scroll_position {
774 let top_anchor = self
775 .buffer()
776 .read(cx)
777 .snapshot(cx)
778 .anchor_before(Point::new(top_row, 0));
779 let scroll_anchor = ScrollAnchor {
780 offset: gpui::Point::new(x, y),
781 anchor: top_anchor,
782 };
783 self.set_scroll_anchor(scroll_anchor, window, cx);
784 }
785 }
786}