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 Axis, MutableAppContext, Task, ViewContext,
13};
14use language::Bias;
15
16use crate::{
17 display_map::{DisplaySnapshot, ToDisplayPoint},
18 hover_popover::hide_hover,
19 Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint,
20};
21
22use self::{
23 autoscroll::{Autoscroll, AutoscrollStrategy},
24 scroll_amount::ScrollAmount,
25};
26
27pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
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: Vector2F,
36 pub top_anchor: Anchor,
37}
38
39impl ScrollAnchor {
40 fn new() -> Self {
41 Self {
42 offset: Vector2F::zero(),
43 top_anchor: Anchor::min(),
44 }
45 }
46
47 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
48 let mut scroll_position = self.offset;
49 if self.top_anchor != Anchor::min() {
50 let scroll_top = self.top_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.top_anchor.to_point(buffer).row
60 }
61}
62
63#[derive(Clone, Copy, Debug)]
64pub struct OngoingScroll {
65 last_event: Instant,
66 axis: Option<Axis>,
67}
68
69impl OngoingScroll {
70 fn new() -> Self {
71 Self {
72 last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
73 axis: None,
74 }
75 }
76
77 pub fn filter(&self, delta: &mut Vector2F) -> Option<Axis> {
78 const UNLOCK_PERCENT: f32 = 1.9;
79 const UNLOCK_LOWER_BOUND: f32 = 6.;
80 let mut axis = self.axis;
81
82 let x = delta.x().abs();
83 let y = delta.y().abs();
84 let duration = Instant::now().duration_since(self.last_event);
85 if duration > SCROLL_EVENT_SEPARATION {
86 //New ongoing scroll will start, determine axis
87 axis = if x <= y {
88 Some(Axis::Vertical)
89 } else {
90 Some(Axis::Horizontal)
91 };
92 } else if x.max(y) >= UNLOCK_LOWER_BOUND {
93 //Check if the current ongoing will need to unlock
94 match axis {
95 Some(Axis::Vertical) => {
96 if x > y && x >= y * UNLOCK_PERCENT {
97 axis = None;
98 }
99 }
100
101 Some(Axis::Horizontal) => {
102 if y > x && y >= x * UNLOCK_PERCENT {
103 axis = None;
104 }
105 }
106
107 None => {}
108 }
109 }
110
111 match axis {
112 Some(Axis::Vertical) => *delta = vec2f(0., delta.y()),
113 Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
114 None => {}
115 }
116
117 axis
118 }
119}
120
121pub struct ScrollManager {
122 vertical_scroll_margin: f32,
123 anchor: ScrollAnchor,
124 ongoing: OngoingScroll,
125 autoscroll_request: Option<(Autoscroll, bool)>,
126 last_autoscroll: Option<(Vector2F, f32, f32, AutoscrollStrategy)>,
127 show_scrollbars: bool,
128 hide_scrollbar_task: Option<Task<()>>,
129 visible_line_count: Option<f32>,
130}
131
132impl ScrollManager {
133 pub fn new() -> Self {
134 ScrollManager {
135 vertical_scroll_margin: 3.0,
136 anchor: ScrollAnchor::new(),
137 ongoing: OngoingScroll::new(),
138 autoscroll_request: None,
139 show_scrollbars: true,
140 hide_scrollbar_task: None,
141 last_autoscroll: None,
142 visible_line_count: None,
143 }
144 }
145
146 pub fn clone_state(&mut self, other: &Self) {
147 self.anchor = other.anchor;
148 self.ongoing = other.ongoing;
149 }
150
151 pub fn anchor(&self) -> ScrollAnchor {
152 self.anchor
153 }
154
155 pub fn ongoing_scroll(&self) -> OngoingScroll {
156 self.ongoing
157 }
158
159 pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
160 self.ongoing.last_event = Instant::now();
161 self.ongoing.axis = axis;
162 }
163
164 pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
165 self.anchor.scroll_position(snapshot)
166 }
167
168 fn set_scroll_position(
169 &mut self,
170 scroll_position: Vector2F,
171 map: &DisplaySnapshot,
172 local: bool,
173 cx: &mut ViewContext<Editor>,
174 ) {
175 let new_anchor = if scroll_position.y() <= 0. {
176 ScrollAnchor {
177 top_anchor: Anchor::min(),
178 offset: scroll_position.max(vec2f(0., 0.)),
179 }
180 } else {
181 let scroll_top_buffer_offset =
182 DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right);
183 let top_anchor = map
184 .buffer_snapshot
185 .anchor_at(scroll_top_buffer_offset, Bias::Right);
186
187 ScrollAnchor {
188 top_anchor,
189 offset: vec2f(
190 scroll_position.x(),
191 scroll_position.y() - top_anchor.to_display_point(&map).row() as f32,
192 ),
193 }
194 };
195
196 self.set_anchor(new_anchor, local, cx);
197 }
198
199 fn set_anchor(&mut self, anchor: ScrollAnchor, local: bool, cx: &mut ViewContext<Editor>) {
200 self.anchor = anchor;
201 cx.emit(Event::ScrollPositionChanged { local });
202 self.show_scrollbar(cx);
203 self.autoscroll_request.take();
204 cx.notify();
205 }
206
207 pub fn show_scrollbar(&mut self, cx: &mut ViewContext<Editor>) {
208 if !self.show_scrollbars {
209 self.show_scrollbars = true;
210 cx.notify();
211 }
212
213 if cx.default_global::<ScrollbarAutoHide>().0 {
214 self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
215 cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
216 if let Some(editor) = editor.upgrade(&cx) {
217 editor.update(&mut cx, |editor, cx| {
218 editor.scroll_manager.show_scrollbars = false;
219 cx.notify();
220 });
221 }
222 }));
223 } else {
224 self.hide_scrollbar_task = None;
225 }
226 }
227
228 pub fn scrollbars_visible(&self) -> bool {
229 self.show_scrollbars
230 }
231
232 pub fn has_autoscroll_request(&self) -> bool {
233 self.autoscroll_request.is_some()
234 }
235
236 pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
237 if max < self.anchor.offset.x() {
238 self.anchor.offset.set_x(max);
239 true
240 } else {
241 false
242 }
243 }
244}
245
246impl Editor {
247 pub fn vertical_scroll_margin(&mut self) -> usize {
248 self.scroll_manager.vertical_scroll_margin as usize
249 }
250
251 pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
252 self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
253 cx.notify();
254 }
255
256 pub fn visible_line_count(&self) -> Option<f32> {
257 self.scroll_manager.visible_line_count
258 }
259
260 pub(crate) fn set_visible_line_count(&mut self, lines: f32) {
261 self.scroll_manager.visible_line_count = Some(lines)
262 }
263
264 pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
265 self.set_scroll_position_internal(scroll_position, true, cx);
266 }
267
268 pub(crate) fn set_scroll_position_internal(
269 &mut self,
270 scroll_position: Vector2F,
271 local: bool,
272 cx: &mut ViewContext<Self>,
273 ) {
274 let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
275
276 hide_hover(self, cx);
277 self.scroll_manager
278 .set_scroll_position(scroll_position, &map, local, cx);
279 }
280
281 pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
283 self.scroll_manager.anchor.scroll_position(&display_map)
284 }
285
286 pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
287 hide_hover(self, cx);
288 self.scroll_manager.set_anchor(scroll_anchor, true, cx);
289 }
290
291 pub(crate) fn set_scroll_anchor_remote(
292 &mut self,
293 scroll_anchor: ScrollAnchor,
294 cx: &mut ViewContext<Self>,
295 ) {
296 hide_hover(self, cx);
297 self.scroll_manager.set_anchor(scroll_anchor, false, cx);
298 }
299
300 pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
301 if matches!(self.mode, EditorMode::SingleLine) {
302 cx.propagate_action();
303 return;
304 }
305
306 if self.take_rename(true, cx).is_some() {
307 return;
308 }
309
310 if amount.move_context_menu_selection(self, cx) {
311 return;
312 }
313
314 let cur_position = self.scroll_position(cx);
315 let new_pos = cur_position + vec2f(0., amount.lines(self) - 1.);
316 self.set_scroll_position(new_pos, cx);
317 }
318
319 /// Returns an ordering. The newest selection is:
320 /// Ordering::Equal => on screen
321 /// Ordering::Less => above the screen
322 /// Ordering::Greater => below the screen
323 pub fn newest_selection_on_screen(&self, cx: &mut MutableAppContext) -> Ordering {
324 let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
325 let newest_head = self
326 .selections
327 .newest_anchor()
328 .head()
329 .to_display_point(&snapshot);
330 let screen_top = self
331 .scroll_manager
332 .anchor
333 .top_anchor
334 .to_display_point(&snapshot);
335
336 if screen_top > newest_head {
337 return Ordering::Less;
338 }
339
340 if let Some(visible_lines) = self.visible_line_count() {
341 if newest_head.row() < screen_top.row() + visible_lines as u32 {
342 return Ordering::Equal;
343 }
344 }
345
346 Ordering::Greater
347 }
348}