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