Ejemplo n.º 1
0
def load_vi_open_in_editor_bindings(registry, vi_state, filter=None):
    """
    Pressing 'v' in navigation mode will open the buffer in an external editor.
    """
    navigation_mode = ViStateFilter(vi_state, InputMode.NAVIGATION) & ~ filters.HasSelection()
    handle = create_handle_decorator(registry, filter)

    @handle('v', filter=navigation_mode)
    def _(event):
        event.current_buffer.open_in_editor(event.cli)
Ejemplo n.º 2
0
def load_emacs_open_in_editor_bindings(registry, filter=None):
    """
    Pressing C-X C-E will open the buffer in an external editor.
    """
    handle = create_handle_decorator(registry, filter)
    has_selection = filters.HasSelection()

    @handle(Keys.ControlX, Keys.ControlE, filter=~has_selection)
    def _(event):
        """
        Open editor.
        """
        event.current_buffer.open_in_editor(event.cli)
Ejemplo n.º 3
0
def load_vi_system_bindings(registry,
                            vi_state,
                            filter=None,
                            system_buffer_name='system'):
    assert isinstance(vi_state, ViState)

    has_focus = filters.HasFocus(system_buffer_name)
    navigation_mode = ViStateFilter(
        vi_state, InputMode.NAVIGATION) & ~filters.HasSelection()

    handle = create_handle_decorator(registry, filter)

    @handle('!', filter=~has_focus & navigation_mode)
    def _(event):
        """
        '!' opens the system prompt.
        """
        event.cli.focus_stack.push(system_buffer_name)
        vi_state.input_mode = InputMode.INSERT

    @handle(Keys.Escape, filter=has_focus)
    @handle(Keys.ControlC, filter=has_focus)
    def _(event):
        """
        Cancel system prompt.
        """
        vi_state.input_mode = InputMode.NAVIGATION
        event.cli.buffers[system_buffer_name].reset()
        event.cli.focus_stack.pop()

    @handle(Keys.ControlJ, filter=has_focus)
    def _(event):
        """
        Run system command.
        """
        vi_state.input_mode = InputMode.NAVIGATION

        system_buffer = event.cli.buffers[system_buffer_name]
        event.cli.run_system_command(system_buffer.text)
        system_buffer.reset(append_to_history=True)

        # Focus previous buffer again.
        event.cli.focus_stack.pop()
