예제 #1
0
def test_prefix_meta():
    # Test the prefix-meta command.
    b = KeyBindings()
    b.add('j', 'j', filter=ViInsertMode())(prefix_meta)

    result, cli = _feed_cli_with_input('hellojjIX\r',
                                       key_bindings=b,
                                       editing_mode=EditingMode.VI)
    assert result.text == 'Xhello'
예제 #2
0
def _create_more_session(message: str = '--MORE--') -> 'PromptSession':
    """
    Create a `PromptSession` object for displaying the "--MORE--".
    """
    from prompt_toolkit_dev.shortcuts import PromptSession
    bindings = KeyBindings()

    @bindings.add(' ')
    @bindings.add('y')
    @bindings.add('Y')
    @bindings.add(Keys.ControlJ)
    @bindings.add(Keys.ControlM)
    @bindings.add(Keys.ControlI)  # Tab.
    def _(event: E) -> None:
        event.app.exit(result=True)

    @bindings.add('n')
    @bindings.add('N')
    @bindings.add('q')
    @bindings.add('Q')
    @bindings.add(Keys.ControlC)
    def _(event: E) -> None:
        event.app.exit(result=False)

    @bindings.add(Keys.Any)
    def _(event: E) -> None:
        " Disable inserting of text. "

    return PromptSession(message, key_bindings=bindings, erase_when_done=True)
예제 #3
0
def bindings(handlers):
    bindings = KeyBindings()
    bindings.add(Keys.ControlX, Keys.ControlC)(handlers.controlx_controlc)
    bindings.add(Keys.ControlX)(handlers.control_x)
    bindings.add(Keys.ControlD)(handlers.control_d)
    bindings.add(Keys.ControlSquareClose,
                 Keys.Any)(handlers.control_square_close_any)

    return bindings
예제 #4
0
    def __init__(self, values: Sequence[Tuple[_T, AnyFormattedText]]) -> None:
        assert len(values) > 0

        self.values = values
        # current_values will be used in multiple_selection,
        # current_value will be used otherwise.
        self.current_values: List[_T] = []
        self.current_value: _T = values[0][0]
        self._selected_index = 0

        # Key bindings.
        kb = KeyBindings()

        @kb.add('up')
        def _(event: E) -> None:
            self._selected_index = max(0, self._selected_index - 1)

        @kb.add('down')
        def _(event: E) -> None:
            self._selected_index = min(
                len(self.values) - 1, self._selected_index + 1)

        @kb.add('pageup')
        def _(event: E) -> None:
            w = event.app.layout.current_window
            self._selected_index = max(
                0, self._selected_index - len(w.render_info.displayed_lines))

        @kb.add('pagedown')
        def _(event: E) -> None:
            w = event.app.layout.current_window
            self._selected_index = min(
                len(self.values) - 1,
                self._selected_index + len(w.render_info.displayed_lines))

        @kb.add('enter')
        @kb.add(' ')
        def _(event: E) -> None:
            self._handle_enter()

        @kb.add(Keys.Any)
        def _(event: E) -> None:
            # We first check values after the selected value, then all values.
            for value in self.values[self._selected_index + 1:] + self.values:
                if value[1].startswith(event.data):
                    self._selected_index = self.values.index(value)
                    return

        # Control and window.
        self.control = FormattedTextControl(self._get_text_fragments,
                                            key_bindings=kb,
                                            focusable=True)

        self.window = Window(content=self.control,
                             style=self.container_style,
                             right_margins=[
                                 ScrollbarMargin(display_arrows=True),
                             ],
                             dont_extend_height=True)
