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