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