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'
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)
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
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)
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'
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
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)
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
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)
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()
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
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), ])
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, )
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