summaryrefslogtreecommitdiffstats
path: root/src/prompt_toolkit/key_binding/bindings/scroll.py
blob: 83a4be1f8fc5d1856a36836ed0e48e20d2fab4e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"""
Key bindings, for scrolling up and down through pages.

This are separate bindings, because GNU readline doesn't have them, but
they are very useful for navigating through long multiline buffers, like in
Vi, Emacs, etc...
"""
from __future__ import annotations

from prompt_toolkit.key_binding.key_processor import KeyPressEvent

__all__ = [
    "scroll_forward",
    "scroll_backward",
    "scroll_half_page_up",
    "scroll_half_page_down",
    "scroll_one_line_up",
    "scroll_one_line_down",
]

E = KeyPressEvent


def scroll_forward(event: E, half: bool = False) -> None:
    """
    Scroll window down.
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        info = w.render_info
        ui_content = info.ui_content

        # Height to scroll.
        scroll_height = info.window_height
        if half:
            scroll_height //= 2

        # Calculate how many lines is equivalent to that vertical space.
        y = b.document.cursor_position_row + 1
        height = 0
        while y < ui_content.line_count:
            line_height = info.get_height_for_line(y)

            if height + line_height < scroll_height:
                height += line_height
                y += 1
            else:
                break

        b.cursor_position = b.document.translate_row_col_to_index(y, 0)


def scroll_backward(event: E, half: bool = False) -> None:
    """
    Scroll window up.
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        info = w.render_info

        # Height to scroll.
        scroll_height = info.window_height
        if half:
            scroll_height //= 2

        # Calculate how many lines is equivalent to that vertical space.
        y = max(0, b.document.cursor_position_row - 1)
        height = 0
        while y > 0:
            line_height = info.get_height_for_line(y)

            if height + line_height < scroll_height:
                height += line_height
                y -= 1
            else:
                break

        b.cursor_position = b.document.translate_row_col_to_index(y, 0)


def scroll_half_page_down(event: E) -> None:
    """
    Same as ControlF, but only scroll half a page.
    """
    scroll_forward(event, half=True)


def scroll_half_page_up(event: E) -> None:
    """
    Same as ControlB, but only scroll half a page.
    """
    scroll_backward(event, half=True)


def scroll_one_line_down(event: E) -> None:
    """
    scroll_offset += 1
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w:
        # When the cursor is at the top, move to the next line. (Otherwise, only scroll.)
        if w.render_info:
            info = w.render_info

            if w.vertical_scroll < info.content_height - info.window_height:
                if info.cursor_position.y <= info.configured_scroll_offsets.top:
                    b.cursor_position += b.document.get_cursor_down_position()

                w.vertical_scroll += 1


def scroll_one_line_up(event: E) -> None:
    """
    scroll_offset -= 1
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w:
        # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.)
        if w.render_info:
            info = w.render_info

            if w.vertical_scroll > 0:
                first_line_height = info.get_height_for_line(info.first_visible_line())

                cursor_up = info.cursor_position.y - (
                    info.window_height
                    - 1
                    - first_line_height
                    - info.configured_scroll_offsets.bottom
                )

                # Move cursor up, as many steps as the height of the first line.
                # TODO: not entirely correct yet, in case of line wrapping and many long lines.
                for _ in range(max(0, cursor_up)):
                    b.cursor_position += b.document.get_cursor_up_position()

                # Scroll window
                w.vertical_scroll -= 1


def scroll_page_down(event: E) -> None:
    """
    Scroll page down. (Prefer the cursor at the top of the page, after scrolling.)
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        # Scroll down one page.
        line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1)
        w.vertical_scroll = line_index

        b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
        b.cursor_position += b.document.get_start_of_line_position(
            after_whitespace=True
        )


def scroll_page_up(event: E) -> None:
    """
    Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.)
    """
    w = event.app.layout.current_window
    b = event.app.current_buffer

    if w and w.render_info:
        # Put cursor at the first visible line. (But make sure that the cursor
        # moves at least one line up.)
        line_index = max(
            0,
            min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1),
        )

        b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
        b.cursor_position += b.document.get_start_of_line_position(
            after_whitespace=True
        )

        # Set the scroll offset. We can safely set it to zero; the Window will
        # make sure that it scrolls at least until the cursor becomes visible.
        w.vertical_scroll = 0