예제 #5
0
def test_previous_key_sequence(processor):
    """
    test whether we receive the correct previous_key_sequence.
    """
    with set_dummy_app():
        events = []

        def handler(event):
            events.append(event)

        # Build registry.
        registry = KeyBindings()
        registry.add('a', 'a')(handler)
        registry.add('b', 'b')(handler)
        processor = KeyProcessor(registry)

        # Create processor and feed keys.
        processor.feed(KeyPress('a', 'a'))
        processor.feed(KeyPress('a', 'a'))
        processor.feed(KeyPress('b', 'b'))
        processor.feed(KeyPress('b', 'b'))
        processor.process_keys()

        # Test.
        assert len(events) == 2
        assert len(events[0].key_sequence) == 2
        assert events[0].key_sequence[0].key == 'a'
        assert events[0].key_sequence[0].data == 'a'
        assert events[0].key_sequence[1].key == 'a'
        assert events[0].key_sequence[1].data == 'a'
        assert events[0].previous_key_sequence == []

        assert len(events[1].key_sequence) == 2
        assert events[1].key_sequence[0].key == 'b'
        assert events[1].key_sequence[0].data == 'b'
        assert events[1].key_sequence[1].key == 'b'
        assert events[1].key_sequence[1].data == 'b'
        assert len(events[1].previous_key_sequence) == 2
        assert events[1].previous_key_sequence[0].key == 'a'
        assert events[1].previous_key_sequence[0].data == 'a'
        assert events[1].previous_key_sequence[1].key == 'a'
        assert events[1].previous_key_sequence[1].data == 'a'
예제 #6
0
    def _get_key_bindings(self) -> KeyBindings:
        " Key bindings for the Button. "
        kb = KeyBindings()

        @kb.add(' ')
        @kb.add('enter')
        def _(event: E) -> None:
            if self.handler is not None:
                self.handler()

        return kb
예제 #7
0
    def get_key_bindings(self) -> 'KeyBindings':
        """
        Expose key bindings that handle the left/right arrow keys when the menu
        is displayed.
        """
        from prompt_toolkit_dev.key_binding.key_bindings import KeyBindings
        kb = KeyBindings()

        @Condition
        def filter() -> bool:
            " Only handle key bindings if this menu is visible. "
            app = get_app()
            complete_state = app.current_buffer.complete_state

            # There need to be completions, and one needs to be selected.
            if complete_state is None or complete_state.complete_index is None:
                return False

            # This menu needs to be visible.
            return any(
                window.content == self
                for window in app.layout.visible_windows)

        def move(right: bool = False) -> None:
            buff = get_app().current_buffer
            complete_state = buff.complete_state

            if complete_state is not None and \
                    complete_state.complete_index is not None:
                # Calculate new complete index.
                new_index = complete_state.complete_index
                if right:
                    new_index += self._rendered_rows
                else:
                    new_index -= self._rendered_rows

                if 0 <= new_index < len(complete_state.completions):
                    buff.go_to_completion(new_index)

        # NOTE: the is_global is required because the completion menu will
        #       never be focussed.

        @kb.add('left', is_global=True, filter=filter)
        def _(event: E) -> None:
            move()

        @kb.add('right', is_global=True, filter=filter)
        def _(event: E) -> None:
            move(True)

        return kb
def load_emacs_page_navigation_bindings() -> KeyBindingsBase:
    """
    Key bindings, for scrolling up and down through pages.
    This are separate bindings, because GNU readline doesn't have them.
    """
    key_bindings = KeyBindings()
    handle = key_bindings.add

    handle('c-v')(scroll_page_down)
    handle('pagedown')(scroll_page_down)
    handle('escape', 'v')(scroll_page_up)
    handle('pageup')(scroll_page_up)

    return ConditionalKeyBindings(key_bindings, emacs_mode)
def load_vi_page_navigation_bindings() -> KeyBindingsBase:
    """
    Key bindings, for scrolling up and down through pages.
    This are separate bindings, because GNU readline doesn't have them.
    """
    key_bindings = KeyBindings()
    handle = key_bindings.add

    handle('c-f')(scroll_forward)
    handle('c-b')(scroll_backward)
    handle('c-d')(scroll_half_page_down)
    handle('c-u')(scroll_half_page_up)
    handle('c-e')(scroll_one_line_down)
    handle('c-y')(scroll_one_line_up)
    handle('pagedown')(scroll_page_down)
    handle('pageup')(scroll_page_up)

    return ConditionalKeyBindings(key_bindings, vi_mode)