Ejemplo n.º 4
0
def load_vi_bindings(registry, vi_state, filter=None):
    """
    Vi extensions.

    # Overview of Readline Vi commands:
    # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf
    """
    assert isinstance(vi_state, ViState)

    load_basic_bindings(registry, filter)
    handle = create_handle_decorator(registry, filter)

    insert_mode = ViStateFilter(vi_state,
                                InputMode.INSERT) & ~filters.HasSelection()
    navigation_mode = ViStateFilter(
        vi_state, InputMode.NAVIGATION) & ~filters.HasSelection()
    replace_mode = ViStateFilter(vi_state,
                                 InputMode.REPLACE) & ~filters.HasSelection()
    selection_mode = filters.HasSelection()

    vi_transform_functions = [
        # Rot 13 transformation
        (('g', '?'), lambda string: codecs.encode(string, 'rot_13')),

        # To lowercase
        (('g', 'u'), lambda string: string.lower()),

        # To uppercase.
        (('g', 'U'), lambda string: string.upper()),

        # Swap case.
        # (XXX: If we would implement 'tildeop', the 'g' prefix is not required.)
        (('g', '~'), lambda string: string.swapcase()),
    ]

    def check_cursor_position(event):
        """
        After every command, make sure that if we are in navigation mode, we
        never put the cursor after the last character of a line. (Unless it's
        an empty line.)
        """
        buffer = event.current_buffer

        if ((filter is None or filter(event.cli))
                and  # First make sure that this key bindings are active.
                vi_state.input_mode == InputMode.NAVIGATION and
                buffer.document.is_cursor_at_the_end_of_line and
                len(buffer.document.current_line) > 0):
            buffer.cursor_position -= 1

    registry.onHandlerCalled += check_cursor_position

    @handle(Keys.Escape)
    def _(event):
        """
        Escape goes to vi navigation mode.
        """
        buffer = event.current_buffer

        if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE):
            buffer.cursor_position += buffer.document.get_cursor_left_position(
            )

        vi_state.input_mode = InputMode.NAVIGATION

        if bool(buffer.selection_state):
            buffer.exit_selection()

    @handle('k', filter=selection_mode)
    def _(event):
        """
        Arrow up in selection mode.
        """
        event.current_buffer.cursor_up(count=event.arg)

    @handle('j', filter=selection_mode)
    def _(event):
        """
        Arrow down in selection mode.
        """
        event.current_buffer.cursor_down(count=event.arg)

    @handle('k', filter=navigation_mode)
    @handle(Keys.Up, filter=navigation_mode)
    @handle(Keys.ControlP, filter=navigation_mode)
    def _(event):
        """
        Arrow up and ControlP in navigation mode go up.
        """
        b = event.current_buffer
        b.auto_up(count=event.arg,
                  history_search=b.enable_history_search(event.cli))

    @handle('j', filter=navigation_mode)
    @handle(Keys.Down, filter=navigation_mode)
    @handle(Keys.ControlN, filter=navigation_mode)
    def _(event):
        """
        Arrow down and Control-N in navigation mode.
        """
        b = event.current_buffer
        b.auto_down(count=event.arg,
                    history_search=b.enable_history_search(event.cli))

    @handle(Keys.Backspace, filter=navigation_mode)
    def _(event):
        """
        In navigation-mode, move cursor.
        """
        event.current_buffer.cursor_position += \
            event.current_buffer.document.get_cursor_left_position(count=event.arg)

    @handle(Keys.ControlV, Keys.Any, filter=insert_mode)
    def _(event):
        """
        Insert a character literally (quoted insert).
        """
        event.current_buffer.insert_text(event.data, overwrite=False)

    @handle(Keys.ControlN, filter=insert_mode)
    def _(event):
        b = event.current_buffer

        if b.complete_state:
            b.complete_next()
        else:
            event.cli.start_completion(select_first=True)

    @handle(Keys.ControlP, filter=insert_mode)
    def _(event):
        """
        Control-P: To previous completion.
        """
        b = event.current_buffer

        if b.complete_state:
            b.complete_previous()
        else:
            event.cli.start_completion(select_last=True)

    @handle(Keys.ControlY, filter=insert_mode)
    def _(event):
        """
        Accept current completion.
        """
        event.current_buffer.complete_state = None

    @handle(Keys.ControlE, filter=insert_mode)
    def _(event):
        """
        Cancel completion. Go back to originally typed text.
        """
        event.current_buffer.cancel_completion()

    @handle(Keys.ControlJ, filter=navigation_mode)
    def _(event):
        """
        In navigation mode, pressing enter will always return the input.
        """
        b = event.current_buffer

        if b.accept_action.is_returnable:
            b.accept_action.validate_and_handle(event.cli, b)

    # ** In navigation mode **

    # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html

    @handle('a', filter=navigation_mode)
    def _(event):
        event.current_buffer.cursor_position += event.current_buffer.document.get_cursor_right_position(
        )
        vi_state.input_mode = InputMode.INSERT

    @handle('A', filter=navigation_mode)
    def _(event):
        event.current_buffer.cursor_position += event.current_buffer.document.get_end_of_line_position(
        )
        vi_state.input_mode = InputMode.INSERT

    @handle('C', filter=navigation_mode)
    def _(event):
        """
        # Change to end of line.
        # Same as 'c$' (which is implemented elsewhere.)
        """
        buffer = event.current_buffer

        deleted = buffer.delete(
            count=buffer.document.get_end_of_line_position())
        event.cli.clipboard.set_text(deleted)
        vi_state.input_mode = InputMode.INSERT

    @handle('c', 'c', filter=navigation_mode)
    @handle('S', filter=navigation_mode)
    def _(event):  # TODO: implement 'arg'
        """
        Change current line
        """
        buffer = event.current_buffer

        # We copy the whole line.
        data = ClipboardData(buffer.document.current_line, SelectionType.LINES)
        event.cli.clipboard.set_data(data)

        # But we delete after the whitespace
        buffer.cursor_position += buffer.document.get_start_of_line_position(
            after_whitespace=True)
        buffer.delete(count=buffer.document.get_end_of_line_position())
        vi_state.input_mode = InputMode.INSERT

    @handle('D', filter=navigation_mode)
    def _(event):
        buffer = event.current_buffer
        deleted = buffer.delete(
            count=buffer.document.get_end_of_line_position())
        event.cli.clipboard.set_text(deleted)

    @handle('d', 'd', filter=navigation_mode)
    def _(event):
        """
        Delete line. (Or the following 'n' lines.)
        """
        buffer = event.current_buffer

        # Split string in before/deleted/after text.
        lines = buffer.document.lines

        before = '\n'.join(lines[:buffer.document.cursor_position_row])
        deleted = '\n'.join(
            lines[buffer.document.
                  cursor_position_row:buffer.document.cursor_position_row +
                  event.arg])
        after = '\n'.join(lines[buffer.document.cursor_position_row +
                                event.arg:])

        # Set new text.
        if before and after:
            before = before + '\n'

        # Set text and cursor position.
        buffer.document = Document(
            text=before + after,
            # Cursor At the start of the first 'after' line, after the leading whitespace.
            cursor_position=len(before) + len(after) - len(after.lstrip(' ')))

        # Set clipboard data
        event.cli.clipboard.set_data(
            ClipboardData(deleted, SelectionType.LINES))

    @handle('i', filter=navigation_mode)
    def _(event):
        vi_state.input_mode = InputMode.INSERT

    @handle('I', filter=navigation_mode)
    def _(event):
        vi_state.input_mode = InputMode.INSERT
        event.current_buffer.cursor_position += event.current_buffer.document.get_start_of_line_position(
            after_whitespace=True)

    @handle('J', filter=navigation_mode)
    def _(event):
        """ Join lines. """
        for i in range(event.arg):
            event.current_buffer.join_next_line()

    @handle('J', filter=selection_mode)
    def _(event):
        """ Join selected lines. """
        event.current_buffer.join_selected_lines()

    @handle('n', filter=navigation_mode)
    def _(event
          ):  # XXX: use `change_delete_move_yank_handler` and implement 'arg'
        """
        Search next.
        """
        event.current_buffer.apply_search(event.cli.search_state)

    @handle('N', filter=navigation_mode)
    def _(event
          ):  # TODO: use `change_delete_move_yank_handler` and implement 'arg'
        """
        Search previous.
        """
        event.current_buffer.apply_search(~event.cli.search_state)

    @handle('p', filter=navigation_mode)
    def _(event):
        """
        Paste after
        """
        event.current_buffer.paste_clipboard_data(
            event.cli.clipboard.get_data(), count=event.arg)

    @handle('P', filter=navigation_mode)
    def _(event):
        """
        Paste before
        """
        event.current_buffer.paste_clipboard_data(
            event.cli.clipboard.get_data(), before=True, count=event.arg)

    @handle('r', Keys.Any, filter=navigation_mode)
    def _(event):
        """
        Replace single character under cursor
        """
        event.current_buffer.insert_text(event.data * event.arg,
                                         overwrite=True)
        event.current_buffer.cursor_position -= 1

    @handle('R', filter=navigation_mode)
    def _(event):
        """
        Go to 'replace'-mode.
        """
        vi_state.input_mode = InputMode.REPLACE

    @handle('s', filter=navigation_mode)
    def _(event):
        """
        Substitute with new text
        (Delete character(s) and go to insert mode.)
        """
        text = event.current_buffer.delete(count=event.arg)
        event.cli.clipboard.set_text(text)
        vi_state.input_mode = InputMode.INSERT

    @handle('u', filter=navigation_mode, save_before=False)
    def _(event):
        for i in range(event.arg):
            event.current_buffer.undo()

    @handle('v', filter=navigation_mode)
    def _(event):
        """
        Start characters selection.
        """
        event.current_buffer.start_selection(
            selection_type=SelectionType.CHARACTERS)

    @handle('V', filter=navigation_mode)
    def _(event):
        """
        Start lines selection.
        """
        event.current_buffer.start_selection(
            selection_type=SelectionType.LINES)

    @handle('a', 'w', filter=selection_mode)
    @handle('a', 'W', filter=selection_mode)
    def _(event):
        """
        Switch from visual linewise mode to visual characterwise mode.
        """
        buffer = event.current_buffer

        if buffer.selection_state and buffer.selection_state.type == SelectionType.LINES:
            buffer.selection_state.type = SelectionType.CHARACTERS

    @handle('x', filter=navigation_mode)
    def _(event):
        """
        Delete character.
        """
        text = event.current_buffer.delete(count=event.arg)
        event.cli.clipboard.set_text(text)

    @handle('x', filter=selection_mode)
    @handle('d', filter=selection_mode)
    def _(event):
        """
        Cut selection.
        """
        clipboard_data = event.current_buffer.cut_selection()
        event.cli.clipboard.set_data(clipboard_data)

    @handle('c', filter=selection_mode)
    def _(event):
        """
        Change selection (cut and go to insert mode).
        """
        clipboard_data = event.current_buffer.cut_selection()
        event.cli.clipboard.set_data(clipboard_data)
        vi_state.input_mode = InputMode.INSERT

    @handle('y', filter=selection_mode)
    def _(event):
        """
        Copy selection.
        """
        clipboard_data = event.current_buffer.copy_selection()
        event.cli.clipboard.set_data(clipboard_data)

    @handle('X', filter=navigation_mode)
    def _(event):
        text = event.current_buffer.delete_before_cursor()
        event.cli.clipboard.set_text(text)

    @handle('y', 'y', filter=navigation_mode)
    @handle('Y', filter=navigation_mode)
    def _(event):
        """
        Yank the whole line.
        """
        text = '\n'.join(
            event.current_buffer.document.lines_from_current[:event.arg])
        event.cli.clipboard.set_data(ClipboardData(text, SelectionType.LINES))

    @handle('+', filter=navigation_mode)
    def _(event):
        """
        Move to first non whitespace of next line
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_cursor_down_position(
            count=event.arg)
        buffer.cursor_position += buffer.document.get_start_of_line_position(
            after_whitespace=True)

    @handle('-', filter=navigation_mode)
    def _(event):
        """
        Move to first non whitespace of previous line
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_cursor_up_position(
            count=event.arg)
        buffer.cursor_position += buffer.document.get_start_of_line_position(
            after_whitespace=True)

    @handle('>', '>', filter=navigation_mode)
    def _(event):
        """
        Indent lines.
        """
        buffer = event.current_buffer
        current_row = buffer.document.cursor_position_row
        indent(buffer, current_row, current_row + event.arg)

    @handle('<', '<', filter=navigation_mode)
    def _(event):
        """
        Unindent lines.
        """
        current_row = event.current_buffer.document.cursor_position_row
        unindent(event.current_buffer, current_row, current_row + event.arg)

    @handle('>', filter=selection_mode)
    def _(event):
        """
        Indent selection
        """
        buffer = event.current_buffer
        selection_type = buffer.selection_state.type

        if selection_type == SelectionType.LINES:
            from_, to = buffer.document.selection_range()
            from_, _ = buffer.document.translate_index_to_position(from_)
            to, _ = buffer.document.translate_index_to_position(to)

            indent(
                buffer, from_ - 1, to, count=event.arg
            )  # XXX: why does translate_index_to_position return 1-based indexing???

    @handle('<', filter=selection_mode)
    def _(event):
        """
        Unindent selection
        """
        buffer = event.current_buffer
        selection_type = buffer.selection_state.type

        if selection_type == SelectionType.LINES:
            from_, to = buffer.document.selection_range()
            from_, _ = buffer.document.translate_index_to_position(from_)
            to, _ = buffer.document.translate_index_to_position(to)

            unindent(buffer, from_ - 1, to, count=event.arg)

    @handle('O', filter=navigation_mode)
    def _(event):
        """
        Open line above and enter insertion mode
        """
        event.current_buffer.insert_line_above(
            copy_margin=not event.cli.in_paste_mode)
        vi_state.input_mode = InputMode.INSERT

    @handle('o', filter=navigation_mode)
    def _(event):
        """
        Open line below and enter insertion mode
        """
        event.current_buffer.insert_line_below(
            copy_margin=not event.cli.in_paste_mode)
        vi_state.input_mode = InputMode.INSERT

    @handle('~', filter=navigation_mode)
    def _(event):
        """
        Reverse case of current character and move cursor forward.
        """
        buffer = event.current_buffer
        c = buffer.document.current_char

        if c is not None and c != '\n':
            c = (c.upper() if c.islower() else c.lower())
            buffer.insert_text(c, overwrite=True)

    @handle('#', filter=navigation_mode)
    def _(event):
        """
        Go to previous occurence of this word.
        """
        b = event.cli.current_buffer

        search_state = event.cli.search_state
        search_state.text = b.document.get_word_under_cursor()
        search_state.direction = IncrementalSearchDirection.BACKWARD

        b.apply_search(search_state)

    @handle('*', filter=navigation_mode)
    def _(event):
        """
        Go to next occurence of this word.
        """
        b = event.cli.current_buffer

        search_state = event.cli.search_state
        search_state.text = b.document.get_word_under_cursor()
        search_state.direction = IncrementalSearchDirection.FORWARD

        b.apply_search(search_state)

    @handle('(', filter=navigation_mode)
    def _(event):
        # TODO: go to begin of sentence.
        pass

    @handle(')', filter=navigation_mode)
    def _(event):
        # TODO: go to end of sentence.
        pass

    def change_delete_move_yank_handler(*keys, **kw):
        """
        Register a change/delete/move/yank handlers. e.g.  'dw'/'cw'/'w'/'yw'
        The decorated function should return a ``CursorRegion``.
        This decorator will create both the 'change', 'delete' and move variants,
        based on that ``CursorRegion``.

        When there is nothing selected yet, this will also handle the "visual"
        binding. E.g. 'viw' should select the current word.
        """
        no_move_handler = kw.pop('no_move_handler', False)

        # TODO: Also do '>' and '<' indent/unindent operators.
        # TODO: Also "gq": text formatting
        #  See: :help motion.txt
        def decorator(func):
            if not no_move_handler:

                @handle(*keys, filter=navigation_mode | selection_mode)
                def move(event):
                    """ Create move handler. """
                    region = func(event)
                    event.current_buffer.cursor_position += region.start

            def create_transform_handler(transform_func, *a):
                @handle(*(a + keys), filter=navigation_mode)
                def _(event):
                    """ Apply transformation (uppercase, lowercase, rot13, swap case). """
                    region = func(event)
                    start, end = region.sorted()
                    buffer = event.current_buffer

                    # Transform.
                    buffer.transform_region(buffer.cursor_position + start,
                                            buffer.cursor_position + end,
                                            transform_func)

                    # Move cursor
                    buffer.cursor_position += (region.end or region.start)

            for k, f in vi_transform_functions:
                create_transform_handler(f, *k)

            @handle('y', *keys, filter=navigation_mode)
            def yank_handler(event):
                """ Create yank handler. """
                region = func(event)
                buffer = event.current_buffer

                start, end = region.sorted()
                substring = buffer.text[buffer.cursor_position +
                                        start:buffer.cursor_position + end]

                if substring:
                    event.cli.clipboard.set_text(substring)

            @handle('v', *keys, filter=navigation_mode)
            def visual_handler(event):
                """ Create visual handler. """
                region = func(event)
                buffer = event.current_buffer

                start, end = region.sorted()
                end += buffer.cursor_position - 1

                buffer.cursor_position += start
                buffer.start_selection()
                buffer.cursor_position = end

            def create(delete_only):
                """ Create delete and change handlers. """
                @handle('cd'[delete_only], *keys, filter=navigation_mode)
                @handle('cd'[delete_only], *keys, filter=navigation_mode)
                def _(event):
                    region = func(event)
                    deleted = ''
                    buffer = event.current_buffer

                    if region:
                        start, end = region.sorted()

                        # Move to the start of the region.
                        buffer.cursor_position += start

                        # Delete until end of region.
                        deleted = buffer.delete(count=end - start)

                    # Set deleted/changed text to clipboard.
                    if deleted:
                        event.cli.clipboard.set_text(deleted)

                    # Only go back to insert mode in case of 'change'.
                    if not delete_only:
                        vi_state.input_mode = InputMode.INSERT

            create(True)
            create(False)
            return func

        return decorator

    @change_delete_move_yank_handler('b')
    def _(event):
        """ Move one word or token left. """
        return CursorRegion(
            event.current_buffer.document.find_start_of_previous_word(
                count=event.arg) or 0)

    @change_delete_move_yank_handler('B')
    def _(event):
        """ Move one non-blank word left """
        return CursorRegion(
            event.current_buffer.document.find_start_of_previous_word(
                count=event.arg, WORD=True) or 0)

    @change_delete_move_yank_handler('$')
    def key_dollar(event):
        """ 'c$', 'd$' and '$':  Delete/change/move until end of line. """
        return CursorRegion(
            event.current_buffer.document.get_end_of_line_position())

    @change_delete_move_yank_handler('w')
    def _(event):
        """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word.  """
        return CursorRegion(
            event.current_buffer.document.find_next_word_beginning(
                count=event.arg)
            or event.current_buffer.document.get_end_of_document_position())

    @change_delete_move_yank_handler('W')
    def _(event):
        """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD.  """
        return CursorRegion(
            event.current_buffer.document.find_next_word_beginning(
                count=event.arg, WORD=True)
            or event.current_buffer.document.get_end_of_document_position())

    @change_delete_move_yank_handler('e')
    def _(event):
        """ End of 'word': 'ce', 'de', 'e' """
        end = event.current_buffer.document.find_next_word_ending(
            count=event.arg)
        return CursorRegion(end - 1 if end else 0)

    @change_delete_move_yank_handler('E')
    def _(event):
        """ End of 'WORD': 'cE', 'dE', 'E' """
        end = event.current_buffer.document.find_next_word_ending(
            count=event.arg, WORD=True)
        return CursorRegion(end - 1 if end else 0)

    @change_delete_move_yank_handler('i', 'w', no_move_handler=True)
    def _(event):
        """ Inner 'word': ciw and diw """
        start, end = event.current_buffer.document.find_boundaries_of_current_word(
        )
        return CursorRegion(start, end)

    @change_delete_move_yank_handler('a', 'w', no_move_handler=True)
    def _(event):
        """ A 'word': caw and daw """
        start, end = event.current_buffer.document.find_boundaries_of_current_word(
            include_trailing_whitespace=True)
        return CursorRegion(start, end)

    @change_delete_move_yank_handler('i', 'W', no_move_handler=True)
    def _(event):
        """ Inner 'WORD': ciW and diW """
        start, end = event.current_buffer.document.find_boundaries_of_current_word(
            WORD=True)
        return CursorRegion(start, end)

    @change_delete_move_yank_handler('a', 'W', no_move_handler=True)
    def _(event):
        """ A 'WORD': caw and daw """
        start, end = event.current_buffer.document.find_boundaries_of_current_word(
            WORD=True, include_trailing_whitespace=True)
        return CursorRegion(start, end)

    @change_delete_move_yank_handler('^')
    def key_circumflex(event):
        """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """
        return CursorRegion(
            event.current_buffer.document.get_start_of_line_position(
                after_whitespace=True))

    @change_delete_move_yank_handler('0', no_move_handler=True)
    def key_zero(event):
        """
        'c0', 'd0': Hard start of line, before whitespace.
        (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.)
        """
        return CursorRegion(
            event.current_buffer.document.get_start_of_line_position(
                after_whitespace=False))

    def create_ci_ca_handles(ci_start, ci_end, inner):
        # TODO: 'dab', 'dib', (brackets or block) 'daB', 'diB', Braces.
        # TODO: 'dat', 'dit', (tags (like xml)
        """
        Delete/Change string between this start and stop character. But keep these characters.
        This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations.
        """
        @change_delete_move_yank_handler('ai'[inner],
                                         ci_start,
                                         no_move_handler=True)
        @change_delete_move_yank_handler('ai'[inner],
                                         ci_end,
                                         no_move_handler=True)
        def _(event):
            start = event.current_buffer.document.find_backwards(
                ci_start, in_current_line=False)
            end = event.current_buffer.document.find(ci_end,
                                                     in_current_line=False)

            if start is not None and end is not None:
                offset = 0 if inner else 1
                return CursorRegion(start + 1 - offset, end + offset)
            else:
                # Nothing found.
                return CursorRegion(0)

    for inner in (False, True):
        for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"),
                                 ('[', ']'), ('<', '>'), ('{', '}'),
                                 ('(', ')')]:
            create_ci_ca_handles(ci_start, ci_end, inner)

    @change_delete_move_yank_handler('{')
    def _(event):
        """
        Move to previous blank-line separated section.
        Implements '{', 'c{', 'd{', 'y{'
        """
        match_func = lambda text: not text or text.isspace()
        line_index = event.current_buffer.document.find_previous_matching_line(
            match_func=match_func, count=event.arg)

        if line_index:
            index = event.current_buffer.document.get_cursor_up_position(
                count=-line_index)
        else:
            index = 0
        return CursorRegion(index)

    @change_delete_move_yank_handler('}')
    def _(event):
        """
        Move to next blank-line separated section.
        Implements '}', 'c}', 'd}', 'y}'
        """
        match_func = lambda text: not text or text.isspace()
        line_index = event.current_buffer.document.find_next_matching_line(
            match_func=match_func, count=event.arg)

        if line_index:
            index = event.current_buffer.document.get_cursor_down_position(
                count=line_index)
        else:
            index = 0

        return CursorRegion(index)

    @change_delete_move_yank_handler('f', Keys.Any)
    def _(event):
        """
        Go to next occurance of character. Typing 'fx' will move the
        cursor to the next occurance of character. 'x'.
        """
        vi_state.last_character_find = CharacterFind(event.data, False)
        match = event.current_buffer.document.find(event.data,
                                                   in_current_line=True,
                                                   count=event.arg)
        return CursorRegion(match or 0)

    @change_delete_move_yank_handler('F', Keys.Any)
    def _(event):
        """
        Go to previous occurance of character. Typing 'Fx' will move the
        cursor to the previous occurance of character. 'x'.
        """
        vi_state.last_character_find = CharacterFind(event.data, True)
        return CursorRegion(
            event.current_buffer.document.find_backwards(
                event.data, in_current_line=True, count=event.arg) or 0)

    @change_delete_move_yank_handler('t', Keys.Any)
    def _(event):
        """
        Move right to the next occurance of c, then one char backward.
        """
        vi_state.last_character_find = CharacterFind(event.data, False)
        match = event.current_buffer.document.find(event.data,
                                                   in_current_line=True,
                                                   count=event.arg)
        return CursorRegion(match - 1 if match else 0)

    @change_delete_move_yank_handler('T', Keys.Any)
    def _(event):
        """
        Move left to the previous occurance of c, then one char forward.
        """
        vi_state.last_character_find = CharacterFind(event.data, True)
        match = event.current_buffer.document.find_backwards(
            event.data, in_current_line=True, count=event.arg)
        return CursorRegion(match + 1 if match else 0)

    def repeat(reverse):
        """
        Create ',' and ';' commands.
        """
        @change_delete_move_yank_handler(',' if reverse else ';')
        def _(event):
            # Repeat the last 'f'/'F'/'t'/'T' command.
            pos = 0

            if vi_state.last_character_find:
                char = vi_state.last_character_find.character
                backwards = vi_state.last_character_find.backwards

                if reverse:
                    backwards = not backwards

                if backwards:
                    pos = event.current_buffer.document.find_backwards(
                        char, in_current_line=True, count=event.arg)
                else:
                    pos = event.current_buffer.document.find(
                        char, in_current_line=True, count=event.arg)
            return CursorRegion(pos or 0)

    repeat(True)
    repeat(False)

    @change_delete_move_yank_handler('h')
    @change_delete_move_yank_handler(Keys.Left)
    def _(event):
        """ Implements 'ch', 'dh', 'h': Cursor left. """
        return CursorRegion(
            event.current_buffer.document.get_cursor_left_position(
                count=event.arg))

    @change_delete_move_yank_handler('j', no_move_handler=True)
    def _(event):
        """ Implements 'cj', 'dj', 'j', ... Cursor up. """
        return CursorRegion(
            event.current_buffer.document.get_cursor_down_position(
                count=event.arg))

    @change_delete_move_yank_handler('k', no_move_handler=True)
    def _(event):
        """ Implements 'ck', 'dk', 'k', ... Cursor up. """
        return CursorRegion(
            event.current_buffer.document.get_cursor_up_position(
                count=event.arg))

    @change_delete_move_yank_handler('l')
    @change_delete_move_yank_handler(' ')
    @change_delete_move_yank_handler(Keys.Right)
    def _(event):
        """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """
        return CursorRegion(
            event.current_buffer.document.get_cursor_right_position(
                count=event.arg))

    @change_delete_move_yank_handler('H')
    def _(event):
        """
        Moves to the start of the visible region. (Below the scroll offset.)
        Implements 'cH', 'dH', 'H'.
        """
        w = find_window_for_buffer_name(event.cli.layout,
                                        event.cli.current_buffer_name)
        b = event.current_buffer

        if w:
            # When we find a Window that has BufferControl showing this window,
            # move to the start of the visible area.
            pos = (b.document.translate_row_col_to_index(
                w.render_info.first_visible_line(after_scroll_offset=True), 0)
                   - b.cursor_position)

        else:
            # Otherwise, move to the start of the input.
            pos = -len(b.document.text_before_cursor)
        return CursorRegion(pos)

    @change_delete_move_yank_handler('L')
    def _(event):
        """
        Moves to the end of the visible region. (Above the scroll offset.)
        """
        w = find_window_for_buffer_name(event.cli.layout,
                                        event.cli.current_buffer_name)
        b = event.current_buffer

        if w:
            # When we find a Window that has BufferControl showing this window,
            # move to the end of the visible area.
            pos = (b.document.translate_row_col_to_index(
                w.render_info.last_visible_line(before_scroll_offset=True), 0)
                   - b.cursor_position)

        else:
            # Otherwise, move to the end of the input.
            pos = len(b.document.text_after_cursor)
        return CursorRegion(pos)

    @handle('z', 'z', filter=navigation_mode | selection_mode)
    def _(event):
        """
        Center Window vertically around cursor.
        """
        w = find_window_for_buffer_name(event.cli.layout,
                                        event.cli.current_buffer_name)
        b = event.cli.current_buffer

        if w and w.render_info:
            # Calculate the offset that we need in order to position the row
            # containing the cursor in the center.
            cursor_position_row = b.document.cursor_position_row

            render_row = w.render_info.input_line_to_screen_line(
                cursor_position_row)
            if render_row is not None:
                w.vertical_scroll = max(
                    0, int(render_row - w.render_info.rendered_height / 2))

    @change_delete_move_yank_handler('%')
    def _(event):
        """
        Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.)
        If an 'arg' has been given, go this this % position in the file.
        """
        buffer = event.current_buffer

        if event._arg:
            # If 'arg' has been given, the meaning of % is to go to the 'x%'
            # row in the file.
            if 0 < event.arg <= 100:
                absolute_index = buffer.document.translate_row_col_to_index(
                    int(event.arg * buffer.document.line_count / 100), 0)
                return CursorRegion(absolute_index -
                                    buffer.document.cursor_position)
            else:
                return CursorRegion(0)  # Do nothing.

        else:
            # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s).
            return CursorRegion(buffer.document.matching_bracket_position)

    @change_delete_move_yank_handler('|')
    def _(event):
        # Move to the n-th column (you may specify the argument n by typing
        # it on number keys, for example, 20|).
        return CursorRegion(
            event.current_buffer.document.get_column_cursor_position(
                event.arg))

    @change_delete_move_yank_handler('g', 'g')
    def _(event):
        """
        Implements 'gg', 'cgg', 'ygg'
        """
        # Move to the top of the input.
        return CursorRegion(
            event.current_buffer.document.get_start_of_document_position())

    @change_delete_move_yank_handler('g', '_')
    def _(event):
        """
        Go to last non-blank of line.
        'g_', 'cg_', 'yg_', etc..
        """
        return CursorRegion(event.current_buffer.document.
                            last_non_blank_of_current_line_position())

    @change_delete_move_yank_handler('g', 'e')
    def _(event):
        """
        Go to last character of previous word.
        'ge', 'cge', 'yge', etc..
        """
        return CursorRegion(
            event.current_buffer.document.find_start_of_previous_word(
                count=event.arg) or 0)

    @change_delete_move_yank_handler('g', 'E')
    def _(event):
        """
        Go to last character of previous WORD.
        'gE', 'cgE', 'ygE', etc..
        """
        return CursorRegion(
            event.current_buffer.document.find_start_of_previous_word(
                count=event.arg, WORD=True) or 0)

    @change_delete_move_yank_handler('G')
    def _(event):
        """
        Go to the end of the document. (If no arg has been given.)
        """
        return CursorRegion(
            len(event.current_buffer.document.text_after_cursor))

    @handle('G', filter=HasArg())
    def _(event):
        """
        If an argument is given, move to this line in the  history. (for
        example, 15G)
        """
        event.current_buffer.go_to_history(event.arg - 1)

    @handle(Keys.Any, filter=navigation_mode)
    @handle(Keys.Any, filter=selection_mode)
    def _(event):
        """
        Always handle numberics in navigation mode as arg.
        """
        if event.data in '123456789' or (event._arg and event.data == '0'):
            event.append_to_arg_count(event.data)
        elif event.data == '0':
            buffer = event.current_buffer
            buffer.cursor_position += buffer.document.get_start_of_line_position(
                after_whitespace=False)

    @handle(Keys.Any, filter=replace_mode)
    def _(event):
        """
        Insert data at cursor position.
        """
        event.current_buffer.insert_text(event.data, overwrite=True)

    def create_selection_transform_handler(keys, transform_func):
        """
        Apply transformation on selection (uppercase, lowercase, rot13, swap case).
        """
        @handle(*keys, filter=selection_mode)
        def _(event):
            range = event.current_buffer.document.selection_range()
            if range:
                event.current_buffer.transform_region(range[0], range[1],
                                                      transform_func)

    for k, f in vi_transform_functions:
        create_selection_transform_handler(k, f)

    @handle(Keys.ControlX, Keys.ControlL, filter=insert_mode)
    def _(event):
        """
        Pressing the ControlX - ControlL sequence in Vi mode does line
        completion based on the other lines in the document and the history.
        """
        event.current_buffer.start_history_lines_completion()

    @handle(Keys.ControlX, Keys.ControlF, filter=insert_mode)
    def _(event):
        """
        Complete file names.
        """
        # TODO
        pass
