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