예제 #10
0
def load_auto_suggest_bindings() -> KeyBindings:
    """
    Key bindings for accepting auto suggestion text.

    (This has to come after the Vi bindings, because they also have an
    implementation for the "right arrow", but we really want the suggestion
    binding when a suggestion is available.)
    """
    key_bindings = KeyBindings()
    handle = key_bindings.add

    @Condition
    def suggestion_available() -> bool:
        app = get_app()
        return (app.current_buffer.suggestion is not None
                and app.current_buffer.document.is_cursor_at_the_end)

    @handle('c-f', filter=suggestion_available)
    @handle('c-e', filter=suggestion_available)
    @handle('right', filter=suggestion_available)
    def _(event: E) -> None:
        """
        Accept suggestion.
        """
        b = event.current_buffer
        suggestion = b.suggestion

        if suggestion:
            b.insert_text(suggestion.text)

    @handle('escape', 'f', filter=suggestion_available & emacs_mode)
    def _(event: E) -> None:
        """
        Fill partial suggestion.
        """
        b = event.current_buffer
        suggestion = b.suggestion

        if suggestion:
            t = re.split(r'(\S+\s+)', suggestion.text)
            b.insert_text(next(x for x in t if x))

    return key_bindings
예제 #11
0
def _create_app(dialog: AnyContainer,
                style: Optional[BaseStyle]) -> Application[Any]:
    # Key bindings.
    bindings = KeyBindings()
    bindings.add('tab')(focus_next)
    bindings.add('s-tab')(focus_previous)

    return Application(layout=Layout(dialog),
                       key_bindings=merge_key_bindings([
                           load_key_bindings(),
                           bindings,
                       ]),
                       mouse_support=True,
                       style=style,
                       full_screen=True)
예제 #12
0
async def _do_wait_for_enter(wait_text: AnyFormattedText) -> None:
    """
    Create a sub application to wait for the enter key press.
    This has two advantages over using 'input'/'raw_input':
    - This will share the same input/output I/O.
    - This doesn't block the event loop.
    """
    from prompt_toolkit_dev.shortcuts import PromptSession

    key_bindings = KeyBindings()

    @key_bindings.add('enter')
    def _(event: E) -> None:
        event.app.exit()

    @key_bindings.add(Keys.Any)
    def _(event: E) -> None:
        " Disallow typing. "
        pass

    session: PromptSession[None] = PromptSession(message=wait_text,
                                                 key_bindings=key_bindings)
    await session.app.run_async()
예제 #13
0
    def __init__(self,
                 body: AnyContainer,
                 title: AnyFormattedText = '',
                 buttons: Optional[Sequence[Button]] = None,
                 modal: bool = True,
                 width: AnyDimension = None,
                 with_background: bool = False) -> None:

        self.body = body
        self.title = title

        buttons = buttons or []

        # When a button is selected, handle left/right key bindings.
        buttons_kb = KeyBindings()
        if len(buttons) > 1:
            first_selected = has_focus(buttons[0])
            last_selected = has_focus(buttons[-1])

            buttons_kb.add('left', filter=~first_selected)(focus_previous)
            buttons_kb.add('right', filter=~last_selected)(focus_next)

        frame_body: AnyContainer
        if buttons:
            frame_body = HSplit([
                # Add optional padding around the body.
                Box(body=DynamicContainer(lambda: self.body),
                    padding=D(preferred=1, max=1),
                    padding_bottom=0),
                # The buttons.
                Box(body=VSplit(buttons, padding=1, key_bindings=buttons_kb),
                    height=D(min=1, max=3, preferred=3))
            ])
        else:
            frame_body = body

        # Key bindings for whole dialog.
        kb = KeyBindings()
        kb.add('tab', filter=~has_completions)(focus_next)
        kb.add('s-tab', filter=~has_completions)(focus_previous)

        frame = Shadow(body=Frame(
            title=lambda: self.title,
            body=frame_body,
            style='class:dialog.body',
            width=(None if with_background is None else width),
            key_bindings=kb,
            modal=modal,
        ))

        self.container: Union[Box, Shadow]
        if with_background:
            self.container = Box(body=frame, style='class:dialog', width=width)
        else:
            self.container = frame
