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