Пример #1
0
 def handle_digit(c):
     """
     Handle input of arguments.
     The first number needs to be preceeded by escape.
     """
     @handle(c, filter=HasArg())
     @handle(Keys.Escape, c, filter=~HasArg())
     def _(event):
         event.append_to_arg_count(c)
Пример #2
0
    def __init__(self):
        def get_tokens(cli):
            if cli.input_processor.arg is not None:
                return [(Token.Arg, ' %i ' % cli.input_processor.arg)]
            else:
                return []

        super(SimpleArgToolbar, self).__init__(
            TokenListControl(get_tokens, align_right=True), filter=HasArg()),
Пример #3
0
    def __init__(self):
        def get_text():
            app = get_app()
            if app.key_processor.arg is not None:
                return ' %s ' % app.key_processor.arg
            else:
                return ''

        super(_Arg, self).__init__(Window(FormattedTextControl(get_text),
                                          style='class:arg',
                                          align=WindowAlign.RIGHT),
                                   filter=HasArg())
Пример #4
0
    def __init__(self) -> None:
        def get_text() -> str:
            app = get_app()
            if app.key_processor.arg is not None:
                return " %s " % app.key_processor.arg
            else:
                return ""

        super().__init__(
            Window(
                FormattedTextControl(get_text),
                style="class:arg",
                align=WindowAlign.RIGHT,
            ),
            filter=HasArg(),
        )
Пример #5
0
 def __init__(self):
     super(ArgToolbar,
           self).__init__(content=Window(ArgToolbarControl(),
                                         height=LayoutDimension.exact(1)),
                          filter=HasArg())
Пример #6
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
Пример #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
    filter = to_cli_filter(filter)

    handle = create_handle_decorator(registry, filter & EmacsMode())
    insert_mode = EmacsInsertMode()
    has_selection = 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)(get_by_name('beginning-of-line'))
    handle(Keys.ControlB)(get_by_name('backward-char'))
    handle(Keys.ControlDelete, filter=insert_mode)(get_by_name('kill-word'))
    handle(Keys.ControlE)(get_by_name('end-of-line'))
    handle(Keys.ControlF)(get_by_name('forward-char'))
    handle(Keys.ControlLeft)(get_by_name('backward-word'))
    handle(Keys.ControlRight)(get_by_name('forward-word'))
    handle(Keys.ControlX, 'r', 'y', filter=insert_mode)(get_by_name('yank'))
    handle(Keys.ControlY, filter=insert_mode)(get_by_name('yank'))
    handle(Keys.Escape, 'b')(get_by_name('backward-word'))
    handle(Keys.Escape, 'c',
           filter=insert_mode)(get_by_name('capitalize-word'))
    handle(Keys.Escape, 'd', filter=insert_mode)(get_by_name('kill-word'))
    handle(Keys.Escape, 'f')(get_by_name('forward-word'))
    handle(Keys.Escape, 'l', filter=insert_mode)(get_by_name('downcase-word'))
    handle(Keys.Escape, 'u', filter=insert_mode)(get_by_name('uppercase-word'))
    handle(Keys.Escape, Keys.ControlH,
           filter=insert_mode)(get_by_name('unix-word-rubout'))
    handle(Keys.Escape, Keys.Backspace,
           filter=insert_mode)(get_by_name('unix-word-rubout'))
    handle(Keys.Escape, '\\',
           filter=insert_mode)(get_by_name('delete-horizontal-space'))

    handle(Keys.ControlUnderscore,
           save_before=(lambda e: False),
           filter=insert_mode)(get_by_name('undo'))

    handle(Keys.ControlX,
           Keys.ControlU,
           save_before=(lambda e: False),
           filter=insert_mode)(get_by_name('undo'))

    handle(Keys.Escape, '<',
           filter=~has_selection)(get_by_name('beginning-of-history'))
    handle(Keys.Escape, '>',
           filter=~has_selection)(get_by_name('end-of-history'))

    @handle(Keys.ControlN)
    def _(event):
        " Next line. "
        event.current_buffer.auto_down()

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

    @handle(Keys.ControlP)
    def _(event):
        " Previous line. "
        event.current_buffer.auto_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)

    def handle_digit(c):
        """
        Handle input of arguments.
        The first number needs to be preceeded by escape.
        """
        @handle(c, filter=HasArg())
        @handle(Keys.Escape, c)
        def _(event):
            event.append_to_arg_count(c)

    for c in '0123456789':
        handle_digit(c)

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

    @handle('-', filter=Condition(lambda cli: cli.input_processor.arg == '-'))
    def _(event):
        """
        When '-' is typed again, after exactly '-' has been given as an
        argument, ignore this.
        """
        event.cli.input_processor.arg = '-'

    is_returnable = Condition(
        lambda cli: cli.current_buffer.accept_action.is_returnable)

    # Meta + Newline: always accept input.
    handle(Keys.Escape, Keys.ControlJ,
           filter=insert_mode & is_returnable)(get_by_name('accept-line'))

    def character_search(buff, char, count):
        if count < 0:
            match = buff.document.find_backwards(char,
                                                 in_current_line=True,
                                                 count=-count)
        else:
            match = buff.document.find(char, in_current_line=True, count=count)

        if match is not None:
            buff.cursor_position += match

    @handle(Keys.ControlSquareClose, Keys.Any)
    def _(event):
        " When Ctl-] + a character is pressed. go to that character. "
        character_search(event.current_buffer, event.data, event.arg)

    @handle(Keys.Escape, Keys.ControlSquareClose, Keys.Any)
    def _(event):
        " Like Ctl-], but backwards. "
        character_search(event.current_buffer, event.data, -event.arg)

    @handle(Keys.Escape, 'a')
    def _(event):
        " Previous sentence. "
        # TODO:

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

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

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

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

        # List all completions.
        complete_event = CompleteEvent(text_inserted=False,
                                       completion_requested=True)
        completions = list(
            buff.completer.get_completions(buff.document, complete_event))

        # Insert them.
        text_to_insert = ' '.join(c.text for c in completions)
        buff.insert_text(text_to_insert)

    @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.is_cursor_at_the_end_of_line:
            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 (if the current buffer is not empty).
        """
        # Take the current cursor position as the start of this selection.
        buff = event.current_buffer
        if buff.text:
            buff.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, 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=insert_mode)
    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_, to + 1, count=event.arg)

    @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_, to + 1, count=event.arg)