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