예제 #14
0
    def _build_key_bindings(self) -> KeyBindingsBase:
        focused = has_focus(self.system_buffer)

        # Emacs
        emacs_bindings = KeyBindings()
        handle = emacs_bindings.add

        @handle('escape', filter=focused)
        @handle('c-g', filter=focused)
        @handle('c-c', filter=focused)
        def _(event: E) -> None:
            " Hide system prompt. "
            self.system_buffer.reset()
            event.app.layout.focus_last()

        @handle('enter', filter=focused)
        def _(event: E) -> None:
            " Run system command. "
            event.app.run_system_command(
                self.system_buffer.text,
                display_before_text=self._get_display_before_text())
            self.system_buffer.reset(append_to_history=True)
            event.app.layout.focus_last()

        # Vi.
        vi_bindings = KeyBindings()
        handle = vi_bindings.add

        @handle('escape', filter=focused)
        @handle('c-c', filter=focused)
        def _(event: E) -> None:
            " Hide system prompt. "
            event.app.vi_state.input_mode = InputMode.NAVIGATION
            self.system_buffer.reset()
            event.app.layout.focus_last()

        @handle('enter', filter=focused)
        def _(event: E) -> None:
            " Run system command. "
            event.app.vi_state.input_mode = InputMode.NAVIGATION
            event.app.run_system_command(
                self.system_buffer.text,
                display_before_text=self._get_display_before_text())
            self.system_buffer.reset(append_to_history=True)
            event.app.layout.focus_last()

        # Global bindings. (Listen to these bindings, even when this widget is
        # not focussed.)
        global_bindings = KeyBindings()
        handle = global_bindings.add

        @handle(Keys.Escape, '!', filter=~focused & emacs_mode, is_global=True)
        def _(event: E) -> None:
            " M-'!' will focus this user control. "
            event.app.layout.focus(self.window)

        @handle('!',
                filter=~focused & vi_mode & vi_navigation_mode,
                is_global=True)
        def _(event: E) -> None:
            " Focus. "
            event.app.vi_state.input_mode = InputMode.INSERT
            event.app.layout.focus(self.window)

        return merge_key_bindings([
            ConditionalKeyBindings(emacs_bindings, emacs_mode),
            ConditionalKeyBindings(vi_bindings, vi_mode),
            ConditionalKeyBindings(global_bindings,
                                   self.enable_global_bindings),
        ])