Ejemplo n.º 5
0
def load_vi_search_bindings(registry,
                            vi_state,
                            filter=None,
                            search_buffer_name=SEARCH_BUFFER):
    assert isinstance(vi_state, ViState)

    has_focus = filters.HasFocus(search_buffer_name)
    navigation_mode = ~has_focus & (ViStateFilter(
        vi_state, InputMode.NAVIGATION) | filters.HasSelection())
    handle = create_handle_decorator(registry, filter)

    @handle('/', filter=navigation_mode)
    @handle(Keys.ControlS, filter=~has_focus)
    def _(event):
        """
        Vi-style forward search.
        """
        # Set the ViState.
        event.cli.search_state.direction = IncrementalSearchDirection.FORWARD
        vi_state.input_mode = InputMode.INSERT

        # Focus search buffer.
        event.cli.focus_stack.push(search_buffer_name)

    @handle('?', filter=navigation_mode)
    @handle(Keys.ControlR, filter=~has_focus)
    def _(event):
        """
        Vi-style backward search.
        """
        # Set the ViState.
        event.cli.search_state.direction = IncrementalSearchDirection.BACKWARD

        # Focus search buffer.
        event.cli.focus_stack.push(search_buffer_name)
        vi_state.input_mode = InputMode.INSERT

    @handle(Keys.ControlJ, filter=has_focus)
    def _(event):
        """
        Apply the search. (At the / or ? prompt.)
        """
        input_buffer = event.cli.buffers[event.cli.focus_stack.previous]
        search_buffer = event.cli.buffers[search_buffer_name]

        # Update search state.
        if search_buffer.text:
            event.cli.search_state.text = search_buffer.text

        # Apply search.
        input_buffer.apply_search(event.cli.search_state)

        # Add query to history of search line.
        search_buffer.append_to_history()
        search_buffer.reset()

        # Focus previous document again.
        vi_state.input_mode = InputMode.NAVIGATION
        event.cli.focus_stack.pop()

    def search_buffer_is_empty(cli):
        """ Returns True when the search buffer is empty. """
        return cli.buffers[search_buffer_name].text == ''

    @handle(Keys.Escape, filter=has_focus)
    @handle(Keys.ControlC, filter=has_focus)
    @handle(Keys.Backspace,
            filter=has_focus & Condition(search_buffer_is_empty))
    def _(event):
        """
        Cancel search.
        """
        vi_state.input_mode = InputMode.NAVIGATION

        event.cli.focus_stack.pop()
        event.cli.buffers[search_buffer_name].reset()
