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