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