Ejemplo n.º 6
0
def load_basic_bindings(registry, filter=None):
    handle = create_handle_decorator(registry, filter)
    has_selection = filters.HasSelection()

    @handle(Keys.ControlA)
    @handle(Keys.ControlB)
    @handle(Keys.ControlC)
    @handle(Keys.ControlD)
    @handle(Keys.ControlE)
    @handle(Keys.ControlF)
    @handle(Keys.ControlG)
    @handle(Keys.ControlH)
    @handle(Keys.ControlI)
    @handle(Keys.ControlJ)
    @handle(Keys.ControlK)
    @handle(Keys.ControlL)
    @handle(Keys.ControlM)
    @handle(Keys.ControlN)
    @handle(Keys.ControlO)
    @handle(Keys.ControlP)
    @handle(Keys.ControlQ)
    @handle(Keys.ControlR)
    @handle(Keys.ControlS)
    @handle(Keys.ControlT)
    @handle(Keys.ControlU)
    @handle(Keys.ControlV)
    @handle(Keys.ControlW)
    @handle(Keys.ControlX)
    @handle(Keys.ControlY)
    @handle(Keys.ControlZ)
    @handle(Keys.F1)
    @handle(Keys.F2)
    @handle(Keys.F3)
    @handle(Keys.F4)
    @handle(Keys.F5)
    @handle(Keys.F6)
    @handle(Keys.F7)
    @handle(Keys.F8)
    @handle(Keys.F9)
    @handle(Keys.F10)
    @handle(Keys.F11)
    @handle(Keys.F12)
    @handle(Keys.F13)
    @handle(Keys.F14)
    @handle(Keys.F15)
    @handle(Keys.F16)
    @handle(Keys.F17)
    @handle(Keys.F18)
    @handle(Keys.F19)
    @handle(Keys.F20)
    @handle(Keys.ControlSpace)
    @handle(Keys.ControlBackslash)
    @handle(Keys.ControlSquareClose)
    @handle(Keys.ControlCircumflex)
    @handle(Keys.ControlUnderscore)
    @handle(Keys.Backspace)
    @handle(Keys.Up)
    @handle(Keys.Down)
    @handle(Keys.Right)
    @handle(Keys.Left)
    @handle(Keys.Home)
    @handle(Keys.End)
    @handle(Keys.Delete)
    @handle(Keys.ShiftDelete)
    @handle(Keys.PageUp)
    @handle(Keys.PageDown)
    @handle(Keys.BackTab)
    @handle(Keys.Tab)
    @handle(Keys.ControlLeft)
    @handle(Keys.ControlRight)
    @handle(Keys.ControlUp)
    @handle(Keys.ControlDown)
    def _(event):
        """
        First, for any of these keys, Don't do anything by default. Also don't
        catch them in the 'Any' handler which will insert them as data.

        If people want to insert these characters as a literal, they can always
        do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi
        mode.)
        """
        pass

    @handle(Keys.Home)
    def _(event):
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.home_position

    @handle(Keys.End)
    def _(event):
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.end_position

    # CTRL keys.

    @handle(Keys.ControlC)
    def _(event):
        if event.current_buffer.returnable(event.cli):
            event.cli.set_abort()

    @handle(Keys.ControlD)  # XXX: this is emacs behaviour.
    def _(event):
        buffer = event.current_buffer

        # When there is text, act as delete, otherwise call exit.
        if buffer.text:
            buffer.delete()
        else:
            event.cli.set_exit()

    @handle(Keys.ControlI, filter= ~has_selection)
    def _(event):
        r"""
        Ctrl-I is identical to "\t"

        Traditional tab-completion, where the first tab completes the common
        suffix and the second tab lists all the completions.
        """
        buffer = event.current_buffer

        def second_tab():
            buffer.complete_next(start_at_first=False)

        # On the second tab-press, or when already navigating through
        # completions.
        if event.second_press or (buffer.complete_state and buffer.complete_state.complete_index is not None):
            second_tab()
        else:
            # On the first tab press, only complete the common parts of all completions.
            has_common = buffer.complete_common()
            if not has_common:
                second_tab()

    @handle(Keys.BackTab, filter= ~has_selection)
    def _(event):
        """
        Shift+Tab: go to previous completion.
        """
        event.current_buffer.complete_previous()

    @handle(Keys.ControlM)
    def _(event):
        """
        Transform ControlM (\r) by default into ControlJ (\n).
        (This way it is sufficient for other key bindings to handle only ControlJ.)
        Windows sends \r instead of \n when pressing enter.
        """
        def feed():
            event.cli.input_processor.feed_key(KeyPress(Keys.ControlJ, ''))

        # We use `call_from_executor`, to schedule this for later execution,
        # otherwise, we're sending data into a generator which is already
        # executing.
        event.cli.call_from_executor(feed)

    @handle(Keys.Escape, Keys.ControlM)
    def _(event):
        """
        Transform Esc-ControlM into Esc-ControlJ.
        """
        def feed():
            event.cli.input_processor.feed_key(KeyPress(Keys.Escape, ''))
            event.cli.input_processor.feed_key(KeyPress(Keys.ControlJ, ''))

        event.cli.call_from_executor(feed)

    @handle(Keys.ControlJ, filter= ~has_selection)
    def _(event):
        """
        Newline/Enter. (Or return input.)
        """
        buffer = event.current_buffer

        if buffer.is_multiline:
            buffer.newline()
        else:
            if event.current_buffer.returnable(event.cli) and buffer.validate():
                event.current_buffer.add_to_history()
                event.cli.set_return_value(buffer.document)

    @handle(Keys.ControlK, filter= ~has_selection)
    def _(event):
        buffer = event.current_buffer
        deleted = buffer.delete(count=buffer.document.get_end_of_line_position())
        event.cli.clipboard.set_text(deleted)

    @handle(Keys.ControlL)
    def _(event):
        event.cli.renderer.clear()

    @handle(Keys.ControlT, filter= ~has_selection)
    def _(event):
        event.current_buffer.swap_characters_before_cursor()

    @handle(Keys.ControlU, filter= ~has_selection)
    def _(event):
        """
        Clears the line before the cursor position. If you are at the end of
        the line, clears the entire line.
        """
        buffer = event.current_buffer
        deleted = buffer.delete_before_cursor(count=-buffer.document.get_start_of_line_position())
        event.cli.clipboard.set_text(deleted)

    @handle(Keys.ControlW, filter= ~has_selection)
    def _(event):
        """
        Delete the word before the cursor.
        """
        buffer = event.current_buffer
        pos = buffer.document.find_start_of_previous_word(count=event.arg)

        if pos:
            deleted = buffer.delete_before_cursor(count=-pos)
            event.cli.clipboard.set_text(deleted)

    @handle(Keys.PageUp, filter= ~has_selection)
    def _(event):
        event.current_buffer.history_backward()

    @handle(Keys.PageDown, filter= ~has_selection)
    def _(event):
        event.current_buffer.history_forward()

    @handle(Keys.Left)
    def _(event):
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_cursor_left_position(count=event.arg)

    @handle(Keys.Right)
    def _(event):
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_cursor_right_position(count=event.arg)

    @handle(Keys.Up, filter= ~has_selection)
    def _(event):
        event.current_buffer.auto_up(count=event.arg)

    @handle(Keys.Up, filter=has_selection)
    def _(event):
        event.current_buffer.cursor_up(count=event.arg)

    @handle(Keys.Down, filter= ~has_selection)
    def _(event):
        event.current_buffer.auto_down(count=event.arg)

    @handle(Keys.Down, filter=has_selection)
    def _(event):
        event.current_buffer.cursor_down(count=event.arg)

    @handle(Keys.ControlH, filter= ~has_selection)
    @handle(Keys.Backspace, filter= ~has_selection)
    def _(event):
        buffer = event.current_buffer
        buffer.delete_before_cursor(count=event.arg)

    @handle(Keys.Delete, filter= ~has_selection)
    def _(event):
        event.current_buffer.delete(count=event.arg)

    @handle(Keys.ShiftDelete, filter= ~has_selection)
    def _(event):
        event.current_buffer.delete(count=event.arg)

    @handle(Keys.Any, filter= ~has_selection)
    def _(event):
        """
        Insert data at cursor position.
        """
        event.current_buffer.insert_text(event.data * event.arg)

    @handle(Keys.CPRResponse)
    def _(event):
        """
        Handle incoming Cursor-Position-Request response.
        """
        # The incoming data looks like u'\x1b[35;1R'
        # Parse row/col information.
        row, col = map(int, event.data[2:-1].split(';'))

        # Report absolute cursor position to the renderer.
        event.cli.renderer.report_absolute_cursor_row(row)

    @handle(Keys.ControlZ)
    def _(event):
        """
        Suspend process to background.
        """
        event.cli.suspend_to_background()
