1"""
2Key bindings, for scrolling up and down through pages.
3
4This are separate bindings, because GNU readline doesn't have them, but
5they are very useful for navigating through long multiline buffers, like in
6Vi, Emacs, etc...
7"""
8
9from __future__ import annotations
10
11from prompt_toolkit.key_binding.key_processor import KeyPressEvent
12
13__all__ = [
14 "scroll_forward",
15 "scroll_backward",
16 "scroll_half_page_up",
17 "scroll_half_page_down",
18 "scroll_one_line_up",
19 "scroll_one_line_down",
20]
21
22E = KeyPressEvent
23
24
25def scroll_forward(event: E, half: bool = False) -> None:
26 """
27 Scroll window down.
28 """
29 w = event.app.layout.current_window
30 b = event.app.current_buffer
31
32 if w and w.render_info:
33 info = w.render_info
34 ui_content = info.ui_content
35
36 # Height to scroll.
37 scroll_height = info.window_height
38 if half:
39 scroll_height //= 2
40
41 # Calculate how many lines is equivalent to that vertical space.
42 y = b.document.cursor_position_row + 1
43 height = 0
44 while y < ui_content.line_count:
45 line_height = info.get_height_for_line(y)
46
47 if height + line_height < scroll_height:
48 height += line_height
49 y += 1
50 else:
51 break
52
53 b.cursor_position = b.document.translate_row_col_to_index(y, 0)
54
55
56def scroll_backward(event: E, half: bool = False) -> None:
57 """
58 Scroll window up.
59 """
60 w = event.app.layout.current_window
61 b = event.app.current_buffer
62
63 if w and w.render_info:
64 info = w.render_info
65
66 # Height to scroll.
67 scroll_height = info.window_height
68 if half:
69 scroll_height //= 2
70
71 # Calculate how many lines is equivalent to that vertical space.
72 y = max(0, b.document.cursor_position_row - 1)
73 height = 0
74 while y > 0:
75 line_height = info.get_height_for_line(y)
76
77 if height + line_height < scroll_height:
78 height += line_height
79 y -= 1
80 else:
81 break
82
83 b.cursor_position = b.document.translate_row_col_to_index(y, 0)
84
85
86def scroll_half_page_down(event: E) -> None:
87 """
88 Same as ControlF, but only scroll half a page.
89 """
90 scroll_forward(event, half=True)
91
92
93def scroll_half_page_up(event: E) -> None:
94 """
95 Same as ControlB, but only scroll half a page.
96 """
97 scroll_backward(event, half=True)
98
99
100def scroll_one_line_down(event: E) -> None:
101 """
102 scroll_offset += 1
103 """
104 w = event.app.layout.current_window
105 b = event.app.current_buffer
106
107 if w:
108 # When the cursor is at the top, move to the next line. (Otherwise, only scroll.)
109 if w.render_info:
110 info = w.render_info
111
112 if w.vertical_scroll < info.content_height - info.window_height:
113 if info.cursor_position.y <= info.configured_scroll_offsets.top:
114 b.cursor_position += b.document.get_cursor_down_position()
115
116 w.vertical_scroll += 1
117
118
119def scroll_one_line_up(event: E) -> None:
120 """
121 scroll_offset -= 1
122 """
123 w = event.app.layout.current_window
124 b = event.app.current_buffer
125
126 if w:
127 # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.)
128 if w.render_info:
129 info = w.render_info
130
131 if w.vertical_scroll > 0:
132 first_line_height = info.get_height_for_line(info.first_visible_line())
133
134 cursor_up = info.cursor_position.y - (
135 info.window_height
136 - 1
137 - first_line_height
138 - info.configured_scroll_offsets.bottom
139 )
140
141 # Move cursor up, as many steps as the height of the first line.
142 # TODO: not entirely correct yet, in case of line wrapping and many long lines.
143 for _ in range(max(0, cursor_up)):
144 b.cursor_position += b.document.get_cursor_up_position()
145
146 # Scroll window
147 w.vertical_scroll -= 1
148
149
150def scroll_page_down(event: E) -> None:
151 """
152 Scroll page down. (Prefer the cursor at the top of the page, after scrolling.)
153 """
154 w = event.app.layout.current_window
155 b = event.app.current_buffer
156
157 if w and w.render_info:
158 # Scroll down one page.
159 line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1)
160 w.vertical_scroll = line_index
161
162 b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
163 b.cursor_position += b.document.get_start_of_line_position(
164 after_whitespace=True
165 )
166
167
168def scroll_page_up(event: E) -> None:
169 """
170 Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.)
171 """
172 w = event.app.layout.current_window
173 b = event.app.current_buffer
174
175 if w and w.render_info:
176 # Put cursor at the first visible line. (But make sure that the cursor
177 # moves at least one line up.)
178 line_index = max(
179 0,
180 min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1),
181 )
182
183 b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
184 b.cursor_position += b.document.get_start_of_line_position(
185 after_whitespace=True
186 )
187
188 # Set the scroll offset. We can safely set it to zero; the Window will
189 # make sure that it scrolls at least until the cursor becomes visible.
190 w.vertical_scroll = 0