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