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