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