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 let top_anchor = map.buffer_snapshot().anchor_after(scroll_top_buffer_point);
255
256 self.set_anchor(
257 ScrollAnchor {
258 anchor: top_anchor,
259 offset: point(
260 scroll_position.x.max(0.),
261 scroll_top - top_anchor.to_display_point(map).row().as_f64(),
262 ),
263 },
264 scroll_top_buffer_point.row,
265 local,
266 autoscroll,
267 workspace_id,
268 window,
269 cx,
270 )
271 }
272
273 fn set_anchor(
274 &mut self,
275 anchor: ScrollAnchor,
276 top_row: u32,
277 local: bool,
278 autoscroll: bool,
279 workspace_id: Option<WorkspaceId>,
280 window: &mut Window,
281 cx: &mut Context<Editor>,
282 ) -> WasScrolled {
283 let adjusted_anchor = if self.forbid_vertical_scroll {
284 ScrollAnchor {
285 offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y),
286 anchor: self.anchor.anchor,
287 }
288 } else {
289 anchor
290 };
291
292 self.autoscroll_request.take();
293 if self.anchor == adjusted_anchor {
294 return WasScrolled(false);
295 }
296
297 self.anchor = adjusted_anchor;
298 cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
299 self.show_scrollbars(window, cx);
300 if let Some(workspace_id) = workspace_id {
301 let item_id = cx.entity().entity_id().as_u64() as ItemId;
302
303 cx.foreground_executor()
304 .spawn(async move {
305 log::debug!(
306 "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}"
307 );
308 DB.save_scroll_position(
309 item_id,
310 workspace_id,
311 top_row,
312 anchor.offset.x,
313 anchor.offset.y,
314 )
315 .await
316 .log_err()
317 })
318 .detach()
319 }
320 cx.notify();
321
322 WasScrolled(true)
323 }
324
325 pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
326 if !self.show_scrollbars {
327 self.show_scrollbars = true;
328 cx.notify();
329 }
330
331 if cx.default_global::<ScrollbarAutoHide>().should_hide() {
332 self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
333 cx.background_executor()
334 .timer(SCROLLBAR_SHOW_INTERVAL)
335 .await;
336 editor
337 .update(cx, |editor, cx| {
338 editor.scroll_manager.show_scrollbars = false;
339 cx.notify();
340 })
341 .log_err();
342 }));
343 } else {
344 self.hide_scrollbar_task = None;
345 }
346 }
347
348 pub fn scrollbars_visible(&self) -> bool {
349 self.show_scrollbars
350 }
351
352 pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> {
353 self.autoscroll_request.take()
354 }
355
356 pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> {
357 self.active_scrollbar.as_ref()
358 }
359
360 pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
361 self.active_scrollbar
362 .as_ref()
363 .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
364 .map(|scrollbar| scrollbar.axis)
365 }
366
367 pub fn any_scrollbar_dragged(&self) -> bool {
368 self.active_scrollbar
369 .as_ref()
370 .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging)
371 }
372
373 pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
374 self.update_active_scrollbar_state(
375 Some(ActiveScrollbarState::new(
376 axis,
377 ScrollbarThumbState::Hovered,
378 )),
379 cx,
380 );
381 }
382
383 pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
384 self.update_active_scrollbar_state(
385 Some(ActiveScrollbarState::new(
386 axis,
387 ScrollbarThumbState::Dragging,
388 )),
389 cx,
390 );
391 }
392
393 pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) {
394 self.update_active_scrollbar_state(None, cx);
395 }
396
397 fn update_active_scrollbar_state(
398 &mut self,
399 new_state: Option<ActiveScrollbarState>,
400 cx: &mut Context<Editor>,
401 ) {
402 if self.active_scrollbar != new_state {
403 self.active_scrollbar = new_state;
404 cx.notify();
405 }
406 }
407
408 pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) {
409 self.update_minimap_thumb_state(
410 Some(if hovered {
411 ScrollbarThumbState::Hovered
412 } else {
413 ScrollbarThumbState::Idle
414 }),
415 cx,
416 );
417 }
418
419 pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) {
420 self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx);
421 }
422
423 pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) {
424 self.update_minimap_thumb_state(None, cx);
425 }
426
427 pub fn is_dragging_minimap(&self) -> bool {
428 self.minimap_thumb_state
429 .is_some_and(|state| state == ScrollbarThumbState::Dragging)
430 }
431
432 fn update_minimap_thumb_state(
433 &mut self,
434 thumb_state: Option<ScrollbarThumbState>,
435 cx: &mut Context<Editor>,
436 ) {
437 if self.minimap_thumb_state != thumb_state {
438 self.minimap_thumb_state = thumb_state;
439 cx.notify();
440 }
441 }
442
443 pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> {
444 self.minimap_thumb_state
445 }
446
447 pub fn clamp_scroll_left(&mut self, max: f64) -> bool {
448 if max < self.anchor.offset.x {
449 self.anchor.offset.x = max;
450 true
451 } else {
452 false
453 }
454 }
455
456 pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
457 self.forbid_vertical_scroll = forbid;
458 }
459
460 pub fn forbid_vertical_scroll(&self) -> bool {
461 self.forbid_vertical_scroll
462 }
463}
464
465impl Editor {
466 pub fn vertical_scroll_margin(&self) -> usize {
467 self.scroll_manager.vertical_scroll_margin as usize
468 }
469
470 pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
471 self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
472 cx.notify();
473 }
474
475 pub fn visible_line_count(&self) -> Option<f64> {
476 self.scroll_manager.visible_line_count
477 }
478
479 pub fn visible_row_count(&self) -> Option<u32> {
480 self.visible_line_count()
481 .map(|line_count| line_count as u32 - 1)
482 }
483
484 pub fn visible_column_count(&self) -> Option<f64> {
485 self.scroll_manager.visible_column_count
486 }
487
488 pub(crate) fn set_visible_line_count(
489 &mut self,
490 lines: f64,
491 window: &mut Window,
492 cx: &mut Context<Self>,
493 ) {
494 let opened_first_time = self.scroll_manager.visible_line_count.is_none();
495 self.scroll_manager.visible_line_count = Some(lines);
496 if opened_first_time {
497 self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
498 editor
499 .update_in(cx, |editor, window, cx| {
500 editor.register_visible_buffers(cx);
501 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
502 editor.update_lsp_data(None, window, cx);
503 })
504 .ok();
505 });
506 }
507 }
508
509 pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
510 self.scroll_manager.visible_column_count = Some(columns);
511 }
512
513 pub fn apply_scroll_delta(
514 &mut self,
515 scroll_delta: gpui::Point<f32>,
516 window: &mut Window,
517 cx: &mut Context<Self>,
518 ) {
519 let mut delta = scroll_delta;
520 if self.scroll_manager.forbid_vertical_scroll {
521 delta.y = 0.0;
522 }
523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
524 let position =
525 self.scroll_manager.anchor.scroll_position(&display_map) + delta.map(f64::from);
526 self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
527 }
528
529 pub fn set_scroll_position(
530 &mut self,
531 scroll_position: gpui::Point<ScrollOffset>,
532 window: &mut Window,
533 cx: &mut Context<Self>,
534 ) -> WasScrolled {
535 let mut position = scroll_position;
536 if self.scroll_manager.forbid_vertical_scroll {
537 let current_position = self.scroll_position(cx);
538 position.y = current_position.y;
539 }
540 self.set_scroll_position_internal(position, true, false, window, cx)
541 }
542
543 /// Scrolls so that `row` is at the top of the editor view.
544 pub fn set_scroll_top_row(
545 &mut self,
546 row: DisplayRow,
547 window: &mut Window,
548 cx: &mut Context<Editor>,
549 ) {
550 let snapshot = self.snapshot(window, cx).display_snapshot;
551 let new_screen_top = DisplayPoint::new(row, 0);
552 let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
553 let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
554
555 self.set_scroll_anchor(
556 ScrollAnchor {
557 anchor: new_anchor,
558 offset: Default::default(),
559 },
560 window,
561 cx,
562 );
563 }
564
565 pub(crate) fn set_scroll_position_internal(
566 &mut self,
567 scroll_position: gpui::Point<ScrollOffset>,
568 local: bool,
569 autoscroll: bool,
570 window: &mut Window,
571 cx: &mut Context<Self>,
572 ) -> WasScrolled {
573 let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
574 self.set_scroll_position_taking_display_map(
575 scroll_position,
576 local,
577 autoscroll,
578 map,
579 window,
580 cx,
581 )
582 }
583
584 fn set_scroll_position_taking_display_map(
585 &mut self,
586 scroll_position: gpui::Point<ScrollOffset>,
587 local: bool,
588 autoscroll: bool,
589 display_map: DisplaySnapshot,
590 window: &mut Window,
591 cx: &mut Context<Self>,
592 ) -> WasScrolled {
593 hide_hover(self, cx);
594 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
595
596 self.edit_prediction_preview
597 .set_previous_scroll_position(None);
598
599 let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
600 let current_position = self.scroll_manager.anchor.scroll_position(&display_map);
601 gpui::Point::new(scroll_position.x, current_position.y)
602 } else {
603 scroll_position
604 };
605
606 let editor_was_scrolled = self.scroll_manager.set_scroll_position(
607 adjusted_position,
608 &display_map,
609 local,
610 autoscroll,
611 workspace_id,
612 window,
613 cx,
614 );
615
616 self.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
617 cx.background_executor()
618 .timer(Duration::from_millis(50))
619 .await;
620 editor
621 .update_in(cx, |editor, window, cx| {
622 editor.register_visible_buffers(cx);
623 editor.refresh_colors_for_visible_range(None, window, cx);
624 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
625 })
626 .ok();
627 });
628
629 editor_was_scrolled
630 }
631
632 pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
634 self.scroll_manager.anchor.scroll_position(&display_map)
635 }
636
637 pub fn set_scroll_anchor(
638 &mut self,
639 scroll_anchor: ScrollAnchor,
640 window: &mut Window,
641 cx: &mut Context<Self>,
642 ) {
643 hide_hover(self, cx);
644 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
645 let top_row = scroll_anchor
646 .anchor
647 .to_point(&self.buffer().read(cx).snapshot(cx))
648 .row;
649 self.scroll_manager.set_anchor(
650 scroll_anchor,
651 top_row,
652 true,
653 false,
654 workspace_id,
655 window,
656 cx,
657 );
658 }
659
660 pub(crate) fn set_scroll_anchor_remote(
661 &mut self,
662 scroll_anchor: ScrollAnchor,
663 window: &mut Window,
664 cx: &mut Context<Self>,
665 ) {
666 hide_hover(self, cx);
667 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
668 let snapshot = &self.buffer().read(cx).snapshot(cx);
669 if !scroll_anchor.anchor.is_valid(snapshot) {
670 log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
671 return;
672 }
673 let top_row = scroll_anchor.anchor.to_point(snapshot).row;
674 self.scroll_manager.set_anchor(
675 scroll_anchor,
676 top_row,
677 false,
678 false,
679 workspace_id,
680 window,
681 cx,
682 );
683 }
684
685 pub fn scroll_screen(
686 &mut self,
687 amount: &ScrollAmount,
688 window: &mut Window,
689 cx: &mut Context<Self>,
690 ) {
691 if matches!(self.mode, EditorMode::SingleLine) {
692 cx.propagate();
693 return;
694 }
695
696 if self.take_rename(true, window, cx).is_some() {
697 return;
698 }
699
700 let mut current_position = self.scroll_position(cx);
701 let Some(visible_line_count) = self.visible_line_count() else {
702 return;
703 };
704 let Some(mut visible_column_count) = self.visible_column_count() else {
705 return;
706 };
707
708 // If the user has a preferred line length, and has the editor
709 // configured to wrap at the preferred line length, or bounded to it,
710 // use that value over the visible column count. This was mostly done so
711 // that tests could actually be written for vim's `z l`, `z h`, `z
712 // shift-l` and `z shift-h` commands, as there wasn't a good way to
713 // configure the editor to only display a certain number of columns. If
714 // that ever happens, this could probably be removed.
715 let settings = AllLanguageSettings::get_global(cx);
716 if matches!(
717 settings.defaults.soft_wrap,
718 SoftWrap::PreferredLineLength | SoftWrap::Bounded
719 ) && (settings.defaults.preferred_line_length as f64) < visible_column_count
720 {
721 visible_column_count = settings.defaults.preferred_line_length as f64;
722 }
723
724 // If the scroll position is currently at the left edge of the document
725 // (x == 0.0) and the intent is to scroll right, the gutter's margin
726 // should first be added to the current position, otherwise the cursor
727 // will end at the column position minus the margin, which looks off.
728 if current_position.x == 0.0
729 && amount.columns(visible_column_count) > 0.
730 && let Some(last_position_map) = &self.last_position_map
731 {
732 current_position.x +=
733 f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
734 }
735 let new_position = current_position
736 + point(
737 amount.columns(visible_column_count),
738 amount.lines(visible_line_count),
739 );
740 self.set_scroll_position(new_position, window, cx);
741 }
742
743 /// Returns an ordering. The newest selection is:
744 /// Ordering::Equal => on screen
745 /// Ordering::Less => above or to the left of the screen
746 /// Ordering::Greater => below or to the right of the screen
747 pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
748 let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
749 let newest_head = self
750 .selections
751 .newest_anchor()
752 .head()
753 .to_display_point(&snapshot);
754 let screen_top = self
755 .scroll_manager
756 .anchor
757 .anchor
758 .to_display_point(&snapshot);
759
760 if screen_top > newest_head {
761 return Ordering::Less;
762 }
763
764 if let (Some(visible_lines), Some(visible_columns)) =
765 (self.visible_line_count(), self.visible_column_count())
766 && newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32)
767 && newest_head.column() <= screen_top.column() + visible_columns as u32
768 {
769 return Ordering::Equal;
770 }
771
772 Ordering::Greater
773 }
774
775 pub fn read_scroll_position_from_db(
776 &mut self,
777 item_id: u64,
778 workspace_id: WorkspaceId,
779 window: &mut Window,
780 cx: &mut Context<Editor>,
781 ) {
782 let scroll_position = DB.get_scroll_position(item_id, workspace_id);
783 if let Ok(Some((top_row, x, y))) = scroll_position {
784 let top_anchor = self
785 .buffer()
786 .read(cx)
787 .snapshot(cx)
788 .anchor_before(Point::new(top_row, 0));
789 let scroll_anchor = ScrollAnchor {
790 offset: gpui::Point::new(x, y),
791 anchor: top_anchor,
792 };
793 self.set_scroll_anchor(scroll_anchor, window, cx);
794 }
795 }
796}