1use std::cmp::Ordering;
2
3use crate::Vim;
4use editor::{display_map::ToDisplayPoint, scroll::scroll_amount::ScrollAmount, Editor};
5use gpui::{actions, AppContext, ViewContext};
6use language::Bias;
7use workspace::Workspace;
8
9actions!(
10 vim,
11 [LineUp, LineDown, ScrollUp, ScrollDown, PageUp, PageDown,]
12);
13
14pub fn init(cx: &mut AppContext) {
15 cx.add_action(|_: &mut Workspace, _: &LineDown, cx| {
16 scroll(cx, |c| ScrollAmount::Line(c.unwrap_or(1.)))
17 });
18 cx.add_action(|_: &mut Workspace, _: &LineUp, cx| {
19 scroll(cx, |c| ScrollAmount::Line(-c.unwrap_or(1.)))
20 });
21 cx.add_action(|_: &mut Workspace, _: &PageDown, cx| {
22 scroll(cx, |c| ScrollAmount::Page(c.unwrap_or(1.)))
23 });
24 cx.add_action(|_: &mut Workspace, _: &PageUp, cx| {
25 scroll(cx, |c| ScrollAmount::Page(-c.unwrap_or(1.)))
26 });
27 cx.add_action(|_: &mut Workspace, _: &ScrollDown, cx| {
28 scroll(cx, |c| {
29 if let Some(c) = c {
30 ScrollAmount::Line(c)
31 } else {
32 ScrollAmount::Page(0.5)
33 }
34 })
35 });
36 cx.add_action(|_: &mut Workspace, _: &ScrollUp, cx| {
37 scroll(cx, |c| {
38 if let Some(c) = c {
39 ScrollAmount::Line(-c)
40 } else {
41 ScrollAmount::Page(-0.5)
42 }
43 })
44 });
45}
46
47fn scroll(cx: &mut ViewContext<Workspace>, by: fn(c: Option<f32>) -> ScrollAmount) {
48 Vim::update(cx, |vim, cx| {
49 let amount = by(vim.pop_number_operator(cx).map(|c| c as f32));
50 vim.update_active_editor(cx, |editor, cx| scroll_editor(editor, &amount, cx));
51 })
52}
53
54fn scroll_editor(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
55 let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
56 editor.scroll_screen(amount, cx);
57 if should_move_cursor {
58 let selection_ordering = editor.newest_selection_on_screen(cx);
59 if selection_ordering.is_eq() {
60 return;
61 }
62
63 let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
64 visible_rows as u32
65 } else {
66 return;
67 };
68
69 let top_anchor = editor.scroll_manager.anchor().anchor;
70
71 editor.change_selections(None, cx, |s| {
72 s.replace_cursors_with(|snapshot| {
73 let mut new_point = top_anchor.to_display_point(&snapshot);
74
75 match selection_ordering {
76 Ordering::Less => {
77 new_point = snapshot.clip_point(new_point, Bias::Right);
78 }
79 Ordering::Greater => {
80 *new_point.row_mut() += visible_rows - 1;
81 new_point = snapshot.clip_point(new_point, Bias::Left);
82 }
83 Ordering::Equal => unreachable!(),
84 }
85
86 vec![new_point]
87 })
88 });
89 }
90}
91
92#[cfg(test)]
93mod test {
94 use crate::{state::Mode, test::VimTestContext};
95 use gpui::geometry::vector::vec2f;
96 use indoc::indoc;
97
98 #[gpui::test]
99 async fn test_scroll(cx: &mut gpui::TestAppContext) {
100 let mut cx = VimTestContext::new(cx, true).await;
101
102 cx.set_state(indoc! {"ˇa\nb\nc\nd\ne\n"}, Mode::Normal);
103
104 cx.update_editor(|editor, cx| {
105 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 0.))
106 });
107 cx.simulate_keystrokes(["ctrl-e"]);
108 cx.update_editor(|editor, cx| {
109 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 1.))
110 });
111 cx.simulate_keystrokes(["2", "ctrl-e"]);
112 cx.update_editor(|editor, cx| {
113 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 3.))
114 });
115 cx.simulate_keystrokes(["ctrl-y"]);
116 cx.update_editor(|editor, cx| {
117 assert_eq!(editor.snapshot(cx).scroll_position(), vec2f(0., 2.))
118 });
119 }
120}