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