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 persistence::DB,
22 Anchor, DisplayPoint, Editor, EditorMode, Event, InlayHintRefreshReason, MultiBufferSnapshot,
23 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);
32pub const VERTICAL_SCROLL_MARGIN: f32 = 3.;
33const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
34
35#[derive(Default)]
36pub struct ScrollbarAutoHide(pub bool);
37
38#[derive(Clone, Copy, Debug, PartialEq)]
39pub struct ScrollAnchor {
40 pub offset: Vector2F,
41 pub anchor: Anchor,
42}
43
44impl ScrollAnchor {
45 fn new() -> Self {
46 Self {
47 offset: Vector2F::zero(),
48 anchor: Anchor::min(),
49 }
50 }
51
52 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
53 let mut scroll_position = self.offset;
54 if self.anchor != Anchor::min() {
55 let scroll_top = self.anchor.to_display_point(snapshot).row() as f32;
56 scroll_position.set_y(scroll_top + scroll_position.y());
57 } else {
58 scroll_position.set_y(0.);
59 }
60 scroll_position
61 }
62
63 pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
64 self.anchor.to_point(buffer).row
65 }
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 Vector2F) -> Option<Axis> {
83 const UNLOCK_PERCENT: f32 = 1.9;
84 const UNLOCK_LOWER_BOUND: f32 = 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) => *delta = vec2f(0., delta.y()),
118 Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
119 None => {}
120 }
121
122 axis
123 }
124}
125
126pub struct ScrollManager {
127 vertical_scroll_margin: f32,
128 anchor: ScrollAnchor,
129 ongoing: OngoingScroll,
130 autoscroll_request: Option<(Autoscroll, bool)>,
131 last_autoscroll: Option<(Vector2F, f32, f32, AutoscrollStrategy)>,
132 show_scrollbars: bool,
133 hide_scrollbar_task: Option<Task<()>>,
134 visible_line_count: Option<f32>,
135}
136
137impl ScrollManager {
138 pub fn new() -> Self {
139 ScrollManager {
140 vertical_scroll_margin: VERTICAL_SCROLL_MARGIN,
141 anchor: ScrollAnchor::new(),
142 ongoing: OngoingScroll::new(),
143 autoscroll_request: None,
144 show_scrollbars: true,
145 hide_scrollbar_task: None,
146 last_autoscroll: None,
147 visible_line_count: None,
148 }
149 }
150
151 pub fn clone_state(&mut self, other: &Self) {
152 self.anchor = other.anchor;
153 self.ongoing = other.ongoing;
154 }
155
156 pub fn anchor(&self) -> ScrollAnchor {
157 self.anchor
158 }
159
160 pub fn ongoing_scroll(&self) -> OngoingScroll {
161 self.ongoing
162 }
163
164 pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
165 self.ongoing.last_event = Instant::now();
166 self.ongoing.axis = axis;
167 }
168
169 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
170 self.anchor.scroll_position(snapshot)
171 }
172
173 fn set_scroll_position(
174 &mut self,
175 scroll_position: Vector2F,
176 map: &DisplaySnapshot,
177 local: bool,
178 autoscroll: bool,
179 workspace_id: Option<i64>,
180 cx: &mut ViewContext<Editor>,
181 ) {
182 let (new_anchor, top_row) = if scroll_position.y() <= 0. {
183 (
184 ScrollAnchor {
185 anchor: Anchor::min(),
186 offset: scroll_position.max(vec2f(0., 0.)),
187 },
188 0,
189 )
190 } else {
191 let scroll_top_buffer_point =
192 DisplayPoint::new(scroll_position.y() as u32, 0).to_point(&map);
193 let top_anchor = map
194 .buffer_snapshot
195 .anchor_at(scroll_top_buffer_point, Bias::Right);
196
197 (
198 ScrollAnchor {
199 anchor: top_anchor,
200 offset: vec2f(
201 scroll_position.x(),
202 scroll_position.y() - top_anchor.to_display_point(&map).row() as f32,
203 ),
204 },
205 scroll_top_buffer_point.row,
206 )
207 };
208
209 self.set_anchor(new_anchor, top_row, local, autoscroll, workspace_id, cx);
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, cx: &mut ViewContext<Self>) {
299 let opened_first_time = self.scroll_manager.visible_line_count.is_none();
300 self.scroll_manager.visible_line_count = Some(lines);
301 if opened_first_time {
302 cx.spawn(|editor, mut cx| async move {
303 editor
304 .update(&mut cx, |editor, cx| {
305 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx)
306 })
307 .ok()
308 })
309 .detach()
310 }
311 }
312
313 pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
314 self.set_scroll_position_internal(scroll_position, true, false, cx);
315 }
316
317 pub(crate) fn set_scroll_position_internal(
318 &mut self,
319 scroll_position: Vector2F,
320 local: bool,
321 autoscroll: bool,
322 cx: &mut ViewContext<Self>,
323 ) {
324 let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
325
326 hide_hover(self, cx);
327 let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
328 self.scroll_manager.set_scroll_position(
329 scroll_position,
330 &map,
331 local,
332 autoscroll,
333 workspace_id,
334 cx,
335 );
336
337 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
338 }
339
340 pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
342 self.scroll_manager.anchor.scroll_position(&display_map)
343 }
344
345 pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
346 hide_hover(self, cx);
347 let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
348 let top_row = scroll_anchor
349 .anchor
350 .to_point(&self.buffer().read(cx).snapshot(cx))
351 .row;
352 self.scroll_manager
353 .set_anchor(scroll_anchor, top_row, true, false, workspace_id, cx);
354 }
355
356 pub(crate) fn set_scroll_anchor_remote(
357 &mut self,
358 scroll_anchor: ScrollAnchor,
359 cx: &mut ViewContext<Self>,
360 ) {
361 hide_hover(self, cx);
362 let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
363 let top_row = scroll_anchor
364 .anchor
365 .to_point(&self.buffer().read(cx).snapshot(cx))
366 .row;
367 self.scroll_manager
368 .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx);
369 }
370
371 pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
372 if matches!(self.mode, EditorMode::SingleLine) {
373 cx.propagate_action();
374 return;
375 }
376
377 if self.take_rename(true, cx).is_some() {
378 return;
379 }
380
381 let cur_position = self.scroll_position(cx);
382 let new_pos = cur_position + vec2f(0., amount.lines(self));
383 self.set_scroll_position(new_pos, cx);
384 }
385
386 /// Returns an ordering. The newest selection is:
387 /// Ordering::Equal => on screen
388 /// Ordering::Less => above the screen
389 /// Ordering::Greater => below the screen
390 pub fn newest_selection_on_screen(&self, cx: &mut AppContext) -> Ordering {
391 let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
392 let newest_head = self
393 .selections
394 .newest_anchor()
395 .head()
396 .to_display_point(&snapshot);
397 let screen_top = self
398 .scroll_manager
399 .anchor
400 .anchor
401 .to_display_point(&snapshot);
402
403 if screen_top > newest_head {
404 return Ordering::Less;
405 }
406
407 if let Some(visible_lines) = self.visible_line_count() {
408 if newest_head.row() < screen_top.row() + visible_lines as u32 {
409 return Ordering::Equal;
410 }
411 }
412
413 Ordering::Greater
414 }
415
416 pub fn read_scroll_position_from_db(
417 &mut self,
418 item_id: usize,
419 workspace_id: WorkspaceId,
420 cx: &mut ViewContext<Editor>,
421 ) {
422 let scroll_position = DB.get_scroll_position(item_id, workspace_id);
423 if let Ok(Some((top_row, x, y))) = scroll_position {
424 let top_anchor = self
425 .buffer()
426 .read(cx)
427 .snapshot(cx)
428 .anchor_at(Point::new(top_row as u32, 0), Bias::Left);
429 let scroll_anchor = ScrollAnchor {
430 offset: Vector2F::new(x, y),
431 anchor: top_anchor,
432 };
433 self.set_scroll_anchor(scroll_anchor, cx);
434 }
435 }
436}