예제 #15
0
    def __init__(self, body: AnyContainer, menu_items: List['MenuItem'],
                 floats: Optional[List[Float]] = None,
                 key_bindings: Optional[KeyBindingsBase] = None) -> None:

        self.body = body
        self.menu_items = menu_items
        self.selected_menu = [0]

        # Key bindings.
        kb = KeyBindings()

        @Condition
        def in_main_menu() -> bool:
            return len(self.selected_menu) == 1

        @Condition
        def in_sub_menu() -> bool:
            return len(self.selected_menu) > 1

        # Navigation through the main menu.

        @kb.add('left', filter=in_main_menu)
        def _(event: E) -> None:
            self.selected_menu[0] = max(0, self.selected_menu[0] - 1)

        @kb.add('right', filter=in_main_menu)
        def _(event: E) -> None:
            self.selected_menu[0] = min(
                len(self.menu_items) - 1, self.selected_menu[0] + 1)

        @kb.add('down', filter=in_main_menu)
        def _(event: E) -> None:
            self.selected_menu.append(0)

        @kb.add('c-c', filter=in_main_menu)
        @kb.add('c-g', filter=in_main_menu)
        def _(event: E) -> None:
            " Leave menu. "
            event.app.layout.focus_last()

        # Sub menu navigation.

        @kb.add('left', filter=in_sub_menu)
        @kb.add('c-g', filter=in_sub_menu)
        @kb.add('c-c', filter=in_sub_menu)
        def _(event: E) -> None:
            " Go back to parent menu. "
            if len(self.selected_menu) > 1:
                self.selected_menu.pop()

        @kb.add('right', filter=in_sub_menu)
        def _(event: E) -> None:
            " go into sub menu. "
            if self._get_menu(len(self.selected_menu) - 1).children:
                self.selected_menu.append(0)

            # If This item does not have a sub menu. Go up in the parent menu.
            elif len(self.selected_menu) == 2 and self.selected_menu[0] < len(self.menu_items) - 1:
                self.selected_menu = [min(
                    len(self.menu_items) - 1, self.selected_menu[0] + 1)]
                if self.menu_items[self.selected_menu[0]].children:
                    self.selected_menu.append(0)

        @kb.add('up', filter=in_sub_menu)
        def _(event: E) -> None:
            " Select previous (enabled) menu item or return to main menu. "
            # Look for previous enabled items in this sub menu.
            menu = self._get_menu(len(self.selected_menu) - 2)
            index = self.selected_menu[-1]

            previous_indexes = [i for i, item in enumerate(menu.children)
                            if i < index and not item.disabled]

            if previous_indexes:
                self.selected_menu[-1] = previous_indexes[-1]
            elif len(self.selected_menu) == 2:
                # Return to main menu.
                self.selected_menu.pop()

        @kb.add('down', filter=in_sub_menu)
        def _(event: E) -> None:
            " Select next (enabled) menu item. "
            menu = self._get_menu(len(self.selected_menu) - 2)
            index = self.selected_menu[-1]

            next_indexes = [i for i, item in enumerate(menu.children)
                            if i > index and not item.disabled]

            if next_indexes:
                self.selected_menu[-1] = next_indexes[0]

        @kb.add('enter')
        def _(event: E) -> None:
            " Click the selected menu item. "
            item = self._get_menu(len(self.selected_menu) - 1)
            if item.handler:
                event.app.layout.focus_last()
                item.handler()

        # Controls.
        self.control = FormattedTextControl(
            self._get_menu_fragments,
            key_bindings=kb,
            focusable=True,
            show_cursor=False)

        self.window = Window(
            height=1,
            content=self.control,
            style='class:menu-bar')

        submenu = self._submenu(0)
        submenu2 = self._submenu(1)
        submenu3 = self._submenu(2)

        @Condition
        def has_focus() -> bool:
            return get_app().layout.current_window == self.window

        self.container = FloatContainer(
            content=HSplit([
                # The titlebar.
                self.window,

                # The 'body', like defined above.
                body,
            ]),
            floats=[
                Float(xcursor=True, ycursor=True,
                      content=ConditionalContainer(
                          content=Shadow(body=submenu),
                          filter=has_focus)),
                Float(attach_to_window=submenu,
                      xcursor=True, ycursor=True,
                      allow_cover_cursor=True,
                      content=ConditionalContainer(
                          content=Shadow(body=submenu2),
                          filter=has_focus & Condition(lambda: len(self.selected_menu) >= 1))),
                Float(attach_to_window=submenu2,
                      xcursor=True, ycursor=True,
                      allow_cover_cursor=True,
                      content=ConditionalContainer(
                          content=Shadow(body=submenu3),
                          filter=has_focus & Condition(lambda: len(self.selected_menu) >= 2))),

                # --
            ] + (floats or []),
            key_bindings=key_bindings,
        )
예제 #16
0
def test_remove_bindings(handlers):
    with set_dummy_app():
        h = handlers.controlx_controlc
        h2 = handlers.controld

        # Test passing a handler to the remove() function.
        bindings = KeyBindings()
        bindings.add(Keys.ControlX, Keys.ControlC)(h)
        bindings.add(Keys.ControlD)(h2)
        assert len(bindings.bindings) == 2
        bindings.remove(h)
        assert len(bindings.bindings) == 1

        # Test passing a key sequence to the remove() function.
        bindings = KeyBindings()
        bindings.add(Keys.ControlX, Keys.ControlC)(h)
        bindings.add(Keys.ControlD)(h2)
        assert len(bindings.bindings) == 2
        bindings.remove(Keys.ControlX, Keys.ControlC)
        assert len(bindings.bindings) == 1