Ejemplo n.º 7
0
def load_emacs_bindings(registry, filter=Always()):
    """
    Some e-macs extensions.
    """
    # Overview of Readline emacs commands:
    # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf

    assert isinstance(filter, CLIFilter)

    handle = create_handle_decorator(registry, filter)
    has_selection = filters.HasSelection()

    @handle(Keys.Escape)
    def _(event):
        """
        By default, ignore escape key.

        (If we don't put this here, and Esc is followed by a key which sequence
        is not handled, we'll insert an Escape character in the input stream.
        Something we don't want and happens to easily in emacs mode.
        Further, people can always use ControlQ to do a quoted insert.)
        """
        pass

    @handle(Keys.ControlA)
    def _(event):
        """
        Start of line.
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_start_of_line_position(
            after_whitespace=False)

    @handle(Keys.ControlB)
    def _(event):
        """
        Character back.
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_cursor_left_position(
            count=event.arg)

    @handle(Keys.ControlE)
    def _(event):
        """
        End of line.
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_end_of_line_position()

    @handle(Keys.ControlF)
    def _(event):
        """
        Character forward.
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.get_cursor_right_position(
            count=event.arg)

    @handle(Keys.ControlN, filter=~has_selection)
    def _(event):
        """
        Next line.
        """
        event.current_buffer.auto_down()

    @handle(Keys.ControlN, filter=has_selection)
    def _(event):
        """
        Next line.
        """
        event.current_buffer.cursor_down()

    @handle(Keys.ControlO, filter=~has_selection)
    def _(event):
        """
        Insert newline, but don't move the cursor.
        """
        event.current_buffer.insert_text('\n', move_cursor=False)

    @handle(Keys.ControlP, filter=~has_selection)
    def _(event):
        """
        Previous line.
        """
        event.current_buffer.auto_up(count=event.arg)

    @handle(Keys.ControlP, filter=has_selection)
    def _(event):
        """
        Previous line.
        """
        event.current_buffer.cursor_up(count=event.arg)

    @handle(Keys.ControlQ, Keys.Any, filter=~has_selection)
    def _(event):
        """
        Quoted insert.

        For vt100 terminals, you have to disable flow control by running
        ``stty -ixon``, otherwise Ctrl-Q and Ctrl-S are captured by the
        terminal.
        """
        event.current_buffer.insert_text(event.data, overwrite=False)

    @handle(Keys.ControlY, filter=~has_selection)
    @handle(Keys.ControlX, 'r', 'y', filter=~has_selection)
    def _(event):
        """
        Paste before cursor.
        """
        event.current_buffer.paste_clipboard_data(
            event.cli.clipboard.get_data(), count=event.arg, before=True)

    @handle(Keys.ControlUnderscore, save_before=False, filter=~has_selection)
    def _(event):
        """
        Undo.
        """
        event.current_buffer.undo()

    def handle_digit(c):
        """
        Handle Alt + digit in the `meta_digit` method.
        """
        @handle(Keys.Escape, c)
        def _(event):
            event.append_to_arg_count(c)

    for c in '0123456789':
        handle_digit(c)

    @handle(Keys.Escape, '-')
    def _(event):
        """
        """
        if event._arg is None:
            event.append_to_arg_count('-')

    @handle(Keys.Escape, Keys.ControlJ, filter=~has_selection)
    def _(event):
        """
        Meta + Newline: always accept input.
        """
        b = event.current_buffer

        if b.accept_action.is_returnable:
            b.accept_action.validate_and_handle(event.cli, b)

    @handle(Keys.ControlSquareClose, Keys.Any)
    def _(event):
        """
        When Ctl-] + a character is pressed. go to that character.
        """
        match = event.current_buffer.document.find(event.data,
                                                   in_current_line=True,
                                                   count=(event.arg))
        if match is not None:
            event.current_buffer.cursor_position += match

    @handle(Keys.Escape, Keys.Backspace, filter=~has_selection)
    def _(event):
        """
        Delete word backwards.
        """
        buffer = event.current_buffer
        pos = buffer.document.find_start_of_previous_word(count=event.arg)

        if pos:
            deleted = buffer.delete_before_cursor(count=-pos)
            event.cli.clipboard.set_text(deleted)

    @handle(Keys.Escape, 'a', filter=~has_selection)
    def _(event):
        """
        Previous sentence.
        """
        # TODO:
        pass

    @handle(Keys.Escape, 'c', filter=~has_selection)
    def _(event):
        """
        Capitalize the current (or following) word.
        """
        buffer = event.current_buffer

        for i in range(event.arg):
            pos = buffer.document.find_next_word_ending()
            words = buffer.document.text_after_cursor[:pos]
            buffer.insert_text(words.title(), overwrite=True)

    @handle(Keys.Escape, 'd', filter=~has_selection)
    def _(event):
        """
        Delete word forwards.
        """
        buffer = event.current_buffer
        pos = buffer.document.find_next_word_ending(count=event.arg)

        if pos:
            deleted = buffer.delete(count=pos)
            event.cli.clipboard.set_text(deleted)

    @handle(Keys.Escape, 'e', filter=~has_selection)
    def _(event):
        """ Move to end of sentence. """
        # TODO:
        pass

    @handle(Keys.Escape, 'f')
    @handle(Keys.ControlRight)
    def _(event):
        """
        Cursor to end of next word.
        """
        buffer = event.current_buffer
        pos = buffer.document.find_next_word_ending(count=event.arg)

        if pos:
            buffer.cursor_position += pos

    @handle(Keys.Escape, 'b')
    @handle(Keys.ControlLeft)
    def _(event):
        """
        Cursor to start of previous word.
        """
        buffer = event.current_buffer
        pos = buffer.document.find_previous_word_beginning(count=event.arg)
        if pos:
            buffer.cursor_position += pos

    @handle(Keys.Escape, 'l', filter=~has_selection)
    def _(event):
        """
        Lowercase the current (or following) word.
        """
        buffer = event.current_buffer

        for i in range(event.arg):  # XXX: not DRY: see meta_c and meta_u!!
            pos = buffer.document.find_next_word_ending()
            words = buffer.document.text_after_cursor[:pos]
            buffer.insert_text(words.lower(), overwrite=True)

    @handle(Keys.Escape, 't', filter=~has_selection)
    def _(event):
        """
        Swap the last two words before the cursor.
        """
        # TODO

    @handle(Keys.Escape, 'u', filter=~has_selection)
    def _(event):
        """
        Uppercase the current (or following) word.
        """
        buffer = event.current_buffer

        for i in range(event.arg):
            pos = buffer.document.find_next_word_ending()
            words = buffer.document.text_after_cursor[:pos]
            buffer.insert_text(words.upper(), overwrite=True)

    @handle(Keys.Escape, '.', filter=~has_selection)
    def _(event):
        """
        Rotate through the last word (white-space delimited) of the previous lines in history.
        """
        # TODO

    @handle(Keys.Escape, '\\', filter=~has_selection)
    def _(event):
        """
        Delete all spaces and tabs around point.
        (delete-horizontal-space)
        """

    @handle(Keys.Escape, '*', filter=~has_selection)
    def _(event):
        """
        `meta-*`: Insert all possible completions of the preceding text.
        """

    @handle(Keys.ControlX,
            Keys.ControlU,
            save_before=False,
            filter=~has_selection)
    def _(event):
        event.current_buffer.undo()

    @handle(Keys.ControlX, Keys.ControlX)
    def _(event):
        """
        Move cursor back and forth between the start and end of the current
        line.
        """
        buffer = event.current_buffer

        if buffer.document.current_char == '\n':
            buffer.cursor_position += buffer.document.get_start_of_line_position(
                after_whitespace=False)
        else:
            buffer.cursor_position += buffer.document.get_end_of_line_position(
            )

    @handle(Keys.ControlSpace)
    def _(event):
        """
        Start of the selection.
        """
        # Take the current cursor position as the start of this selection.
        event.current_buffer.start_selection(
            selection_type=SelectionType.CHARACTERS)

    @handle(Keys.ControlG, filter=~has_selection)
    def _(event):
        """
        Control + G: Cancel completion menu and validation state.
        """
        event.current_buffer.complete_state = None
        event.current_buffer.validation_error = None

    @handle(Keys.ControlG, filter=has_selection)
    def _(event):
        """
        Cancel selection.
        """
        event.current_buffer.exit_selection()

    @handle(Keys.ControlW, filter=has_selection)
    @handle(Keys.ControlX, 'r', 'k', filter=has_selection)
    def _(event):
        """
        Cut selected text.
        """
        data = event.current_buffer.cut_selection()
        event.cli.clipboard.set_data(data)

    @handle(Keys.Escape, 'w', filter=has_selection)
    def _(event):
        """
        Copy selected text.
        """
        data = event.current_buffer.copy_selection()
        event.cli.clipboard.set_data(data)

    @handle(Keys.Escape, '<', filter=~has_selection)
    def _(event):
        """
        Move to the first line in the history.
        """
        event.current_buffer.go_to_history(0)

    @handle(Keys.Escape, '>', filter=~has_selection)
    def _(event):
        """
        Move to the end of the input history.
        This is the line we are editing.
        """
        buffer = event.current_buffer
        buffer.go_to_history(len(buffer._working_lines) - 1)

    @handle(Keys.Escape, Keys.Left)
    def _(event):
        """
        Cursor to start of previous word.
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.find_previous_word_beginning(
            count=event.arg) or 0

    @handle(Keys.Escape, Keys.Right)
    def _(event):
        """
        Cursor to start of next word.
        """
        buffer = event.current_buffer
        buffer.cursor_position += buffer.document.find_next_word_beginning(count=event.arg) or \
            buffer.document.get_end_of_document_position()

    @handle(Keys.Escape, '/', filter=~has_selection)
    def _(event):
        """
        M-/: Complete.
        """
        b = event.current_buffer
        if b.complete_state:
            b.complete_next()
        else:
            event.cli.start_completion(select_first=True)

    @handle(Keys.ControlC, '>', filter=has_selection)
    def _(event):
        """
        Indent selected text.
        """
        buffer = event.current_buffer

        buffer.cursor_position += buffer.document.get_start_of_line_position(
            after_whitespace=True)

        from_, to = buffer.document.selection_range()
        from_, _ = buffer.document.translate_index_to_position(from_)
        to, _ = buffer.document.translate_index_to_position(to)

        indent(
            buffer, from_ - 1, to, count=event.arg
        )  # XXX: why does translate_index_to_position return 1-based indexing???

    @handle(Keys.ControlC, '<', filter=has_selection)
    def _(event):
        """
        Unindent selected text.
        """
        buffer = event.current_buffer

        from_, to = buffer.document.selection_range()
        from_, _ = buffer.document.translate_index_to_position(from_)
        to, _ = buffer.document.translate_index_to_position(to)

        unindent(buffer, from_ - 1, to, count=event.arg)
Ejemplo n.º 8
0
def load_vi_search_bindings(registry,
                            vi_state,
                            filter=None,
                            search_buffer_name='search'):
    assert isinstance(vi_state, ViState)

    has_focus = filters.HasFocus(search_buffer_name)
    navigation_mode = ~has_focus & (ViStateFilter(
        vi_state, InputMode.NAVIGATION) | filters.HasSelection())
    handle = create_handle_decorator(registry, filter)

    @handle('/', filter=navigation_mode)
    @handle(Keys.ControlS)
    def _(event):
        """
        Vi-style forward search.
        """
        vi_state.search_direction = direction = IncrementalSearchDirection.FORWARD
        vi_state.input_mode = InputMode.INSERT

        if event.cli.focus_stack.current != search_buffer_name:
            event.cli.focus_stack.push(search_buffer_name)

        event.cli.buffers[event.cli.focus_stack.previous].incremental_search(
            direction)

    @handle('?', filter=navigation_mode)
    @handle(Keys.ControlR)
    def _(event):
        """
        Vi-style backward search.
        """
        vi_state.search_direction = direction = IncrementalSearchDirection.BACKWARD
        vi_state.input_mode = InputMode.INSERT

        if event.cli.focus_stack.current != search_buffer_name:
            event.cli.focus_stack.push(search_buffer_name)

        event.cli.buffers[event.cli.focus_stack.previous].incremental_search(
            direction)

    @handle(Keys.ControlJ, filter=has_focus)
    def _(event):
        """
        Enter at the / or ? prompt.
        """
        search_buffer = event.cli.buffers[search_buffer_name]
        vi_state.input_mode = InputMode.NAVIGATION

        # Add query to history of search line.
        search_buffer.add_to_history()
        search_buffer.reset()

        # Focus previous document again.
        event.cli.focus_stack.pop()

    @handle(Keys.Any, filter=has_focus)
    def _(event):
        """
        Insert text after the / or ? prompt.
        """
        event.cli.current_buffer.insert_text(event.data)

        # Set current search search text as line search text.
        buffer = event.cli.buffers[event.cli.focus_stack.previous]
        buffer.set_search_text(event.cli.current_buffer.text)

    @handle(Keys.Escape, filter=has_focus)
    @handle(Keys.ControlC, filter=has_focus)
    def _(event):
        """
        Cancel search.
        """
        vi_state.input_mode = InputMode.NAVIGATION

        event.cli.focus_stack.pop()
        event.current_buffer.exit_isearch(restore_original_line=True)

        event.cli.buffers[search_buffer_name].reset()