1# pylint: disable=function-redefined 
    2from __future__ import annotations 
    3 
    4import codecs 
    5import string 
    6from enum import Enum 
    7from itertools import accumulate 
    8from typing import Callable, Iterable, Tuple, TypeVar 
    9 
    10from prompt_toolkit.application.current import get_app 
    11from prompt_toolkit.buffer import Buffer, indent, reshape_text, unindent 
    12from prompt_toolkit.clipboard import ClipboardData 
    13from prompt_toolkit.document import Document 
    14from prompt_toolkit.filters import ( 
    15    Always, 
    16    Condition, 
    17    Filter, 
    18    has_arg, 
    19    is_read_only, 
    20    is_searching, 
    21) 
    22from prompt_toolkit.filters.app import ( 
    23    in_paste_mode, 
    24    is_multiline, 
    25    vi_digraph_mode, 
    26    vi_insert_mode, 
    27    vi_insert_multiple_mode, 
    28    vi_mode, 
    29    vi_navigation_mode, 
    30    vi_recording_macro, 
    31    vi_replace_mode, 
    32    vi_replace_single_mode, 
    33    vi_search_direction_reversed, 
    34    vi_selection_mode, 
    35    vi_waiting_for_text_object_mode, 
    36) 
    37from prompt_toolkit.input.vt100_parser import Vt100Parser 
    38from prompt_toolkit.key_binding.digraphs import DIGRAPHS 
    39from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent 
    40from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode 
    41from prompt_toolkit.keys import Keys 
    42from prompt_toolkit.search import SearchDirection 
    43from prompt_toolkit.selection import PasteMode, SelectionState, SelectionType 
    44 
    45from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase 
    46from .named_commands import get_by_name 
    47 
    48__all__ = [ 
    49    "load_vi_bindings", 
    50    "load_vi_search_bindings", 
    51] 
    52 
    53E = KeyPressEvent 
    54 
    55ascii_lowercase = string.ascii_lowercase 
    56 
    57vi_register_names = ascii_lowercase + "0123456789" 
    58 
    59 
    60class TextObjectType(Enum): 
    61    EXCLUSIVE = "EXCLUSIVE" 
    62    INCLUSIVE = "INCLUSIVE" 
    63    LINEWISE = "LINEWISE" 
    64    BLOCK = "BLOCK" 
    65 
    66 
    67class TextObject: 
    68    """ 
    69    Return struct for functions wrapped in ``text_object``. 
    70    Both `start` and `end` are relative to the current cursor position. 
    71    """ 
    72 
    73    def __init__( 
    74        self, start: int, end: int = 0, type: TextObjectType = TextObjectType.EXCLUSIVE 
    75    ): 
    76        self.start = start 
    77        self.end = end 
    78        self.type = type 
    79 
    80    @property 
    81    def selection_type(self) -> SelectionType: 
    82        if self.type == TextObjectType.LINEWISE: 
    83            return SelectionType.LINES 
    84        if self.type == TextObjectType.BLOCK: 
    85            return SelectionType.BLOCK 
    86        else: 
    87            return SelectionType.CHARACTERS 
    88 
    89    def sorted(self) -> tuple[int, int]: 
    90        """ 
    91        Return a (start, end) tuple where start <= end. 
    92        """ 
    93        if self.start < self.end: 
    94            return self.start, self.end 
    95        else: 
    96            return self.end, self.start 
    97 
    98    def operator_range(self, document: Document) -> tuple[int, int]: 
    99        """ 
    100        Return a (start, end) tuple with start <= end that indicates the range 
    101        operators should operate on. 
    102        `buffer` is used to get start and end of line positions. 
    103 
    104        This should return something that can be used in a slice, so the `end` 
    105        position is *not* included. 
    106        """ 
    107        start, end = self.sorted() 
    108        doc = document 
    109 
    110        if ( 
    111            self.type == TextObjectType.EXCLUSIVE 
    112            and doc.translate_index_to_position(end + doc.cursor_position)[1] == 0 
    113        ): 
    114            # If the motion is exclusive and the end of motion is on the first 
    115            # column, the end position becomes end of previous line. 
    116            end -= 1 
    117        if self.type == TextObjectType.INCLUSIVE: 
    118            end += 1 
    119        if self.type == TextObjectType.LINEWISE: 
    120            # Select whole lines 
    121            row, col = doc.translate_index_to_position(start + doc.cursor_position) 
    122            start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position 
    123            row, col = doc.translate_index_to_position(end + doc.cursor_position) 
    124            end = ( 
    125                doc.translate_row_col_to_index(row, len(doc.lines[row])) 
    126                - doc.cursor_position 
    127            ) 
    128        return start, end 
    129 
    130    def get_line_numbers(self, buffer: Buffer) -> tuple[int, int]: 
    131        """ 
    132        Return a (start_line, end_line) pair. 
    133        """ 
    134        # Get absolute cursor positions from the text object. 
    135        from_, to = self.operator_range(buffer.document) 
    136        from_ += buffer.cursor_position 
    137        to += buffer.cursor_position 
    138 
    139        # Take the start of the lines. 
    140        from_, _ = buffer.document.translate_index_to_position(from_) 
    141        to, _ = buffer.document.translate_index_to_position(to) 
    142 
    143        return from_, to 
    144 
    145    def cut(self, buffer: Buffer) -> tuple[Document, ClipboardData]: 
    146        """ 
    147        Turn text object into `ClipboardData` instance. 
    148        """ 
    149        from_, to = self.operator_range(buffer.document) 
    150 
    151        from_ += buffer.cursor_position 
    152        to += buffer.cursor_position 
    153 
    154        # For Vi mode, the SelectionState does include the upper position, 
    155        # while `self.operator_range` does not. So, go one to the left, unless 
    156        # we're in the line mode, then we don't want to risk going to the 
    157        # previous line, and missing one line in the selection. 
    158        if self.type != TextObjectType.LINEWISE: 
    159            to -= 1 
    160 
    161        document = Document( 
    162            buffer.text, 
    163            to, 
    164            SelectionState(original_cursor_position=from_, type=self.selection_type), 
    165        ) 
    166 
    167        new_document, clipboard_data = document.cut_selection() 
    168        return new_document, clipboard_data 
    169 
    170 
    171# Typevar for any text object function: 
    172TextObjectFunction = Callable[[E], TextObject] 
    173_TOF = TypeVar("_TOF", bound=TextObjectFunction) 
    174 
    175 
    176def create_text_object_decorator( 
    177    key_bindings: KeyBindings, 
    178) -> Callable[..., Callable[[_TOF], _TOF]]: 
    179    """ 
    180    Create a decorator that can be used to register Vi text object implementations. 
    181    """ 
    182 
    183    def text_object_decorator( 
    184        *keys: Keys | str, 
    185        filter: Filter = Always(), 
    186        no_move_handler: bool = False, 
    187        no_selection_handler: bool = False, 
    188        eager: bool = False, 
    189    ) -> Callable[[_TOF], _TOF]: 
    190        """ 
    191        Register a text object function. 
    192 
    193        Usage:: 
    194 
    195            @text_object('w', filter=..., no_move_handler=False) 
    196            def handler(event): 
    197                # Return a text object for this key. 
    198                return TextObject(...) 
    199 
    200        :param no_move_handler: Disable the move handler in navigation mode. 
    201            (It's still active in selection mode.) 
    202        """ 
    203 
    204        def decorator(text_object_func: _TOF) -> _TOF: 
    205            @key_bindings.add( 
    206                *keys, filter=vi_waiting_for_text_object_mode & filter, eager=eager 
    207            ) 
    208            def _apply_operator_to_text_object(event: E) -> None: 
    209                # Arguments are multiplied. 
    210                vi_state = event.app.vi_state 
    211                event._arg = str((vi_state.operator_arg or 1) * (event.arg or 1)) 
    212 
    213                # Call the text object handler. 
    214                text_obj = text_object_func(event) 
    215 
    216                # Get the operator function. 
    217                # (Should never be None here, given the 
    218                # `vi_waiting_for_text_object_mode` filter state.) 
    219                operator_func = vi_state.operator_func 
    220 
    221                if text_obj is not None and operator_func is not None: 
    222                    # Call the operator function with the text object. 
    223                    operator_func(event, text_obj) 
    224 
    225                # Clear operator. 
    226                event.app.vi_state.operator_func = None 
    227                event.app.vi_state.operator_arg = None 
    228 
    229            # Register a move operation. (Doesn't need an operator.) 
    230            if not no_move_handler: 
    231 
    232                @key_bindings.add( 
    233                    *keys, 
    234                    filter=~vi_waiting_for_text_object_mode 
    235                    & filter 
    236                    & vi_navigation_mode, 
    237                    eager=eager, 
    238                ) 
    239                def _move_in_navigation_mode(event: E) -> None: 
    240                    """ 
    241                    Move handler for navigation mode. 
    242                    """ 
    243                    text_object = text_object_func(event) 
    244                    event.current_buffer.cursor_position += text_object.start 
    245 
    246            # Register a move selection operation. 
    247            if not no_selection_handler: 
    248 
    249                @key_bindings.add( 
    250                    *keys, 
    251                    filter=~vi_waiting_for_text_object_mode 
    252                    & filter 
    253                    & vi_selection_mode, 
    254                    eager=eager, 
    255                ) 
    256                def _move_in_selection_mode(event: E) -> None: 
    257                    """ 
    258                    Move handler for selection mode. 
    259                    """ 
    260                    text_object = text_object_func(event) 
    261                    buff = event.current_buffer 
    262                    selection_state = buff.selection_state 
    263 
    264                    if selection_state is None: 
    265                        return  # Should not happen, because of the `vi_selection_mode` filter. 
    266 
    267                    # When the text object has both a start and end position, like 'i(' or 'iw', 
    268                    # Turn this into a selection, otherwise the cursor. 
    269                    if text_object.end: 
    270                        # Take selection positions from text object. 
    271                        start, end = text_object.operator_range(buff.document) 
    272                        start += buff.cursor_position 
    273                        end += buff.cursor_position 
    274 
    275                        selection_state.original_cursor_position = start 
    276                        buff.cursor_position = end 
    277 
    278                        # Take selection type from text object. 
    279                        if text_object.type == TextObjectType.LINEWISE: 
    280                            selection_state.type = SelectionType.LINES 
    281                        else: 
    282                            selection_state.type = SelectionType.CHARACTERS 
    283                    else: 
    284                        event.current_buffer.cursor_position += text_object.start 
    285 
    286            # Make it possible to chain @text_object decorators. 
    287            return text_object_func 
    288 
    289        return decorator 
    290 
    291    return text_object_decorator 
    292 
    293 
    294# Typevar for any operator function: 
    295OperatorFunction = Callable[[E, TextObject], None] 
    296_OF = TypeVar("_OF", bound=OperatorFunction) 
    297 
    298 
    299def create_operator_decorator( 
    300    key_bindings: KeyBindings, 
    301) -> Callable[..., Callable[[_OF], _OF]]: 
    302    """ 
    303    Create a decorator that can be used for registering Vi operators. 
    304    """ 
    305 
    306    def operator_decorator( 
    307        *keys: Keys | str, filter: Filter = Always(), eager: bool = False 
    308    ) -> Callable[[_OF], _OF]: 
    309        """ 
    310        Register a Vi operator. 
    311 
    312        Usage:: 
    313 
    314            @operator('d', filter=...) 
    315            def handler(event, text_object): 
    316                # Do something with the text object here. 
    317        """ 
    318 
    319        def decorator(operator_func: _OF) -> _OF: 
    320            @key_bindings.add( 
    321                *keys, 
    322                filter=~vi_waiting_for_text_object_mode & filter & vi_navigation_mode, 
    323                eager=eager, 
    324            ) 
    325            def _operator_in_navigation(event: E) -> None: 
    326                """ 
    327                Handle operator in navigation mode. 
    328                """ 
    329                # When this key binding is matched, only set the operator 
    330                # function in the ViState. We should execute it after a text 
    331                # object has been received. 
    332                event.app.vi_state.operator_func = operator_func 
    333                event.app.vi_state.operator_arg = event.arg 
    334 
    335            @key_bindings.add( 
    336                *keys, 
    337                filter=~vi_waiting_for_text_object_mode & filter & vi_selection_mode, 
    338                eager=eager, 
    339            ) 
    340            def _operator_in_selection(event: E) -> None: 
    341                """ 
    342                Handle operator in selection mode. 
    343                """ 
    344                buff = event.current_buffer 
    345                selection_state = buff.selection_state 
    346 
    347                if selection_state is not None: 
    348                    # Create text object from selection. 
    349                    if selection_state.type == SelectionType.LINES: 
    350                        text_obj_type = TextObjectType.LINEWISE 
    351                    elif selection_state.type == SelectionType.BLOCK: 
    352                        text_obj_type = TextObjectType.BLOCK 
    353                    else: 
    354                        text_obj_type = TextObjectType.INCLUSIVE 
    355 
    356                    text_object = TextObject( 
    357                        selection_state.original_cursor_position - buff.cursor_position, 
    358                        type=text_obj_type, 
    359                    ) 
    360 
    361                    # Execute operator. 
    362                    operator_func(event, text_object) 
    363 
    364                    # Quit selection mode. 
    365                    buff.selection_state = None 
    366 
    367            return operator_func 
    368 
    369        return decorator 
    370 
    371    return operator_decorator 
    372 
    373 
    374@Condition 
    375def is_returnable() -> bool: 
    376    return get_app().current_buffer.is_returnable 
    377 
    378 
    379@Condition 
    380def in_block_selection() -> bool: 
    381    buff = get_app().current_buffer 
    382    return bool( 
    383        buff.selection_state and buff.selection_state.type == SelectionType.BLOCK 
    384    ) 
    385 
    386 
    387@Condition 
    388def digraph_symbol_1_given() -> bool: 
    389    return get_app().vi_state.digraph_symbol1 is not None 
    390 
    391 
    392@Condition 
    393def search_buffer_is_empty() -> bool: 
    394    "Returns True when the search buffer is empty." 
    395    return get_app().current_buffer.text == "" 
    396 
    397 
    398@Condition 
    399def tilde_operator() -> bool: 
    400    return get_app().vi_state.tilde_operator 
    401 
    402 
    403def load_vi_bindings() -> KeyBindingsBase: 
    404    """ 
    405    Vi extensions. 
    406 
    407    # Overview of Readline Vi commands: 
    408    # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf 
    409    """ 
    410    # Note: Some key bindings have the "~IsReadOnly()" filter added. This 
    411    #       prevents the handler to be executed when the focus is on a 
    412    #       read-only buffer. 
    413    #       This is however only required for those that change the ViState to 
    414    #       INSERT mode. The `Buffer` class itself throws the 
    415    #       `EditReadOnlyBuffer` exception for any text operations which is 
    416    #       handled correctly. There is no need to add "~IsReadOnly" to all key 
    417    #       bindings that do text manipulation. 
    418 
    419    key_bindings = KeyBindings() 
    420    handle = key_bindings.add 
    421 
    422    # (Note: Always take the navigation bindings in read-only mode, even when 
    423    #  ViState says different.) 
    424 
    425    TransformFunction = Tuple[Tuple[str, ...], Filter, Callable[[str], str]] 
    426 
    427    vi_transform_functions: list[TransformFunction] = [ 
    428        # Rot 13 transformation 
    429        ( 
    430            ("g", "?"), 
    431            Always(), 
    432            lambda string: codecs.encode(string, "rot_13"), 
    433        ), 
    434        # To lowercase 
    435        (("g", "u"), Always(), lambda string: string.lower()), 
    436        # To uppercase. 
    437        (("g", "U"), Always(), lambda string: string.upper()), 
    438        # Swap case. 
    439        (("g", "~"), Always(), lambda string: string.swapcase()), 
    440        ( 
    441            ("~",), 
    442            tilde_operator, 
    443            lambda string: string.swapcase(), 
    444        ), 
    445    ] 
    446 
    447    # Insert a character literally (quoted insert). 
    448    handle("c-v", filter=vi_insert_mode)(get_by_name("quoted-insert")) 
    449 
    450    @handle("escape") 
    451    def _back_to_navigation(event: E) -> None: 
    452        """ 
    453        Escape goes to vi navigation mode. 
    454        """ 
    455        buffer = event.current_buffer 
    456        vi_state = event.app.vi_state 
    457 
    458        if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): 
    459            buffer.cursor_position += buffer.document.get_cursor_left_position() 
    460 
    461        vi_state.input_mode = InputMode.NAVIGATION 
    462 
    463        if bool(buffer.selection_state): 
    464            buffer.exit_selection() 
    465 
    466    @handle("k", filter=vi_selection_mode) 
    467    def _up_in_selection(event: E) -> None: 
    468        """ 
    469        Arrow up in selection mode. 
    470        """ 
    471        event.current_buffer.cursor_up(count=event.arg) 
    472 
    473    @handle("j", filter=vi_selection_mode) 
    474    def _down_in_selection(event: E) -> None: 
    475        """ 
    476        Arrow down in selection mode. 
    477        """ 
    478        event.current_buffer.cursor_down(count=event.arg) 
    479 
    480    @handle("up", filter=vi_navigation_mode) 
    481    @handle("c-p", filter=vi_navigation_mode) 
    482    def _up_in_navigation(event: E) -> None: 
    483        """ 
    484        Arrow up and ControlP in navigation mode go up. 
    485        """ 
    486        event.current_buffer.auto_up(count=event.arg) 
    487 
    488    @handle("k", filter=vi_navigation_mode) 
    489    def _go_up(event: E) -> None: 
    490        """ 
    491        Go up, but if we enter a new history entry, move to the start of the 
    492        line. 
    493        """ 
    494        event.current_buffer.auto_up( 
    495            count=event.arg, go_to_start_of_line_if_history_changes=True 
    496        ) 
    497 
    498    @handle("down", filter=vi_navigation_mode) 
    499    @handle("c-n", filter=vi_navigation_mode) 
    500    def _go_down(event: E) -> None: 
    501        """ 
    502        Arrow down and Control-N in navigation mode. 
    503        """ 
    504        event.current_buffer.auto_down(count=event.arg) 
    505 
    506    @handle("j", filter=vi_navigation_mode) 
    507    def _go_down2(event: E) -> None: 
    508        """ 
    509        Go down, but if we enter a new history entry, go to the start of the line. 
    510        """ 
    511        event.current_buffer.auto_down( 
    512            count=event.arg, go_to_start_of_line_if_history_changes=True 
    513        ) 
    514 
    515    @handle("backspace", filter=vi_navigation_mode) 
    516    def _go_left(event: E) -> None: 
    517        """ 
    518        In navigation-mode, move cursor. 
    519        """ 
    520        event.current_buffer.cursor_position += ( 
    521            event.current_buffer.document.get_cursor_left_position(count=event.arg) 
    522        ) 
    523 
    524    @handle("c-n", filter=vi_insert_mode) 
    525    def _complete_next(event: E) -> None: 
    526        b = event.current_buffer 
    527 
    528        if b.complete_state: 
    529            b.complete_next() 
    530        else: 
    531            b.start_completion(select_first=True) 
    532 
    533    @handle("c-p", filter=vi_insert_mode) 
    534    def _complete_prev(event: E) -> None: 
    535        """ 
    536        Control-P: To previous completion. 
    537        """ 
    538        b = event.current_buffer 
    539 
    540        if b.complete_state: 
    541            b.complete_previous() 
    542        else: 
    543            b.start_completion(select_last=True) 
    544 
    545    @handle("c-g", filter=vi_insert_mode) 
    546    @handle("c-y", filter=vi_insert_mode) 
    547    def _accept_completion(event: E) -> None: 
    548        """ 
    549        Accept current completion. 
    550        """ 
    551        event.current_buffer.complete_state = None 
    552 
    553    @handle("c-e", filter=vi_insert_mode) 
    554    def _cancel_completion(event: E) -> None: 
    555        """ 
    556        Cancel completion. Go back to originally typed text. 
    557        """ 
    558        event.current_buffer.cancel_completion() 
    559 
    560    # In navigation mode, pressing enter will always return the input. 
    561    handle("enter", filter=vi_navigation_mode & is_returnable)( 
    562        get_by_name("accept-line") 
    563    ) 
    564 
    565    # In insert mode, also accept input when enter is pressed, and the buffer 
    566    # has been marked as single line. 
    567    handle("enter", filter=is_returnable & ~is_multiline)(get_by_name("accept-line")) 
    568 
    569    @handle("enter", filter=~is_returnable & vi_navigation_mode) 
    570    def _start_of_next_line(event: E) -> None: 
    571        """ 
    572        Go to the beginning of next line. 
    573        """ 
    574        b = event.current_buffer 
    575        b.cursor_down(count=event.arg) 
    576        b.cursor_position += b.document.get_start_of_line_position( 
    577            after_whitespace=True 
    578        ) 
    579 
    580    # ** In navigation mode ** 
    581 
    582    # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html 
    583 
    584    @handle("insert", filter=vi_navigation_mode) 
    585    def _insert_mode(event: E) -> None: 
    586        """ 
    587        Pressing the Insert key. 
    588        """ 
    589        event.app.vi_state.input_mode = InputMode.INSERT 
    590 
    591    @handle("insert", filter=vi_insert_mode) 
    592    def _navigation_mode(event: E) -> None: 
    593        """ 
    594        Pressing the Insert key. 
    595        """ 
    596        event.app.vi_state.input_mode = InputMode.NAVIGATION 
    597 
    598    @handle("a", filter=vi_navigation_mode & ~is_read_only) 
    599    # ~IsReadOnly, because we want to stay in navigation mode for 
    600    # read-only buffers. 
    601    def _a(event: E) -> None: 
    602        event.current_buffer.cursor_position += ( 
    603            event.current_buffer.document.get_cursor_right_position() 
    604        ) 
    605        event.app.vi_state.input_mode = InputMode.INSERT 
    606 
    607    @handle("A", filter=vi_navigation_mode & ~is_read_only) 
    608    def _A(event: E) -> None: 
    609        event.current_buffer.cursor_position += ( 
    610            event.current_buffer.document.get_end_of_line_position() 
    611        ) 
    612        event.app.vi_state.input_mode = InputMode.INSERT 
    613 
    614    @handle("C", filter=vi_navigation_mode & ~is_read_only) 
    615    def _change_until_end_of_line(event: E) -> None: 
    616        """ 
    617        Change to end of line. 
    618        Same as 'c$' (which is implemented elsewhere.) 
    619        """ 
    620        buffer = event.current_buffer 
    621 
    622        deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) 
    623        event.app.clipboard.set_text(deleted) 
    624        event.app.vi_state.input_mode = InputMode.INSERT 
    625 
    626    @handle("c", "c", filter=vi_navigation_mode & ~is_read_only) 
    627    @handle("S", filter=vi_navigation_mode & ~is_read_only) 
    628    def _change_current_line(event: E) -> None:  # TODO: implement 'arg' 
    629        """ 
    630        Change current line 
    631        """ 
    632        buffer = event.current_buffer 
    633 
    634        # We copy the whole line. 
    635        data = ClipboardData(buffer.document.current_line, SelectionType.LINES) 
    636        event.app.clipboard.set_data(data) 
    637 
    638        # But we delete after the whitespace 
    639        buffer.cursor_position += buffer.document.get_start_of_line_position( 
    640            after_whitespace=True 
    641        ) 
    642        buffer.delete(count=buffer.document.get_end_of_line_position()) 
    643        event.app.vi_state.input_mode = InputMode.INSERT 
    644 
    645    @handle("D", filter=vi_navigation_mode) 
    646    def _delete_until_end_of_line(event: E) -> None: 
    647        """ 
    648        Delete from cursor position until the end of the line. 
    649        """ 
    650        buffer = event.current_buffer 
    651        deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) 
    652        event.app.clipboard.set_text(deleted) 
    653 
    654    @handle("d", "d", filter=vi_navigation_mode) 
    655    def _delete_line(event: E) -> None: 
    656        """ 
    657        Delete line. (Or the following 'n' lines.) 
    658        """ 
    659        buffer = event.current_buffer 
    660 
    661        # Split string in before/deleted/after text. 
    662        lines = buffer.document.lines 
    663 
    664        before = "\n".join(lines[: buffer.document.cursor_position_row]) 
    665        deleted = "\n".join( 
    666            lines[ 
    667                buffer.document.cursor_position_row : buffer.document.cursor_position_row 
    668                + event.arg 
    669            ] 
    670        ) 
    671        after = "\n".join(lines[buffer.document.cursor_position_row + event.arg :]) 
    672 
    673        # Set new text. 
    674        if before and after: 
    675            before = before + "\n" 
    676 
    677        # Set text and cursor position. 
    678        buffer.document = Document( 
    679            text=before + after, 
    680            # Cursor At the start of the first 'after' line, after the leading whitespace. 
    681            cursor_position=len(before) + len(after) - len(after.lstrip(" ")), 
    682        ) 
    683 
    684        # Set clipboard data 
    685        event.app.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES)) 
    686 
    687    @handle("x", filter=vi_selection_mode) 
    688    def _cut(event: E) -> None: 
    689        """ 
    690        Cut selection. 
    691        ('x' is not an operator.) 
    692        """ 
    693        clipboard_data = event.current_buffer.cut_selection() 
    694        event.app.clipboard.set_data(clipboard_data) 
    695 
    696    @handle("i", filter=vi_navigation_mode & ~is_read_only) 
    697    def _i(event: E) -> None: 
    698        event.app.vi_state.input_mode = InputMode.INSERT 
    699 
    700    @handle("I", filter=vi_navigation_mode & ~is_read_only) 
    701    def _I(event: E) -> None: 
    702        event.app.vi_state.input_mode = InputMode.INSERT 
    703        event.current_buffer.cursor_position += ( 
    704            event.current_buffer.document.get_start_of_line_position( 
    705                after_whitespace=True 
    706            ) 
    707        ) 
    708 
    709    @handle("I", filter=in_block_selection & ~is_read_only) 
    710    def insert_in_block_selection(event: E, after: bool = False) -> None: 
    711        """ 
    712        Insert in block selection mode. 
    713        """ 
    714        buff = event.current_buffer 
    715 
    716        # Store all cursor positions. 
    717        positions = [] 
    718 
    719        if after: 
    720 
    721            def get_pos(from_to: tuple[int, int]) -> int: 
    722                return from_to[1] 
    723 
    724        else: 
    725 
    726            def get_pos(from_to: tuple[int, int]) -> int: 
    727                return from_to[0] 
    728 
    729        for i, from_to in enumerate(buff.document.selection_ranges()): 
    730            positions.append(get_pos(from_to)) 
    731            if i == 0: 
    732                buff.cursor_position = get_pos(from_to) 
    733 
    734        buff.multiple_cursor_positions = positions 
    735 
    736        # Go to 'INSERT_MULTIPLE' mode. 
    737        event.app.vi_state.input_mode = InputMode.INSERT_MULTIPLE 
    738        buff.exit_selection() 
    739 
    740    @handle("A", filter=in_block_selection & ~is_read_only) 
    741    def _append_after_block(event: E) -> None: 
    742        insert_in_block_selection(event, after=True) 
    743 
    744    @handle("J", filter=vi_navigation_mode & ~is_read_only) 
    745    def _join(event: E) -> None: 
    746        """ 
    747        Join lines. 
    748        """ 
    749        for i in range(event.arg): 
    750            event.current_buffer.join_next_line() 
    751 
    752    @handle("g", "J", filter=vi_navigation_mode & ~is_read_only) 
    753    def _join_nospace(event: E) -> None: 
    754        """ 
    755        Join lines without space. 
    756        """ 
    757        for i in range(event.arg): 
    758            event.current_buffer.join_next_line(separator="") 
    759 
    760    @handle("J", filter=vi_selection_mode & ~is_read_only) 
    761    def _join_selection(event: E) -> None: 
    762        """ 
    763        Join selected lines. 
    764        """ 
    765        event.current_buffer.join_selected_lines() 
    766 
    767    @handle("g", "J", filter=vi_selection_mode & ~is_read_only) 
    768    def _join_selection_nospace(event: E) -> None: 
    769        """ 
    770        Join selected lines without space. 
    771        """ 
    772        event.current_buffer.join_selected_lines(separator="") 
    773 
    774    @handle("p", filter=vi_navigation_mode) 
    775    def _paste(event: E) -> None: 
    776        """ 
    777        Paste after 
    778        """ 
    779        event.current_buffer.paste_clipboard_data( 
    780            event.app.clipboard.get_data(), 
    781            count=event.arg, 
    782            paste_mode=PasteMode.VI_AFTER, 
    783        ) 
    784 
    785    @handle("P", filter=vi_navigation_mode) 
    786    def _paste_before(event: E) -> None: 
    787        """ 
    788        Paste before 
    789        """ 
    790        event.current_buffer.paste_clipboard_data( 
    791            event.app.clipboard.get_data(), 
    792            count=event.arg, 
    793            paste_mode=PasteMode.VI_BEFORE, 
    794        ) 
    795 
    796    @handle('"', Keys.Any, "p", filter=vi_navigation_mode) 
    797    def _paste_register(event: E) -> None: 
    798        """ 
    799        Paste from named register. 
    800        """ 
    801        c = event.key_sequence[1].data 
    802        if c in vi_register_names: 
    803            data = event.app.vi_state.named_registers.get(c) 
    804            if data: 
    805                event.current_buffer.paste_clipboard_data( 
    806                    data, count=event.arg, paste_mode=PasteMode.VI_AFTER 
    807                ) 
    808 
    809    @handle('"', Keys.Any, "P", filter=vi_navigation_mode) 
    810    def _paste_register_before(event: E) -> None: 
    811        """ 
    812        Paste (before) from named register. 
    813        """ 
    814        c = event.key_sequence[1].data 
    815        if c in vi_register_names: 
    816            data = event.app.vi_state.named_registers.get(c) 
    817            if data: 
    818                event.current_buffer.paste_clipboard_data( 
    819                    data, count=event.arg, paste_mode=PasteMode.VI_BEFORE 
    820                ) 
    821 
    822    @handle("r", filter=vi_navigation_mode) 
    823    def _replace(event: E) -> None: 
    824        """ 
    825        Go to 'replace-single'-mode. 
    826        """ 
    827        event.app.vi_state.input_mode = InputMode.REPLACE_SINGLE 
    828 
    829    @handle("R", filter=vi_navigation_mode) 
    830    def _replace_mode(event: E) -> None: 
    831        """ 
    832        Go to 'replace'-mode. 
    833        """ 
    834        event.app.vi_state.input_mode = InputMode.REPLACE 
    835 
    836    @handle("s", filter=vi_navigation_mode & ~is_read_only) 
    837    def _substitute(event: E) -> None: 
    838        """ 
    839        Substitute with new text 
    840        (Delete character(s) and go to insert mode.) 
    841        """ 
    842        text = event.current_buffer.delete(count=event.arg) 
    843        event.app.clipboard.set_text(text) 
    844        event.app.vi_state.input_mode = InputMode.INSERT 
    845 
    846    @handle("u", filter=vi_navigation_mode, save_before=(lambda e: False)) 
    847    def _undo(event: E) -> None: 
    848        for i in range(event.arg): 
    849            event.current_buffer.undo() 
    850 
    851    @handle("V", filter=vi_navigation_mode) 
    852    def _visual_line(event: E) -> None: 
    853        """ 
    854        Start lines selection. 
    855        """ 
    856        event.current_buffer.start_selection(selection_type=SelectionType.LINES) 
    857 
    858    @handle("c-v", filter=vi_navigation_mode) 
    859    def _visual_block(event: E) -> None: 
    860        """ 
    861        Enter block selection mode. 
    862        """ 
    863        event.current_buffer.start_selection(selection_type=SelectionType.BLOCK) 
    864 
    865    @handle("V", filter=vi_selection_mode) 
    866    def _visual_line2(event: E) -> None: 
    867        """ 
    868        Exit line selection mode, or go from non line selection mode to line 
    869        selection mode. 
    870        """ 
    871        selection_state = event.current_buffer.selection_state 
    872 
    873        if selection_state is not None: 
    874            if selection_state.type != SelectionType.LINES: 
    875                selection_state.type = SelectionType.LINES 
    876            else: 
    877                event.current_buffer.exit_selection() 
    878 
    879    @handle("v", filter=vi_navigation_mode) 
    880    def _visual(event: E) -> None: 
    881        """ 
    882        Enter character selection mode. 
    883        """ 
    884        event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) 
    885 
    886    @handle("v", filter=vi_selection_mode) 
    887    def _visual2(event: E) -> None: 
    888        """ 
    889        Exit character selection mode, or go from non-character-selection mode 
    890        to character selection mode. 
    891        """ 
    892        selection_state = event.current_buffer.selection_state 
    893 
    894        if selection_state is not None: 
    895            if selection_state.type != SelectionType.CHARACTERS: 
    896                selection_state.type = SelectionType.CHARACTERS 
    897            else: 
    898                event.current_buffer.exit_selection() 
    899 
    900    @handle("c-v", filter=vi_selection_mode) 
    901    def _visual_block2(event: E) -> None: 
    902        """ 
    903        Exit block selection mode, or go from non block selection mode to block 
    904        selection mode. 
    905        """ 
    906        selection_state = event.current_buffer.selection_state 
    907 
    908        if selection_state is not None: 
    909            if selection_state.type != SelectionType.BLOCK: 
    910                selection_state.type = SelectionType.BLOCK 
    911            else: 
    912                event.current_buffer.exit_selection() 
    913 
    914    @handle("a", "w", filter=vi_selection_mode) 
    915    @handle("a", "W", filter=vi_selection_mode) 
    916    def _visual_auto_word(event: E) -> None: 
    917        """ 
    918        Switch from visual linewise mode to visual characterwise mode. 
    919        """ 
    920        buffer = event.current_buffer 
    921 
    922        if ( 
    923            buffer.selection_state 
    924            and buffer.selection_state.type == SelectionType.LINES 
    925        ): 
    926            buffer.selection_state.type = SelectionType.CHARACTERS 
    927 
    928    @handle("x", filter=vi_navigation_mode) 
    929    def _delete(event: E) -> None: 
    930        """ 
    931        Delete character. 
    932        """ 
    933        buff = event.current_buffer 
    934        count = min(event.arg, len(buff.document.current_line_after_cursor)) 
    935        if count: 
    936            text = event.current_buffer.delete(count=count) 
    937            event.app.clipboard.set_text(text) 
    938 
    939    @handle("X", filter=vi_navigation_mode) 
    940    def _delete_before_cursor(event: E) -> None: 
    941        buff = event.current_buffer 
    942        count = min(event.arg, len(buff.document.current_line_before_cursor)) 
    943        if count: 
    944            text = event.current_buffer.delete_before_cursor(count=count) 
    945            event.app.clipboard.set_text(text) 
    946 
    947    @handle("y", "y", filter=vi_navigation_mode) 
    948    @handle("Y", filter=vi_navigation_mode) 
    949    def _yank_line(event: E) -> None: 
    950        """ 
    951        Yank the whole line. 
    952        """ 
    953        text = "\n".join(event.current_buffer.document.lines_from_current[: event.arg]) 
    954        event.app.clipboard.set_data(ClipboardData(text, SelectionType.LINES)) 
    955 
    956    @handle("+", filter=vi_navigation_mode) 
    957    def _next_line(event: E) -> None: 
    958        """ 
    959        Move to first non whitespace of next line 
    960        """ 
    961        buffer = event.current_buffer 
    962        buffer.cursor_position += buffer.document.get_cursor_down_position( 
    963            count=event.arg 
    964        ) 
    965        buffer.cursor_position += buffer.document.get_start_of_line_position( 
    966            after_whitespace=True 
    967        ) 
    968 
    969    @handle("-", filter=vi_navigation_mode) 
    970    def _prev_line(event: E) -> None: 
    971        """ 
    972        Move to first non whitespace of previous line 
    973        """ 
    974        buffer = event.current_buffer 
    975        buffer.cursor_position += buffer.document.get_cursor_up_position( 
    976            count=event.arg 
    977        ) 
    978        buffer.cursor_position += buffer.document.get_start_of_line_position( 
    979            after_whitespace=True 
    980        ) 
    981 
    982    @handle(">", ">", filter=vi_navigation_mode) 
    983    @handle("c-t", filter=vi_insert_mode) 
    984    def _indent(event: E) -> None: 
    985        """ 
    986        Indent lines. 
    987        """ 
    988        buffer = event.current_buffer 
    989        current_row = buffer.document.cursor_position_row 
    990        indent(buffer, current_row, current_row + event.arg) 
    991 
    992    @handle("<", "<", filter=vi_navigation_mode) 
    993    @handle("c-d", filter=vi_insert_mode) 
    994    def _unindent(event: E) -> None: 
    995        """ 
    996        Unindent lines. 
    997        """ 
    998        current_row = event.current_buffer.document.cursor_position_row 
    999        unindent(event.current_buffer, current_row, current_row + event.arg) 
    1000 
    1001    @handle("O", filter=vi_navigation_mode & ~is_read_only) 
    1002    def _open_above(event: E) -> None: 
    1003        """ 
    1004        Open line above and enter insertion mode 
    1005        """ 
    1006        event.current_buffer.insert_line_above(copy_margin=not in_paste_mode()) 
    1007        event.app.vi_state.input_mode = InputMode.INSERT 
    1008 
    1009    @handle("o", filter=vi_navigation_mode & ~is_read_only) 
    1010    def _open_below(event: E) -> None: 
    1011        """ 
    1012        Open line below and enter insertion mode 
    1013        """ 
    1014        event.current_buffer.insert_line_below(copy_margin=not in_paste_mode()) 
    1015        event.app.vi_state.input_mode = InputMode.INSERT 
    1016 
    1017    @handle("~", filter=vi_navigation_mode) 
    1018    def _reverse_case(event: E) -> None: 
    1019        """ 
    1020        Reverse case of current character and move cursor forward. 
    1021        """ 
    1022        buffer = event.current_buffer 
    1023        c = buffer.document.current_char 
    1024 
    1025        if c is not None and c != "\n": 
    1026            buffer.insert_text(c.swapcase(), overwrite=True) 
    1027 
    1028    @handle("g", "u", "u", filter=vi_navigation_mode & ~is_read_only) 
    1029    def _lowercase_line(event: E) -> None: 
    1030        """ 
    1031        Lowercase current line. 
    1032        """ 
    1033        buff = event.current_buffer 
    1034        buff.transform_current_line(lambda s: s.lower()) 
    1035 
    1036    @handle("g", "U", "U", filter=vi_navigation_mode & ~is_read_only) 
    1037    def _uppercase_line(event: E) -> None: 
    1038        """ 
    1039        Uppercase current line. 
    1040        """ 
    1041        buff = event.current_buffer 
    1042        buff.transform_current_line(lambda s: s.upper()) 
    1043 
    1044    @handle("g", "~", "~", filter=vi_navigation_mode & ~is_read_only) 
    1045    def _swapcase_line(event: E) -> None: 
    1046        """ 
    1047        Swap case of the current line. 
    1048        """ 
    1049        buff = event.current_buffer 
    1050        buff.transform_current_line(lambda s: s.swapcase()) 
    1051 
    1052    @handle("#", filter=vi_navigation_mode) 
    1053    def _prev_occurrence(event: E) -> None: 
    1054        """ 
    1055        Go to previous occurrence of this word. 
    1056        """ 
    1057        b = event.current_buffer 
    1058        search_state = event.app.current_search_state 
    1059 
    1060        search_state.text = b.document.get_word_under_cursor() 
    1061        search_state.direction = SearchDirection.BACKWARD 
    1062 
    1063        b.apply_search(search_state, count=event.arg, include_current_position=False) 
    1064 
    1065    @handle("*", filter=vi_navigation_mode) 
    1066    def _next_occurrence(event: E) -> None: 
    1067        """ 
    1068        Go to next occurrence of this word. 
    1069        """ 
    1070        b = event.current_buffer 
    1071        search_state = event.app.current_search_state 
    1072 
    1073        search_state.text = b.document.get_word_under_cursor() 
    1074        search_state.direction = SearchDirection.FORWARD 
    1075 
    1076        b.apply_search(search_state, count=event.arg, include_current_position=False) 
    1077 
    1078    @handle("(", filter=vi_navigation_mode) 
    1079    def _begin_of_sentence(event: E) -> None: 
    1080        # TODO: go to begin of sentence. 
    1081        # XXX: should become text_object. 
    1082        pass 
    1083 
    1084    @handle(")", filter=vi_navigation_mode) 
    1085    def _end_of_sentence(event: E) -> None: 
    1086        # TODO: go to end of sentence. 
    1087        # XXX: should become text_object. 
    1088        pass 
    1089 
    1090    operator = create_operator_decorator(key_bindings) 
    1091    text_object = create_text_object_decorator(key_bindings) 
    1092 
    1093    @handle(Keys.Any, filter=vi_waiting_for_text_object_mode) 
    1094    def _unknown_text_object(event: E) -> None: 
    1095        """ 
    1096        Unknown key binding while waiting for a text object. 
    1097        """ 
    1098        event.app.output.bell() 
    1099 
    1100    # 
    1101    # *** Operators *** 
    1102    # 
    1103 
    1104    def create_delete_and_change_operators( 
    1105        delete_only: bool, with_register: bool = False 
    1106    ) -> None: 
    1107        """ 
    1108        Delete and change operators. 
    1109 
    1110        :param delete_only: Create an operator that deletes, but doesn't go to insert mode. 
    1111        :param with_register: Copy the deleted text to this named register instead of the clipboard. 
    1112        """ 
    1113        handler_keys: Iterable[str] 
    1114        if with_register: 
    1115            handler_keys = ('"', Keys.Any, "cd"[delete_only]) 
    1116        else: 
    1117            handler_keys = "cd"[delete_only] 
    1118 
    1119        @operator(*handler_keys, filter=~is_read_only) 
    1120        def delete_or_change_operator(event: E, text_object: TextObject) -> None: 
    1121            clipboard_data = None 
    1122            buff = event.current_buffer 
    1123 
    1124            if text_object: 
    1125                new_document, clipboard_data = text_object.cut(buff) 
    1126                buff.document = new_document 
    1127 
    1128            # Set deleted/changed text to clipboard or named register. 
    1129            if clipboard_data and clipboard_data.text: 
    1130                if with_register: 
    1131                    reg_name = event.key_sequence[1].data 
    1132                    if reg_name in vi_register_names: 
    1133                        event.app.vi_state.named_registers[reg_name] = clipboard_data 
    1134                else: 
    1135                    event.app.clipboard.set_data(clipboard_data) 
    1136 
    1137            # Only go back to insert mode in case of 'change'. 
    1138            if not delete_only: 
    1139                event.app.vi_state.input_mode = InputMode.INSERT 
    1140 
    1141    create_delete_and_change_operators(False, False) 
    1142    create_delete_and_change_operators(False, True) 
    1143    create_delete_and_change_operators(True, False) 
    1144    create_delete_and_change_operators(True, True) 
    1145 
    1146    def create_transform_handler( 
    1147        filter: Filter, transform_func: Callable[[str], str], *a: str 
    1148    ) -> None: 
    1149        @operator(*a, filter=filter & ~is_read_only) 
    1150        def _(event: E, text_object: TextObject) -> None: 
    1151            """ 
    1152            Apply transformation (uppercase, lowercase, rot13, swap case). 
    1153            """ 
    1154            buff = event.current_buffer 
    1155            start, end = text_object.operator_range(buff.document) 
    1156 
    1157            if start < end: 
    1158                # Transform. 
    1159                buff.transform_region( 
    1160                    buff.cursor_position + start, 
    1161                    buff.cursor_position + end, 
    1162                    transform_func, 
    1163                ) 
    1164 
    1165                # Move cursor 
    1166                buff.cursor_position += text_object.end or text_object.start 
    1167 
    1168    for k, f, func in vi_transform_functions: 
    1169        create_transform_handler(f, func, *k) 
    1170 
    1171    @operator("y") 
    1172    def _yank(event: E, text_object: TextObject) -> None: 
    1173        """ 
    1174        Yank operator. (Copy text.) 
    1175        """ 
    1176        _, clipboard_data = text_object.cut(event.current_buffer) 
    1177        if clipboard_data.text: 
    1178            event.app.clipboard.set_data(clipboard_data) 
    1179 
    1180    @operator('"', Keys.Any, "y") 
    1181    def _yank_to_register(event: E, text_object: TextObject) -> None: 
    1182        """ 
    1183        Yank selection to named register. 
    1184        """ 
    1185        c = event.key_sequence[1].data 
    1186        if c in vi_register_names: 
    1187            _, clipboard_data = text_object.cut(event.current_buffer) 
    1188            event.app.vi_state.named_registers[c] = clipboard_data 
    1189 
    1190    @operator(">") 
    1191    def _indent_text_object(event: E, text_object: TextObject) -> None: 
    1192        """ 
    1193        Indent. 
    1194        """ 
    1195        buff = event.current_buffer 
    1196        from_, to = text_object.get_line_numbers(buff) 
    1197        indent(buff, from_, to + 1, count=event.arg) 
    1198 
    1199    @operator("<") 
    1200    def _unindent_text_object(event: E, text_object: TextObject) -> None: 
    1201        """ 
    1202        Unindent. 
    1203        """ 
    1204        buff = event.current_buffer 
    1205        from_, to = text_object.get_line_numbers(buff) 
    1206        unindent(buff, from_, to + 1, count=event.arg) 
    1207 
    1208    @operator("g", "q") 
    1209    def _reshape(event: E, text_object: TextObject) -> None: 
    1210        """ 
    1211        Reshape text. 
    1212        """ 
    1213        buff = event.current_buffer 
    1214        from_, to = text_object.get_line_numbers(buff) 
    1215        reshape_text(buff, from_, to) 
    1216 
    1217    # 
    1218    # *** Text objects *** 
    1219    # 
    1220 
    1221    @text_object("b") 
    1222    def _b(event: E) -> TextObject: 
    1223        """ 
    1224        Move one word or token left. 
    1225        """ 
    1226        return TextObject( 
    1227            event.current_buffer.document.find_start_of_previous_word(count=event.arg) 
    1228            or 0 
    1229        ) 
    1230 
    1231    @text_object("B") 
    1232    def _B(event: E) -> TextObject: 
    1233        """ 
    1234        Move one non-blank word left 
    1235        """ 
    1236        return TextObject( 
    1237            event.current_buffer.document.find_start_of_previous_word( 
    1238                count=event.arg, WORD=True 
    1239            ) 
    1240            or 0 
    1241        ) 
    1242 
    1243    @text_object("$") 
    1244    def _dollar(event: E) -> TextObject: 
    1245        """ 
    1246        'c$', 'd$' and '$':  Delete/change/move until end of line. 
    1247        """ 
    1248        return TextObject(event.current_buffer.document.get_end_of_line_position()) 
    1249 
    1250    @text_object("w") 
    1251    def _word_forward(event: E) -> TextObject: 
    1252        """ 
    1253        'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. 
    1254        """ 
    1255        return TextObject( 
    1256            event.current_buffer.document.find_next_word_beginning(count=event.arg) 
    1257            or event.current_buffer.document.get_end_of_document_position() 
    1258        ) 
    1259 
    1260    @text_object("W") 
    1261    def _WORD_forward(event: E) -> TextObject: 
    1262        """ 
    1263        'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. 
    1264        """ 
    1265        return TextObject( 
    1266            event.current_buffer.document.find_next_word_beginning( 
    1267                count=event.arg, WORD=True 
    1268            ) 
    1269            or event.current_buffer.document.get_end_of_document_position() 
    1270        ) 
    1271 
    1272    @text_object("e") 
    1273    def _end_of_word(event: E) -> TextObject: 
    1274        """ 
    1275        End of 'word': 'ce', 'de', 'e' 
    1276        """ 
    1277        end = event.current_buffer.document.find_next_word_ending(count=event.arg) 
    1278        return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) 
    1279 
    1280    @text_object("E") 
    1281    def _end_of_WORD(event: E) -> TextObject: 
    1282        """ 
    1283        End of 'WORD': 'cE', 'dE', 'E' 
    1284        """ 
    1285        end = event.current_buffer.document.find_next_word_ending( 
    1286            count=event.arg, WORD=True 
    1287        ) 
    1288        return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) 
    1289 
    1290    @text_object("i", "w", no_move_handler=True) 
    1291    def _inner_word(event: E) -> TextObject: 
    1292        """ 
    1293        Inner 'word': ciw and diw 
    1294        """ 
    1295        start, end = event.current_buffer.document.find_boundaries_of_current_word() 
    1296        return TextObject(start, end) 
    1297 
    1298    @text_object("a", "w", no_move_handler=True) 
    1299    def _a_word(event: E) -> TextObject: 
    1300        """ 
    1301        A 'word': caw and daw 
    1302        """ 
    1303        start, end = event.current_buffer.document.find_boundaries_of_current_word( 
    1304            include_trailing_whitespace=True 
    1305        ) 
    1306        return TextObject(start, end) 
    1307 
    1308    @text_object("i", "W", no_move_handler=True) 
    1309    def _inner_WORD(event: E) -> TextObject: 
    1310        """ 
    1311        Inner 'WORD': ciW and diW 
    1312        """ 
    1313        start, end = event.current_buffer.document.find_boundaries_of_current_word( 
    1314            WORD=True 
    1315        ) 
    1316        return TextObject(start, end) 
    1317 
    1318    @text_object("a", "W", no_move_handler=True) 
    1319    def _a_WORD(event: E) -> TextObject: 
    1320        """ 
    1321        A 'WORD': caw and daw 
    1322        """ 
    1323        start, end = event.current_buffer.document.find_boundaries_of_current_word( 
    1324            WORD=True, include_trailing_whitespace=True 
    1325        ) 
    1326        return TextObject(start, end) 
    1327 
    1328    @text_object("a", "p", no_move_handler=True) 
    1329    def _paragraph(event: E) -> TextObject: 
    1330        """ 
    1331        Auto paragraph. 
    1332        """ 
    1333        start = event.current_buffer.document.start_of_paragraph() 
    1334        end = event.current_buffer.document.end_of_paragraph(count=event.arg) 
    1335        return TextObject(start, end) 
    1336 
    1337    @text_object("^") 
    1338    def _start_of_line(event: E) -> TextObject: 
    1339        """'c^', 'd^' and '^': Soft start of line, after whitespace.""" 
    1340        return TextObject( 
    1341            event.current_buffer.document.get_start_of_line_position( 
    1342                after_whitespace=True 
    1343            ) 
    1344        ) 
    1345 
    1346    @text_object("0") 
    1347    def _hard_start_of_line(event: E) -> TextObject: 
    1348        """ 
    1349        'c0', 'd0': Hard start of line, before whitespace. 
    1350        (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) 
    1351        """ 
    1352        return TextObject( 
    1353            event.current_buffer.document.get_start_of_line_position( 
    1354                after_whitespace=False 
    1355            ) 
    1356        ) 
    1357 
    1358    def create_ci_ca_handles( 
    1359        ci_start: str, ci_end: str, inner: bool, key: str | None = None 
    1360    ) -> None: 
    1361        # TODO: 'dat', 'dit', (tags (like xml) 
    1362        """ 
    1363        Delete/Change string between this start and stop character. But keep these characters. 
    1364        This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. 
    1365        """ 
    1366 
    1367        def handler(event: E) -> TextObject: 
    1368            if ci_start == ci_end: 
    1369                # Quotes 
    1370                start = event.current_buffer.document.find_backwards( 
    1371                    ci_start, in_current_line=False 
    1372                ) 
    1373                end = event.current_buffer.document.find(ci_end, in_current_line=False) 
    1374            else: 
    1375                # Brackets 
    1376                start = event.current_buffer.document.find_enclosing_bracket_left( 
    1377                    ci_start, ci_end 
    1378                ) 
    1379                end = event.current_buffer.document.find_enclosing_bracket_right( 
    1380                    ci_start, ci_end 
    1381                ) 
    1382 
    1383            if start is not None and end is not None: 
    1384                offset = 0 if inner else 1 
    1385                return TextObject(start + 1 - offset, end + offset) 
    1386            else: 
    1387                # Nothing found. 
    1388                return TextObject(0) 
    1389 
    1390        if key is None: 
    1391            text_object("ai"[inner], ci_start, no_move_handler=True)(handler) 
    1392            text_object("ai"[inner], ci_end, no_move_handler=True)(handler) 
    1393        else: 
    1394            text_object("ai"[inner], key, no_move_handler=True)(handler) 
    1395 
    1396    for inner in (False, True): 
    1397        for ci_start, ci_end in [ 
    1398            ('"', '"'), 
    1399            ("'", "'"), 
    1400            ("`", "`"), 
    1401            ("[", "]"), 
    1402            ("<", ">"), 
    1403            ("{", "}"), 
    1404            ("(", ")"), 
    1405        ]: 
    1406            create_ci_ca_handles(ci_start, ci_end, inner) 
    1407 
    1408        create_ci_ca_handles("(", ")", inner, "b")  # 'dab', 'dib' 
    1409        create_ci_ca_handles("{", "}", inner, "B")  # 'daB', 'diB' 
    1410 
    1411    @text_object("{") 
    1412    def _previous_section(event: E) -> TextObject: 
    1413        """ 
    1414        Move to previous blank-line separated section. 
    1415        Implements '{', 'c{', 'd{', 'y{' 
    1416        """ 
    1417        index = event.current_buffer.document.start_of_paragraph( 
    1418            count=event.arg, before=True 
    1419        ) 
    1420        return TextObject(index) 
    1421 
    1422    @text_object("}") 
    1423    def _next_section(event: E) -> TextObject: 
    1424        """ 
    1425        Move to next blank-line separated section. 
    1426        Implements '}', 'c}', 'd}', 'y}' 
    1427        """ 
    1428        index = event.current_buffer.document.end_of_paragraph( 
    1429            count=event.arg, after=True 
    1430        ) 
    1431        return TextObject(index) 
    1432 
    1433    @text_object("f", Keys.Any) 
    1434    def _find_next_occurrence(event: E) -> TextObject: 
    1435        """ 
    1436        Go to next occurrence of character. Typing 'fx' will move the 
    1437        cursor to the next occurrence of character. 'x'. 
    1438        """ 
    1439        event.app.vi_state.last_character_find = CharacterFind(event.data, False) 
    1440        match = event.current_buffer.document.find( 
    1441            event.data, in_current_line=True, count=event.arg 
    1442        ) 
    1443        if match: 
    1444            return TextObject(match, type=TextObjectType.INCLUSIVE) 
    1445        else: 
    1446            return TextObject(0) 
    1447 
    1448    @text_object("F", Keys.Any) 
    1449    def _find_previous_occurrence(event: E) -> TextObject: 
    1450        """ 
    1451        Go to previous occurrence of character. Typing 'Fx' will move the 
    1452        cursor to the previous occurrence of character. 'x'. 
    1453        """ 
    1454        event.app.vi_state.last_character_find = CharacterFind(event.data, True) 
    1455        return TextObject( 
    1456            event.current_buffer.document.find_backwards( 
    1457                event.data, in_current_line=True, count=event.arg 
    1458            ) 
    1459            or 0 
    1460        ) 
    1461 
    1462    @text_object("t", Keys.Any) 
    1463    def _t(event: E) -> TextObject: 
    1464        """ 
    1465        Move right to the next occurrence of c, then one char backward. 
    1466        """ 
    1467        event.app.vi_state.last_character_find = CharacterFind(event.data, False) 
    1468        match = event.current_buffer.document.find( 
    1469            event.data, in_current_line=True, count=event.arg 
    1470        ) 
    1471        if match: 
    1472            return TextObject(match - 1, type=TextObjectType.INCLUSIVE) 
    1473        else: 
    1474            return TextObject(0) 
    1475 
    1476    @text_object("T", Keys.Any) 
    1477    def _T(event: E) -> TextObject: 
    1478        """ 
    1479        Move left to the previous occurrence of c, then one char forward. 
    1480        """ 
    1481        event.app.vi_state.last_character_find = CharacterFind(event.data, True) 
    1482        match = event.current_buffer.document.find_backwards( 
    1483            event.data, in_current_line=True, count=event.arg 
    1484        ) 
    1485        return TextObject(match + 1 if match else 0) 
    1486 
    1487    def repeat(reverse: bool) -> None: 
    1488        """ 
    1489        Create ',' and ';' commands. 
    1490        """ 
    1491 
    1492        @text_object("," if reverse else ";") 
    1493        def _(event: E) -> TextObject: 
    1494            """ 
    1495            Repeat the last 'f'/'F'/'t'/'T' command. 
    1496            """ 
    1497            pos: int | None = 0 
    1498            vi_state = event.app.vi_state 
    1499 
    1500            type = TextObjectType.EXCLUSIVE 
    1501 
    1502            if vi_state.last_character_find: 
    1503                char = vi_state.last_character_find.character 
    1504                backwards = vi_state.last_character_find.backwards 
    1505 
    1506                if reverse: 
    1507                    backwards = not backwards 
    1508 
    1509                if backwards: 
    1510                    pos = event.current_buffer.document.find_backwards( 
    1511                        char, in_current_line=True, count=event.arg 
    1512                    ) 
    1513                else: 
    1514                    pos = event.current_buffer.document.find( 
    1515                        char, in_current_line=True, count=event.arg 
    1516                    ) 
    1517                    type = TextObjectType.INCLUSIVE 
    1518            if pos: 
    1519                return TextObject(pos, type=type) 
    1520            else: 
    1521                return TextObject(0) 
    1522 
    1523    repeat(True) 
    1524    repeat(False) 
    1525 
    1526    @text_object("h") 
    1527    @text_object("left") 
    1528    def _left(event: E) -> TextObject: 
    1529        """ 
    1530        Implements 'ch', 'dh', 'h': Cursor left. 
    1531        """ 
    1532        return TextObject( 
    1533            event.current_buffer.document.get_cursor_left_position(count=event.arg) 
    1534        ) 
    1535 
    1536    @text_object("j", no_move_handler=True, no_selection_handler=True) 
    1537    # Note: We also need `no_selection_handler`, because we in 
    1538    #       selection mode, we prefer the other 'j' binding that keeps 
    1539    #       `buffer.preferred_column`. 
    1540    def _down(event: E) -> TextObject: 
    1541        """ 
    1542        Implements 'cj', 'dj', 'j', ... Cursor up. 
    1543        """ 
    1544        return TextObject( 
    1545            event.current_buffer.document.get_cursor_down_position(count=event.arg), 
    1546            type=TextObjectType.LINEWISE, 
    1547        ) 
    1548 
    1549    @text_object("k", no_move_handler=True, no_selection_handler=True) 
    1550    def _up(event: E) -> TextObject: 
    1551        """ 
    1552        Implements 'ck', 'dk', 'k', ... Cursor up. 
    1553        """ 
    1554        return TextObject( 
    1555            event.current_buffer.document.get_cursor_up_position(count=event.arg), 
    1556            type=TextObjectType.LINEWISE, 
    1557        ) 
    1558 
    1559    @text_object("l") 
    1560    @text_object(" ") 
    1561    @text_object("right") 
    1562    def _right(event: E) -> TextObject: 
    1563        """ 
    1564        Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. 
    1565        """ 
    1566        return TextObject( 
    1567            event.current_buffer.document.get_cursor_right_position(count=event.arg) 
    1568        ) 
    1569 
    1570    @text_object("H") 
    1571    def _top_of_screen(event: E) -> TextObject: 
    1572        """ 
    1573        Moves to the start of the visible region. (Below the scroll offset.) 
    1574        Implements 'cH', 'dH', 'H'. 
    1575        """ 
    1576        w = event.app.layout.current_window 
    1577        b = event.current_buffer 
    1578 
    1579        if w and w.render_info: 
    1580            # When we find a Window that has BufferControl showing this window, 
    1581            # move to the start of the visible area. 
    1582            pos = ( 
    1583                b.document.translate_row_col_to_index( 
    1584                    w.render_info.first_visible_line(after_scroll_offset=True), 0 
    1585                ) 
    1586                - b.cursor_position 
    1587            ) 
    1588 
    1589        else: 
    1590            # Otherwise, move to the start of the input. 
    1591            pos = -len(b.document.text_before_cursor) 
    1592        return TextObject(pos, type=TextObjectType.LINEWISE) 
    1593 
    1594    @text_object("M") 
    1595    def _middle_of_screen(event: E) -> TextObject: 
    1596        """ 
    1597        Moves cursor to the vertical center of the visible region. 
    1598        Implements 'cM', 'dM', 'M'. 
    1599        """ 
    1600        w = event.app.layout.current_window 
    1601        b = event.current_buffer 
    1602 
    1603        if w and w.render_info: 
    1604            # When we find a Window that has BufferControl showing this window, 
    1605            # move to the center of the visible area. 
    1606            pos = ( 
    1607                b.document.translate_row_col_to_index( 
    1608                    w.render_info.center_visible_line(), 0 
    1609                ) 
    1610                - b.cursor_position 
    1611            ) 
    1612 
    1613        else: 
    1614            # Otherwise, move to the start of the input. 
    1615            pos = -len(b.document.text_before_cursor) 
    1616        return TextObject(pos, type=TextObjectType.LINEWISE) 
    1617 
    1618    @text_object("L") 
    1619    def _end_of_screen(event: E) -> TextObject: 
    1620        """ 
    1621        Moves to the end of the visible region. (Above the scroll offset.) 
    1622        """ 
    1623        w = event.app.layout.current_window 
    1624        b = event.current_buffer 
    1625 
    1626        if w and w.render_info: 
    1627            # When we find a Window that has BufferControl showing this window, 
    1628            # move to the end of the visible area. 
    1629            pos = ( 
    1630                b.document.translate_row_col_to_index( 
    1631                    w.render_info.last_visible_line(before_scroll_offset=True), 0 
    1632                ) 
    1633                - b.cursor_position 
    1634            ) 
    1635 
    1636        else: 
    1637            # Otherwise, move to the end of the input. 
    1638            pos = len(b.document.text_after_cursor) 
    1639        return TextObject(pos, type=TextObjectType.LINEWISE) 
    1640 
    1641    @text_object("n", no_move_handler=True) 
    1642    def _search_next(event: E) -> TextObject: 
    1643        """ 
    1644        Search next. 
    1645        """ 
    1646        buff = event.current_buffer 
    1647        search_state = event.app.current_search_state 
    1648 
    1649        cursor_position = buff.get_search_position( 
    1650            search_state, include_current_position=False, count=event.arg 
    1651        ) 
    1652        return TextObject(cursor_position - buff.cursor_position) 
    1653 
    1654    @handle("n", filter=vi_navigation_mode) 
    1655    def _search_next2(event: E) -> None: 
    1656        """ 
    1657        Search next in navigation mode. (This goes through the history.) 
    1658        """ 
    1659        search_state = event.app.current_search_state 
    1660 
    1661        event.current_buffer.apply_search( 
    1662            search_state, include_current_position=False, count=event.arg 
    1663        ) 
    1664 
    1665    @text_object("N", no_move_handler=True) 
    1666    def _search_previous(event: E) -> TextObject: 
    1667        """ 
    1668        Search previous. 
    1669        """ 
    1670        buff = event.current_buffer 
    1671        search_state = event.app.current_search_state 
    1672 
    1673        cursor_position = buff.get_search_position( 
    1674            ~search_state, include_current_position=False, count=event.arg 
    1675        ) 
    1676        return TextObject(cursor_position - buff.cursor_position) 
    1677 
    1678    @handle("N", filter=vi_navigation_mode) 
    1679    def _search_previous2(event: E) -> None: 
    1680        """ 
    1681        Search previous in navigation mode. (This goes through the history.) 
    1682        """ 
    1683        search_state = event.app.current_search_state 
    1684 
    1685        event.current_buffer.apply_search( 
    1686            ~search_state, include_current_position=False, count=event.arg 
    1687        ) 
    1688 
    1689    @handle("z", "+", filter=vi_navigation_mode | vi_selection_mode) 
    1690    @handle("z", "t", filter=vi_navigation_mode | vi_selection_mode) 
    1691    @handle("z", "enter", filter=vi_navigation_mode | vi_selection_mode) 
    1692    def _scroll_top(event: E) -> None: 
    1693        """ 
    1694        Scrolls the window to makes the current line the first line in the visible region. 
    1695        """ 
    1696        b = event.current_buffer 
    1697        event.app.layout.current_window.vertical_scroll = b.document.cursor_position_row 
    1698 
    1699    @handle("z", "-", filter=vi_navigation_mode | vi_selection_mode) 
    1700    @handle("z", "b", filter=vi_navigation_mode | vi_selection_mode) 
    1701    def _scroll_bottom(event: E) -> None: 
    1702        """ 
    1703        Scrolls the window to makes the current line the last line in the visible region. 
    1704        """ 
    1705        # We can safely set the scroll offset to zero; the Window will make 
    1706        # sure that it scrolls at least enough to make the cursor visible 
    1707        # again. 
    1708        event.app.layout.current_window.vertical_scroll = 0 
    1709 
    1710    @handle("z", "z", filter=vi_navigation_mode | vi_selection_mode) 
    1711    def _scroll_center(event: E) -> None: 
    1712        """ 
    1713        Center Window vertically around cursor. 
    1714        """ 
    1715        w = event.app.layout.current_window 
    1716        b = event.current_buffer 
    1717 
    1718        if w and w.render_info: 
    1719            info = w.render_info 
    1720 
    1721            # Calculate the offset that we need in order to position the row 
    1722            # containing the cursor in the center. 
    1723            scroll_height = info.window_height // 2 
    1724 
    1725            y = max(0, b.document.cursor_position_row - 1) 
    1726            height = 0 
    1727            while y > 0: 
    1728                line_height = info.get_height_for_line(y) 
    1729 
    1730                if height + line_height < scroll_height: 
    1731                    height += line_height 
    1732                    y -= 1 
    1733                else: 
    1734                    break 
    1735 
    1736            w.vertical_scroll = y 
    1737 
    1738    @text_object("%") 
    1739    def _goto_corresponding_bracket(event: E) -> TextObject: 
    1740        """ 
    1741        Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) 
    1742        If an 'arg' has been given, go this this % position in the file. 
    1743        """ 
    1744        buffer = event.current_buffer 
    1745 
    1746        if event._arg: 
    1747            # If 'arg' has been given, the meaning of % is to go to the 'x%' 
    1748            # row in the file. 
    1749            if 0 < event.arg <= 100: 
    1750                absolute_index = buffer.document.translate_row_col_to_index( 
    1751                    int((event.arg * buffer.document.line_count - 1) / 100), 0 
    1752                ) 
    1753                return TextObject( 
    1754                    absolute_index - buffer.document.cursor_position, 
    1755                    type=TextObjectType.LINEWISE, 
    1756                ) 
    1757            else: 
    1758                return TextObject(0)  # Do nothing. 
    1759 
    1760        else: 
    1761            # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). 
    1762            match = buffer.document.find_matching_bracket_position() 
    1763            if match: 
    1764                return TextObject(match, type=TextObjectType.INCLUSIVE) 
    1765            else: 
    1766                return TextObject(0) 
    1767 
    1768    @text_object("|") 
    1769    def _to_column(event: E) -> TextObject: 
    1770        """ 
    1771        Move to the n-th column (you may specify the argument n by typing it on 
    1772        number keys, for example, 20|). 
    1773        """ 
    1774        return TextObject( 
    1775            event.current_buffer.document.get_column_cursor_position(event.arg - 1) 
    1776        ) 
    1777 
    1778    @text_object("g", "g") 
    1779    def _goto_first_line(event: E) -> TextObject: 
    1780        """ 
    1781        Go to the start of the very first line. 
    1782        Implements 'gg', 'cgg', 'ygg' 
    1783        """ 
    1784        d = event.current_buffer.document 
    1785 
    1786        if event._arg: 
    1787            # Move to the given line. 
    1788            return TextObject( 
    1789                d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, 
    1790                type=TextObjectType.LINEWISE, 
    1791            ) 
    1792        else: 
    1793            # Move to the top of the input. 
    1794            return TextObject( 
    1795                d.get_start_of_document_position(), type=TextObjectType.LINEWISE 
    1796            ) 
    1797 
    1798    @text_object("g", "_") 
    1799    def _goto_last_line(event: E) -> TextObject: 
    1800        """ 
    1801        Go to last non-blank of line. 
    1802        'g_', 'cg_', 'yg_', etc.. 
    1803        """ 
    1804        return TextObject( 
    1805            event.current_buffer.document.last_non_blank_of_current_line_position(), 
    1806            type=TextObjectType.INCLUSIVE, 
    1807        ) 
    1808 
    1809    @text_object("g", "e") 
    1810    def _ge(event: E) -> TextObject: 
    1811        """ 
    1812        Go to last character of previous word. 
    1813        'ge', 'cge', 'yge', etc.. 
    1814        """ 
    1815        prev_end = event.current_buffer.document.find_previous_word_ending( 
    1816            count=event.arg 
    1817        ) 
    1818        return TextObject( 
    1819            prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE 
    1820        ) 
    1821 
    1822    @text_object("g", "E") 
    1823    def _gE(event: E) -> TextObject: 
    1824        """ 
    1825        Go to last character of previous WORD. 
    1826        'gE', 'cgE', 'ygE', etc.. 
    1827        """ 
    1828        prev_end = event.current_buffer.document.find_previous_word_ending( 
    1829            count=event.arg, WORD=True 
    1830        ) 
    1831        return TextObject( 
    1832            prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE 
    1833        ) 
    1834 
    1835    @text_object("g", "m") 
    1836    def _gm(event: E) -> TextObject: 
    1837        """ 
    1838        Like g0, but half a screenwidth to the right. (Or as much as possible.) 
    1839        """ 
    1840        w = event.app.layout.current_window 
    1841        buff = event.current_buffer 
    1842 
    1843        if w and w.render_info: 
    1844            width = w.render_info.window_width 
    1845            start = buff.document.get_start_of_line_position(after_whitespace=False) 
    1846            start += int(min(width / 2, len(buff.document.current_line))) 
    1847 
    1848            return TextObject(start, type=TextObjectType.INCLUSIVE) 
    1849        return TextObject(0) 
    1850 
    1851    @text_object("G") 
    1852    def _last_line(event: E) -> TextObject: 
    1853        """ 
    1854        Go to the end of the document. (If no arg has been given.) 
    1855        """ 
    1856        buf = event.current_buffer 
    1857        return TextObject( 
    1858            buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) 
    1859            - buf.cursor_position, 
    1860            type=TextObjectType.LINEWISE, 
    1861        ) 
    1862 
    1863    # 
    1864    # *** Other *** 
    1865    # 
    1866 
    1867    @handle("G", filter=has_arg) 
    1868    def _to_nth_history_line(event: E) -> None: 
    1869        """ 
    1870        If an argument is given, move to this line in the  history. (for 
    1871        example, 15G) 
    1872        """ 
    1873        event.current_buffer.go_to_history(event.arg - 1) 
    1874 
    1875    for n in "123456789": 
    1876 
    1877        @handle( 
    1878            n, 
    1879            filter=vi_navigation_mode 
    1880            | vi_selection_mode 
    1881            | vi_waiting_for_text_object_mode, 
    1882        ) 
    1883        def _arg(event: E) -> None: 
    1884            """ 
    1885            Always handle numerics in navigation mode as arg. 
    1886            """ 
    1887            event.append_to_arg_count(event.data) 
    1888 
    1889    @handle( 
    1890        "0", 
    1891        filter=( 
    1892            vi_navigation_mode | vi_selection_mode | vi_waiting_for_text_object_mode 
    1893        ) 
    1894        & has_arg, 
    1895    ) 
    1896    def _0_arg(event: E) -> None: 
    1897        """ 
    1898        Zero when an argument was already give. 
    1899        """ 
    1900        event.append_to_arg_count(event.data) 
    1901 
    1902    @handle(Keys.Any, filter=vi_replace_mode) 
    1903    def _insert_text(event: E) -> None: 
    1904        """ 
    1905        Insert data at cursor position. 
    1906        """ 
    1907        event.current_buffer.insert_text(event.data, overwrite=True) 
    1908 
    1909    @handle(Keys.Any, filter=vi_replace_single_mode) 
    1910    def _replace_single(event: E) -> None: 
    1911        """ 
    1912        Replace single character at cursor position. 
    1913        """ 
    1914        event.current_buffer.insert_text(event.data, overwrite=True) 
    1915        event.current_buffer.cursor_position -= 1 
    1916        event.app.vi_state.input_mode = InputMode.NAVIGATION 
    1917 
    1918    @handle( 
    1919        Keys.Any, 
    1920        filter=vi_insert_multiple_mode, 
    1921        save_before=(lambda e: not e.is_repeat), 
    1922    ) 
    1923    def _insert_text_multiple_cursors(event: E) -> None: 
    1924        """ 
    1925        Insert data at multiple cursor positions at once. 
    1926        (Usually a result of pressing 'I' or 'A' in block-selection mode.) 
    1927        """ 
    1928        buff = event.current_buffer 
    1929        original_text = buff.text 
    1930 
    1931        # Construct new text. 
    1932        text = [] 
    1933        p = 0 
    1934 
    1935        for p2 in buff.multiple_cursor_positions: 
    1936            text.append(original_text[p:p2]) 
    1937            text.append(event.data) 
    1938            p = p2 
    1939 
    1940        text.append(original_text[p:]) 
    1941 
    1942        # Shift all cursor positions. 
    1943        new_cursor_positions = [ 
    1944            pos + i + 1 for i, pos in enumerate(buff.multiple_cursor_positions) 
    1945        ] 
    1946 
    1947        # Set result. 
    1948        buff.text = "".join(text) 
    1949        buff.multiple_cursor_positions = new_cursor_positions 
    1950        buff.cursor_position += 1 
    1951 
    1952    @handle("backspace", filter=vi_insert_multiple_mode) 
    1953    def _delete_before_multiple_cursors(event: E) -> None: 
    1954        """ 
    1955        Backspace, using multiple cursors. 
    1956        """ 
    1957        buff = event.current_buffer 
    1958        original_text = buff.text 
    1959 
    1960        # Construct new text. 
    1961        deleted_something = False 
    1962        text = [] 
    1963        p = 0 
    1964 
    1965        for p2 in buff.multiple_cursor_positions: 
    1966            if p2 > 0 and original_text[p2 - 1] != "\n":  # Don't delete across lines. 
    1967                text.append(original_text[p : p2 - 1]) 
    1968                deleted_something = True 
    1969            else: 
    1970                text.append(original_text[p:p2]) 
    1971            p = p2 
    1972 
    1973        text.append(original_text[p:]) 
    1974 
    1975        if deleted_something: 
    1976            # Shift all cursor positions. 
    1977            lengths = [len(part) for part in text[:-1]] 
    1978            new_cursor_positions = list(accumulate(lengths)) 
    1979 
    1980            # Set result. 
    1981            buff.text = "".join(text) 
    1982            buff.multiple_cursor_positions = new_cursor_positions 
    1983            buff.cursor_position -= 1 
    1984        else: 
    1985            event.app.output.bell() 
    1986 
    1987    @handle("delete", filter=vi_insert_multiple_mode) 
    1988    def _delete_after_multiple_cursors(event: E) -> None: 
    1989        """ 
    1990        Delete, using multiple cursors. 
    1991        """ 
    1992        buff = event.current_buffer 
    1993        original_text = buff.text 
    1994 
    1995        # Construct new text. 
    1996        deleted_something = False 
    1997        text = [] 
    1998        new_cursor_positions = [] 
    1999        p = 0 
    2000 
    2001        for p2 in buff.multiple_cursor_positions: 
    2002            text.append(original_text[p:p2]) 
    2003            if p2 >= len(original_text) or original_text[p2] == "\n": 
    2004                # Don't delete across lines. 
    2005                p = p2 
    2006            else: 
    2007                p = p2 + 1 
    2008                deleted_something = True 
    2009 
    2010        text.append(original_text[p:]) 
    2011 
    2012        if deleted_something: 
    2013            # Shift all cursor positions. 
    2014            lengths = [len(part) for part in text[:-1]] 
    2015            new_cursor_positions = list(accumulate(lengths)) 
    2016 
    2017            # Set result. 
    2018            buff.text = "".join(text) 
    2019            buff.multiple_cursor_positions = new_cursor_positions 
    2020        else: 
    2021            event.app.output.bell() 
    2022 
    2023    @handle("left", filter=vi_insert_multiple_mode) 
    2024    def _left_multiple(event: E) -> None: 
    2025        """ 
    2026        Move all cursors to the left. 
    2027        (But keep all cursors on the same line.) 
    2028        """ 
    2029        buff = event.current_buffer 
    2030        new_positions = [] 
    2031 
    2032        for p in buff.multiple_cursor_positions: 
    2033            if buff.document.translate_index_to_position(p)[1] > 0: 
    2034                p -= 1 
    2035            new_positions.append(p) 
    2036 
    2037        buff.multiple_cursor_positions = new_positions 
    2038 
    2039        if buff.document.cursor_position_col > 0: 
    2040            buff.cursor_position -= 1 
    2041 
    2042    @handle("right", filter=vi_insert_multiple_mode) 
    2043    def _right_multiple(event: E) -> None: 
    2044        """ 
    2045        Move all cursors to the right. 
    2046        (But keep all cursors on the same line.) 
    2047        """ 
    2048        buff = event.current_buffer 
    2049        new_positions = [] 
    2050 
    2051        for p in buff.multiple_cursor_positions: 
    2052            row, column = buff.document.translate_index_to_position(p) 
    2053            if column < len(buff.document.lines[row]): 
    2054                p += 1 
    2055            new_positions.append(p) 
    2056 
    2057        buff.multiple_cursor_positions = new_positions 
    2058 
    2059        if not buff.document.is_cursor_at_the_end_of_line: 
    2060            buff.cursor_position += 1 
    2061 
    2062    @handle("up", filter=vi_insert_multiple_mode) 
    2063    @handle("down", filter=vi_insert_multiple_mode) 
    2064    def _updown_multiple(event: E) -> None: 
    2065        """ 
    2066        Ignore all up/down key presses when in multiple cursor mode. 
    2067        """ 
    2068 
    2069    @handle("c-x", "c-l", filter=vi_insert_mode) 
    2070    def _complete_line(event: E) -> None: 
    2071        """ 
    2072        Pressing the ControlX - ControlL sequence in Vi mode does line 
    2073        completion based on the other lines in the document and the history. 
    2074        """ 
    2075        event.current_buffer.start_history_lines_completion() 
    2076 
    2077    @handle("c-x", "c-f", filter=vi_insert_mode) 
    2078    def _complete_filename(event: E) -> None: 
    2079        """ 
    2080        Complete file names. 
    2081        """ 
    2082        # TODO 
    2083        pass 
    2084 
    2085    @handle("c-k", filter=vi_insert_mode | vi_replace_mode) 
    2086    def _digraph(event: E) -> None: 
    2087        """ 
    2088        Go into digraph mode. 
    2089        """ 
    2090        event.app.vi_state.waiting_for_digraph = True 
    2091 
    2092    @handle(Keys.Any, filter=vi_digraph_mode & ~digraph_symbol_1_given) 
    2093    def _digraph1(event: E) -> None: 
    2094        """ 
    2095        First digraph symbol. 
    2096        """ 
    2097        event.app.vi_state.digraph_symbol1 = event.data 
    2098 
    2099    @handle(Keys.Any, filter=vi_digraph_mode & digraph_symbol_1_given) 
    2100    def _create_digraph(event: E) -> None: 
    2101        """ 
    2102        Insert digraph. 
    2103        """ 
    2104        try: 
    2105            # Lookup. 
    2106            code: tuple[str, str] = ( 
    2107                event.app.vi_state.digraph_symbol1 or "", 
    2108                event.data, 
    2109            ) 
    2110            if code not in DIGRAPHS: 
    2111                code = code[::-1]  # Try reversing. 
    2112            symbol = DIGRAPHS[code] 
    2113        except KeyError: 
    2114            # Unknown digraph. 
    2115            event.app.output.bell() 
    2116        else: 
    2117            # Insert digraph. 
    2118            overwrite = event.app.vi_state.input_mode == InputMode.REPLACE 
    2119            event.current_buffer.insert_text(chr(symbol), overwrite=overwrite) 
    2120            event.app.vi_state.waiting_for_digraph = False 
    2121        finally: 
    2122            event.app.vi_state.waiting_for_digraph = False 
    2123            event.app.vi_state.digraph_symbol1 = None 
    2124 
    2125    @handle("c-o", filter=vi_insert_mode | vi_replace_mode) 
    2126    def _quick_normal_mode(event: E) -> None: 
    2127        """ 
    2128        Go into normal mode for one single action. 
    2129        """ 
    2130        event.app.vi_state.temporary_navigation_mode = True 
    2131 
    2132    @handle("q", Keys.Any, filter=vi_navigation_mode & ~vi_recording_macro) 
    2133    def _start_macro(event: E) -> None: 
    2134        """ 
    2135        Start recording macro. 
    2136        """ 
    2137        c = event.key_sequence[1].data 
    2138        if c in vi_register_names: 
    2139            vi_state = event.app.vi_state 
    2140 
    2141            vi_state.recording_register = c 
    2142            vi_state.current_recording = "" 
    2143 
    2144    @handle("q", filter=vi_navigation_mode & vi_recording_macro) 
    2145    def _stop_macro(event: E) -> None: 
    2146        """ 
    2147        Stop recording macro. 
    2148        """ 
    2149        vi_state = event.app.vi_state 
    2150 
    2151        # Store and stop recording. 
    2152        if vi_state.recording_register: 
    2153            vi_state.named_registers[vi_state.recording_register] = ClipboardData( 
    2154                vi_state.current_recording 
    2155            ) 
    2156            vi_state.recording_register = None 
    2157            vi_state.current_recording = "" 
    2158 
    2159    @handle("@", Keys.Any, filter=vi_navigation_mode, record_in_macro=False) 
    2160    def _execute_macro(event: E) -> None: 
    2161        """ 
    2162        Execute macro. 
    2163 
    2164        Notice that we pass `record_in_macro=False`. This ensures that the `@x` 
    2165        keys don't appear in the recording itself. This function inserts the 
    2166        body of the called macro back into the KeyProcessor, so these keys will 
    2167        be added later on to the macro of their handlers have 
    2168        `record_in_macro=True`. 
    2169        """ 
    2170        # Retrieve macro. 
    2171        c = event.key_sequence[1].data 
    2172        try: 
    2173            macro = event.app.vi_state.named_registers[c] 
    2174        except KeyError: 
    2175            return 
    2176 
    2177        # Expand macro (which is a string in the register), in individual keys. 
    2178        # Use vt100 parser for this. 
    2179        keys: list[KeyPress] = [] 
    2180 
    2181        parser = Vt100Parser(keys.append) 
    2182        parser.feed(macro.text) 
    2183        parser.flush() 
    2184 
    2185        # Now feed keys back to the input processor. 
    2186        for _ in range(event.arg): 
    2187            event.app.key_processor.feed_multiple(keys, first=True) 
    2188 
    2189    return ConditionalKeyBindings(key_bindings, vi_mode) 
    2190 
    2191 
    2192def load_vi_search_bindings() -> KeyBindingsBase: 
    2193    key_bindings = KeyBindings() 
    2194    handle = key_bindings.add 
    2195    from . import search 
    2196 
    2197    # Vi-style forward search. 
    2198    handle( 
    2199        "/", 
    2200        filter=(vi_navigation_mode | vi_selection_mode) & ~vi_search_direction_reversed, 
    2201    )(search.start_forward_incremental_search) 
    2202    handle( 
    2203        "?", 
    2204        filter=(vi_navigation_mode | vi_selection_mode) & vi_search_direction_reversed, 
    2205    )(search.start_forward_incremental_search) 
    2206    handle("c-s")(search.start_forward_incremental_search) 
    2207 
    2208    # Vi-style backward search. 
    2209    handle( 
    2210        "?", 
    2211        filter=(vi_navigation_mode | vi_selection_mode) & ~vi_search_direction_reversed, 
    2212    )(search.start_reverse_incremental_search) 
    2213    handle( 
    2214        "/", 
    2215        filter=(vi_navigation_mode | vi_selection_mode) & vi_search_direction_reversed, 
    2216    )(search.start_reverse_incremental_search) 
    2217    handle("c-r")(search.start_reverse_incremental_search) 
    2218 
    2219    # Apply the search. (At the / or ? prompt.) 
    2220    handle("enter", filter=is_searching)(search.accept_search) 
    2221 
    2222    handle("c-r", filter=is_searching)(search.reverse_incremental_search) 
    2223    handle("c-s", filter=is_searching)(search.forward_incremental_search) 
    2224 
    2225    handle("c-c")(search.abort_search) 
    2226    handle("c-g")(search.abort_search) 
    2227    handle("backspace", filter=search_buffer_is_empty)(search.abort_search) 
    2228 
    2229    # Handle escape. This should accept the search, just like readline. 
    2230    # `abort_search` would be a meaningful alternative. 
    2231    handle("escape")(search.accept_search) 
    2232 
    2233    return ConditionalKeyBindings(key_bindings, vi_mode)