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