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::{Bias, Point};
17pub use scroll_amount::ScrollAmount;
18use settings::Settings;
19use std::{
20 cmp::Ordering,
21 time::{Duration, Instant},
22};
23use util::ResultExt;
24use workspace::{ItemId, WorkspaceId};
25
26pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
27const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
28
29#[derive(Default)]
30pub struct ScrollbarAutoHide(pub bool);
31
32impl Global for ScrollbarAutoHide {}
33
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub struct ScrollAnchor {
36 pub offset: gpui::Point<f32>,
37 pub anchor: Anchor,
38}
39
40impl ScrollAnchor {
41 pub(super) fn new() -> Self {
42 Self {
43 offset: gpui::Point::default(),
44 anchor: Anchor::min(),
45 }
46 }
47
48 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
49 let mut scroll_position = self.offset;
50 if self.anchor == Anchor::min() {
51 scroll_position.y = 0.;
52 } else {
53 let scroll_top = self.anchor.to_display_point(snapshot).row().as_f32();
54 scroll_position.y += scroll_top;
55 }
56 scroll_position
57 }
58
59 pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
60 self.anchor.to_point(buffer).row
61 }
62}
63
64#[derive(Clone, Copy, Debug)]
65pub struct OngoingScroll {
66 last_event: Instant,
67 axis: Option<Axis>,
68}
69
70impl OngoingScroll {
71 fn new() -> Self {
72 Self {
73 last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
74 axis: None,
75 }
76 }
77
78 pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> {
79 const UNLOCK_PERCENT: f32 = 1.9;
80 const UNLOCK_LOWER_BOUND: Pixels = px(6.);
81 let mut axis = self.axis;
82
83 let x = delta.x.abs();
84 let y = delta.y.abs();
85 let duration = Instant::now().duration_since(self.last_event);
86 if duration > SCROLL_EVENT_SEPARATION {
87 //New ongoing scroll will start, determine axis
88 axis = if x <= y {
89 Some(Axis::Vertical)
90 } else {
91 Some(Axis::Horizontal)
92 };
93 } else if x.max(y) >= UNLOCK_LOWER_BOUND {
94 //Check if the current ongoing will need to unlock
95 match axis {
96 Some(Axis::Vertical) => {
97 if x > y && x >= y * UNLOCK_PERCENT {
98 axis = None;
99 }
100 }
101
102 Some(Axis::Horizontal) => {
103 if y > x && y >= x * UNLOCK_PERCENT {
104 axis = None;
105 }
106 }
107
108 None => {}
109 }
110 }
111
112 match axis {
113 Some(Axis::Vertical) => {
114 *delta = point(px(0.), delta.y);
115 }
116 Some(Axis::Horizontal) => {
117 *delta = point(delta.x, px(0.));
118 }
119 None => {}
120 }
121
122 axis
123 }
124}
125
126pub struct ScrollManager {
127 pub(crate) vertical_scroll_margin: f32,
128 anchor: ScrollAnchor,
129 ongoing: OngoingScroll,
130 autoscroll_request: Option<(Autoscroll, bool)>,
131 last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
132 show_scrollbars: bool,
133 hide_scrollbar_task: Option<Task<()>>,
134 dragging_scrollbar: Option<Axis>,
135 visible_line_count: Option<f32>,
136 forbid_vertical_scroll: bool,
137}
138
139impl ScrollManager {
140 pub fn new(cx: &mut App) -> Self {
141 ScrollManager {
142 vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin,
143 anchor: ScrollAnchor::new(),
144 ongoing: OngoingScroll::new(),
145 autoscroll_request: None,
146 show_scrollbars: true,
147 hide_scrollbar_task: None,
148 dragging_scrollbar: None,
149 last_autoscroll: None,
150 visible_line_count: None,
151 forbid_vertical_scroll: false,
152 }
153 }
154
155 pub fn clone_state(&mut self, other: &Self) {
156 self.anchor = other.anchor;
157 self.ongoing = other.ongoing;
158 }
159
160 pub fn anchor(&self) -> ScrollAnchor {
161 self.anchor
162 }
163
164 pub fn ongoing_scroll(&self) -> OngoingScroll {
165 self.ongoing
166 }
167
168 pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
169 self.ongoing.last_event = Instant::now();
170 self.ongoing.axis = axis;
171 }
172
173 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
174 self.anchor.scroll_position(snapshot)
175 }
176
177 fn set_scroll_position(
178 &mut self,
179 scroll_position: gpui::Point<f32>,
180 map: &DisplaySnapshot,
181 local: bool,
182 autoscroll: bool,
183 workspace_id: Option<WorkspaceId>,
184 window: &mut Window,
185 cx: &mut Context<Editor>,
186 ) {
187 let (new_anchor, top_row) = if scroll_position.y <= 0. {
188 (
189 ScrollAnchor {
190 anchor: Anchor::min(),
191 offset: scroll_position.max(&gpui::Point::default()),
192 },
193 0,
194 )
195 } else {
196 let scroll_top = scroll_position.y;
197 let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
198 ScrollBeyondLastLine::OnePage => scroll_top,
199 ScrollBeyondLastLine::Off => {
200 if let Some(height_in_lines) = self.visible_line_count {
201 let max_row = map.max_point().row().0 as f32;
202 scroll_top.min(max_row - height_in_lines + 1.).max(0.)
203 } else {
204 scroll_top
205 }
206 }
207 ScrollBeyondLastLine::VerticalScrollMargin => {
208 if let Some(height_in_lines) = self.visible_line_count {
209 let max_row = map.max_point().row().0 as f32;
210 scroll_top
211 .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
212 .max(0.)
213 } else {
214 scroll_top
215 }
216 }
217 };
218
219 let scroll_top_buffer_point =
220 DisplayPoint::new(DisplayRow(scroll_top as u32), 0).to_point(map);
221 let top_anchor = map
222 .buffer_snapshot
223 .anchor_at(scroll_top_buffer_point, Bias::Right);
224
225 (
226 ScrollAnchor {
227 anchor: top_anchor,
228 offset: point(
229 scroll_position.x.max(0.),
230 scroll_top - top_anchor.to_display_point(map).row().as_f32(),
231 ),
232 },
233 scroll_top_buffer_point.row,
234 )
235 };
236
237 self.set_anchor(
238 new_anchor,
239 top_row,
240 local,
241 autoscroll,
242 workspace_id,
243 window,
244 cx,
245 );
246 }
247
248 fn set_anchor(
249 &mut self,
250 anchor: ScrollAnchor,
251 top_row: u32,
252 local: bool,
253 autoscroll: bool,
254 workspace_id: Option<WorkspaceId>,
255 window: &mut Window,
256 cx: &mut Context<Editor>,
257 ) {
258 let adjusted_anchor = if self.forbid_vertical_scroll {
259 ScrollAnchor {
260 offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y),
261 anchor: self.anchor.anchor,
262 }
263 } else {
264 anchor
265 };
266
267 self.anchor = adjusted_anchor;
268 cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll });
269 self.show_scrollbars(window, cx);
270 self.autoscroll_request.take();
271 if let Some(workspace_id) = workspace_id {
272 let item_id = cx.entity().entity_id().as_u64() as ItemId;
273
274 cx.foreground_executor()
275 .spawn(async move {
276 log::debug!(
277 "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}"
278 );
279 DB.save_scroll_position(
280 item_id,
281 workspace_id,
282 top_row,
283 anchor.offset.x,
284 anchor.offset.y,
285 )
286 .await
287 .log_err()
288 })
289 .detach()
290 }
291 cx.notify();
292 }
293
294 pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
295 if !self.show_scrollbars {
296 self.show_scrollbars = true;
297 cx.notify();
298 }
299
300 if cx.default_global::<ScrollbarAutoHide>().0 {
301 self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| {
302 cx.background_executor()
303 .timer(SCROLLBAR_SHOW_INTERVAL)
304 .await;
305 editor
306 .update(cx, |editor, cx| {
307 editor.scroll_manager.show_scrollbars = false;
308 cx.notify();
309 })
310 .log_err();
311 }));
312 } else {
313 self.hide_scrollbar_task = None;
314 }
315 }
316
317 pub fn scrollbars_visible(&self) -> bool {
318 self.show_scrollbars
319 }
320
321 pub fn autoscroll_request(&self) -> Option<Autoscroll> {
322 self.autoscroll_request.map(|(autoscroll, _)| autoscroll)
323 }
324
325 pub fn dragging_scrollbar_axis(&self) -> Option<Axis> {
326 self.dragging_scrollbar
327 }
328
329 pub fn any_scrollbar_dragged(&self) -> bool {
330 self.dragging_scrollbar.is_some()
331 }
332
333 pub fn set_dragged_scrollbar_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) {
334 if self.dragging_scrollbar != Some(axis) {
335 self.dragging_scrollbar = Some(axis);
336 cx.notify();
337 }
338 }
339
340 pub fn reset_scrollbar_dragging_state(&mut self, cx: &mut Context<Editor>) {
341 if self.dragging_scrollbar.is_some() {
342 self.dragging_scrollbar = None;
343 cx.notify();
344 }
345 }
346
347 pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
348 if max < self.anchor.offset.x {
349 self.anchor.offset.x = max;
350 true
351 } else {
352 false
353 }
354 }
355
356 pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) {
357 self.forbid_vertical_scroll = forbid;
358 }
359
360 pub fn forbid_vertical_scroll(&self) -> bool {
361 self.forbid_vertical_scroll
362 }
363}
364
365impl Editor {
366 pub fn vertical_scroll_margin(&self) -> usize {
367 self.scroll_manager.vertical_scroll_margin as usize
368 }
369
370 pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
371 self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
372 cx.notify();
373 }
374
375 pub fn visible_line_count(&self) -> Option<f32> {
376 self.scroll_manager.visible_line_count
377 }
378
379 pub fn visible_row_count(&self) -> Option<u32> {
380 self.visible_line_count()
381 .map(|line_count| line_count as u32 - 1)
382 }
383
384 pub(crate) fn set_visible_line_count(
385 &mut self,
386 lines: f32,
387 window: &mut Window,
388 cx: &mut Context<Self>,
389 ) {
390 let opened_first_time = self.scroll_manager.visible_line_count.is_none();
391 self.scroll_manager.visible_line_count = Some(lines);
392 if opened_first_time {
393 cx.spawn_in(window, async move |editor, cx| {
394 editor
395 .update(cx, |editor, cx| {
396 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
397 })
398 .ok()
399 })
400 .detach()
401 }
402 }
403
404 pub fn apply_scroll_delta(
405 &mut self,
406 scroll_delta: gpui::Point<f32>,
407 window: &mut Window,
408 cx: &mut Context<Self>,
409 ) {
410 let mut delta = scroll_delta;
411 if self.scroll_manager.forbid_vertical_scroll {
412 delta.y = 0.0;
413 }
414 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
415 let position = self.scroll_manager.anchor.scroll_position(&display_map) + delta;
416 self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
417 }
418
419 pub fn set_scroll_position(
420 &mut self,
421 scroll_position: gpui::Point<f32>,
422 window: &mut Window,
423 cx: &mut Context<Self>,
424 ) {
425 let mut position = scroll_position;
426 if self.scroll_manager.forbid_vertical_scroll {
427 let current_position = self.scroll_position(cx);
428 position.y = current_position.y;
429 }
430 self.set_scroll_position_internal(position, true, false, window, cx);
431 }
432
433 /// Scrolls so that `row` is at the top of the editor view.
434 pub fn set_scroll_top_row(
435 &mut self,
436 row: DisplayRow,
437 window: &mut Window,
438 cx: &mut Context<Editor>,
439 ) {
440 let snapshot = self.snapshot(window, cx).display_snapshot;
441 let new_screen_top = DisplayPoint::new(row, 0);
442 let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
443 let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
444
445 self.set_scroll_anchor(
446 ScrollAnchor {
447 anchor: new_anchor,
448 offset: Default::default(),
449 },
450 window,
451 cx,
452 );
453 }
454
455 pub(crate) fn set_scroll_position_internal(
456 &mut self,
457 scroll_position: gpui::Point<f32>,
458 local: bool,
459 autoscroll: bool,
460 window: &mut Window,
461 cx: &mut Context<Self>,
462 ) {
463 let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
464 self.set_scroll_position_taking_display_map(
465 scroll_position,
466 local,
467 autoscroll,
468 map,
469 window,
470 cx,
471 );
472 }
473
474 fn set_scroll_position_taking_display_map(
475 &mut self,
476 scroll_position: gpui::Point<f32>,
477 local: bool,
478 autoscroll: bool,
479 display_map: DisplaySnapshot,
480 window: &mut Window,
481 cx: &mut Context<Self>,
482 ) {
483 hide_hover(self, cx);
484 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
485
486 self.edit_prediction_preview
487 .set_previous_scroll_position(None);
488
489 let adjusted_position = if self.scroll_manager.forbid_vertical_scroll {
490 let current_position = self.scroll_manager.anchor.scroll_position(&display_map);
491 gpui::Point::new(scroll_position.x, current_position.y)
492 } else {
493 scroll_position
494 };
495
496 self.scroll_manager.set_scroll_position(
497 adjusted_position,
498 &display_map,
499 local,
500 autoscroll,
501 workspace_id,
502 window,
503 cx,
504 );
505
506 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
507 }
508
509 pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<f32> {
510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
511 self.scroll_manager.anchor.scroll_position(&display_map)
512 }
513
514 pub fn set_scroll_anchor(
515 &mut self,
516 scroll_anchor: ScrollAnchor,
517 window: &mut Window,
518 cx: &mut Context<Self>,
519 ) {
520 hide_hover(self, cx);
521 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
522 let top_row = scroll_anchor
523 .anchor
524 .to_point(&self.buffer().read(cx).snapshot(cx))
525 .row;
526 self.scroll_manager.set_anchor(
527 scroll_anchor,
528 top_row,
529 true,
530 false,
531 workspace_id,
532 window,
533 cx,
534 );
535 }
536
537 pub(crate) fn set_scroll_anchor_remote(
538 &mut self,
539 scroll_anchor: ScrollAnchor,
540 window: &mut Window,
541 cx: &mut Context<Self>,
542 ) {
543 hide_hover(self, cx);
544 let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
545 let snapshot = &self.buffer().read(cx).snapshot(cx);
546 if !scroll_anchor.anchor.is_valid(snapshot) {
547 log::warn!("Invalid scroll anchor: {:?}", scroll_anchor);
548 return;
549 }
550 let top_row = scroll_anchor.anchor.to_point(snapshot).row;
551 self.scroll_manager.set_anchor(
552 scroll_anchor,
553 top_row,
554 false,
555 false,
556 workspace_id,
557 window,
558 cx,
559 );
560 }
561
562 pub fn scroll_screen(
563 &mut self,
564 amount: &ScrollAmount,
565 window: &mut Window,
566 cx: &mut Context<Self>,
567 ) {
568 if matches!(self.mode, EditorMode::SingleLine { .. }) {
569 cx.propagate();
570 return;
571 }
572
573 if self.take_rename(true, window, cx).is_some() {
574 return;
575 }
576
577 let cur_position = self.scroll_position(cx);
578 let Some(visible_line_count) = self.visible_line_count() else {
579 return;
580 };
581 let new_pos = cur_position + point(0., amount.lines(visible_line_count));
582 self.set_scroll_position(new_pos, window, cx);
583 }
584
585 /// Returns an ordering. The newest selection is:
586 /// Ordering::Equal => on screen
587 /// Ordering::Less => above the screen
588 /// Ordering::Greater => below the screen
589 pub fn newest_selection_on_screen(&self, cx: &mut App) -> Ordering {
590 let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
591 let newest_head = self
592 .selections
593 .newest_anchor()
594 .head()
595 .to_display_point(&snapshot);
596 let screen_top = self
597 .scroll_manager
598 .anchor
599 .anchor
600 .to_display_point(&snapshot);
601
602 if screen_top > newest_head {
603 return Ordering::Less;
604 }
605
606 if let Some(visible_lines) = self.visible_line_count() {
607 if newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32) {
608 return Ordering::Equal;
609 }
610 }
611
612 Ordering::Greater
613 }
614
615 pub fn read_scroll_position_from_db(
616 &mut self,
617 item_id: u64,
618 workspace_id: WorkspaceId,
619 window: &mut Window,
620 cx: &mut Context<Editor>,
621 ) {
622 let scroll_position = DB.get_scroll_position(item_id, workspace_id);
623 if let Ok(Some((top_row, x, y))) = scroll_position {
624 let top_anchor = self
625 .buffer()
626 .read(cx)
627 .snapshot(cx)
628 .anchor_at(Point::new(top_row, 0), Bias::Left);
629 let scroll_anchor = ScrollAnchor {
630 offset: gpui::Point::new(x, y),
631 anchor: top_anchor,
632 };
633 self.set_scroll_anchor(scroll_anchor, window, cx);
634 }
635 }
636}