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