def __init__(self, registry=None, enable_vi_mode=Never(), enable_system_bindings=Never(), enable_search=Always(), enable_open_in_editor=Never(), enable_all=Always()): # Accept both Filters and booleans as input. enable_vi_mode = to_cli_filter(enable_vi_mode) enable_system_bindings = to_cli_filter(enable_system_bindings) enable_open_in_editor = to_cli_filter(enable_open_in_editor) enable_all = to_cli_filter(enable_all) # Create registry. assert registry is None or isinstance(registry, Registry) self.registry = registry or Registry() # Emacs mode filter is the opposite of Vi mode. enable_emacs_mode = ~enable_vi_mode # Vi state. (Object to keep track of in which Vi mode we are.) self.vi_state = ViState() # Load basic bindings. load_basic_bindings(self.registry, enable_all) load_basic_system_bindings(self.registry, enable_system_bindings & enable_all) # Load emacs bindings. load_emacs_bindings(self.registry, enable_emacs_mode & enable_all) load_emacs_open_in_editor_bindings( self.registry, enable_emacs_mode & enable_open_in_editor & enable_all) load_emacs_search_bindings( self.registry, enable_emacs_mode & enable_search & enable_all) load_emacs_system_bindings( self.registry, enable_emacs_mode & enable_system_bindings & enable_all) # Load Vi bindings. load_vi_bindings(self.registry, self.vi_state, enable_visual_key=~enable_open_in_editor, filter=enable_vi_mode & enable_all) load_vi_open_in_editor_bindings( self.registry, self.vi_state, enable_vi_mode & enable_open_in_editor & enable_all) load_vi_search_bindings(self.registry, self.vi_state, enable_vi_mode & enable_search & enable_all) load_vi_system_bindings( self.registry, self.vi_state, enable_vi_mode & enable_system_bindings & enable_all)
def _create_more_application(): """ Create an `Application` instance that displays the "--MORE--". """ from prompt_toolkit.shortcuts import create_prompt_application registry = Registry() @registry.add_binding(' ') @registry.add_binding('y') @registry.add_binding('Y') @registry.add_binding(Keys.ControlJ) @registry.add_binding(Keys.ControlI) # Tab. def _(event): event.cli.set_return_value(True) @registry.add_binding('n') @registry.add_binding('N') @registry.add_binding('q') @registry.add_binding('Q') @registry.add_binding(Keys.ControlC) def _(event): event.cli.set_return_value(False) return create_prompt_application('--MORE--', key_bindings_registry=registry, erase_when_done=True)
def load_confirm_exit_bindings(python_input): """ Handle yes/no key presses when the exit confirmation is shown. """ registry = Registry() handle = registry.add_binding confirmation_visible = Condition( lambda cli: python_input.show_exit_confirmation) @handle('y', filter=confirmation_visible) @handle('Y', filter=confirmation_visible) @handle(Keys.ControlJ, filter=confirmation_visible) @handle(Keys.ControlD, filter=confirmation_visible) def _(event): """ Really quit. """ event.cli.exit() @handle(Keys.Any, filter=confirmation_visible) def _(event): """ Cancel exit. """ python_input.show_exit_confirmation = False return registry
def registry(handlers): registry = Registry() registry.add_binding(Keys.ControlX, Keys.ControlC)(handlers.controlx_controlc) registry.add_binding(Keys.ControlX)(handlers.control_x) registry.add_binding(Keys.ControlD)(handlers.control_d) registry.add_binding(Keys.ControlSquareClose, Keys.Any)(handlers.control_square_close_any) return registry
def select(message, *options): """ Display a confirmation prompt. """ styling = style_from_dict({ Token.Key: 'bold', Token.DefaultKey: 'bold underline', }) def _get_tokens(cli): yield (Token, message + ': ') for i, option in enumerate(options): if i: yield (Token, ', ') if option.default: yield (Token.DefaultKey, option.caption[0].upper()) else: yield (Token.Key, option.caption[0].upper()) yield (Token.Caption, option.caption[1:]) yield (Token, '? ') def _event(option, event): event.cli.buffers[DEFAULT_BUFFER].text = option.output event.cli.set_return_value(option.value) registry = Registry() for option in options: handler = functools.partial(_event, option) for char in option.chars: registry.add_binding(char)(handler) if option.fallback: registry.add_binding(Keys.ControlC)(handler) registry.add_binding(Keys.Escape)(handler) if option.default: registry.add_binding(Keys.Enter)(handler) sys.stdout.flush() return run_application( create_prompt_application( get_prompt_tokens=_get_tokens, style=styling, key_bindings_registry=registry, ))
def load_list_bindings(): registry = Registry() handle = registry.add_binding @handle(Keys.Enter) def _accept_selection(event): buf = event.current_buffer buf.accept_action.validate_and_handle(event.cli, buf) return registry
def test_previous_key_sequence(processor, handlers): """ test whether we receive the correct previous_key_sequence. """ events = [] def handler(event): events.append(event) # Build registry. registry = Registry() registry.add_binding('a', 'a')(handler) registry.add_binding('b', 'b')(handler) processor = InputProcessor(registry, lambda: None) # 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 load_sidebar_bindings(python_input): """ Load bindings for the navigation in the sidebar. """ registry = Registry() handle = registry.add_binding sidebar_visible = Condition(lambda cli: python_input.show_sidebar) @handle(Keys.Up, filter=sidebar_visible) @handle(Keys.ControlP, filter=sidebar_visible) @handle('k', filter=sidebar_visible) def _(event): " Go to previous option. " python_input.selected_option_index = ( (python_input.selected_option_index - 1) % python_input.option_count) @handle(Keys.Down, filter=sidebar_visible) @handle(Keys.ControlN, filter=sidebar_visible) @handle('j', filter=sidebar_visible) def _(event): " Go to next option. " python_input.selected_option_index = ( (python_input.selected_option_index + 1) % python_input.option_count) @handle(Keys.Right, filter=sidebar_visible) @handle('l', filter=sidebar_visible) @handle(' ', filter=sidebar_visible) def _(event): " Select next value for current option. " option = python_input.selected_option option.activate_next() @handle(Keys.Left, filter=sidebar_visible) @handle('h', filter=sidebar_visible) def _(event): " Select previous value for current option. " option = python_input.selected_option option.activate_previous() @handle(Keys.ControlC, filter=sidebar_visible) @handle(Keys.ControlG, filter=sidebar_visible) @handle(Keys.ControlD, filter=sidebar_visible) @handle(Keys.ControlJ, filter=sidebar_visible) @handle(Keys.Escape, filter=sidebar_visible) def _(event): " Hide sidebar. " python_input.show_sidebar = False return registry
def setUp(self): class Handlers(object): def __init__(self): self.called = [] def __getattr__(self, name): def func(event): self.called.append(name) return func self.handlers = Handlers() self.registry = Registry() self.registry.add_binding(Keys.ControlX, Keys.ControlC)( self.handlers.controlx_controlc) self.registry.add_binding(Keys.ControlX)(self.handlers.control_x) self.registry.add_binding(Keys.ControlD)(self.handlers.control_d) self.registry.add_binding(Keys.ControlSquareClose, Keys.Any)( self.handlers.control_square_close_any) self.processor = InputProcessor(self.registry, lambda: None)
def load_cpr_bindings(): registry = Registry() handle = registry.add_binding @handle(Keys.CPRResponse, save_before=lambda e: False) 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) return registry
def setUp(self): class Handlers(object): def __init__(self): self.called = [] def __getattr__(self, name): def func(event): self.called.append(name) return func self.handlers = Handlers() self.registry = Registry() self.registry.add_binding(Keys.ControlX, Keys.ControlC)(self.handlers.controlx_controlc) self.registry.add_binding(Keys.ControlX)(self.handlers.control_x) self.registry.add_binding(Keys.ControlD)(self.handlers.control_d) self.registry.add_binding(Keys.ControlSquareClose, Keys.Any)(self.handlers.control_square_close_any) self.processor = InputProcessor(self.registry, lambda: None)
def __init__(self, registry=None, enable_vi_mode=Never(), enable_system_prompt=Never(), enable_search=Always(), enable_open_in_editor=Never()): assert registry is None or isinstance(registry, Registry) assert isinstance(enable_vi_mode, CLIFilter) assert isinstance(enable_system_prompt, CLIFilter) assert isinstance(enable_open_in_editor, CLIFilter) self.registry = registry or Registry() # Emacs mode filter is the opposite of Vi mode. enable_emacs_mode = ~enable_vi_mode # Vi state. (Object to keep track of in which Vi mode we are.) self.vi_state = ViState() # Load emacs bindings. load_emacs_bindings(self.registry, enable_emacs_mode) load_emacs_open_in_editor_bindings( self.registry, enable_emacs_mode & enable_open_in_editor) load_emacs_search_bindings(self.registry, enable_emacs_mode & enable_search) load_emacs_system_bindings(self.registry, enable_emacs_mode & enable_system_prompt) # Load Vi bindings. load_vi_bindings(self.registry, self.vi_state, enable_vi_mode) load_vi_open_in_editor_bindings(self.registry, self.vi_state, enable_vi_mode & enable_open_in_editor) load_vi_search_bindings(self.registry, self.vi_state, enable_vi_mode & enable_search) load_vi_system_bindings(self.registry, self.vi_state, enable_vi_mode & enable_system_prompt)
def __init__(self, registry=None, enable_vi_mode=False, enable_system_prompt=False, enable_search=True, enable_open_in_editor=False): self.registry = registry or Registry() # Flags. You can change these anytime. self.enable_vi_mode = enable_vi_mode self.enable_system_prompt = enable_system_prompt self.enable_search = enable_search self.enable_open_in_editor = enable_open_in_editor # Create set of filters to enable/disable sets of key bindings at # runtime. vi_mode_enabled = ViModeEnabled(self) emacs_mode_enabled = ~ vi_mode_enabled system_prompt_enabled = SystemPromptEnabled(self) search_enabled = SearchEnabled(self) open_in_editor_enabled = OpenInEditorEnabled(self) # Vi state. (Object to keep track of in which Vi mode we are.) self.vi_state = ViState() # Load all bindings in the registry with the correct filters. load_emacs_bindings(self.registry, emacs_mode_enabled) load_emacs_open_in_editor_bindings( self.registry, emacs_mode_enabled & open_in_editor_enabled) load_emacs_search_bindings( self.registry, emacs_mode_enabled & search_enabled) load_emacs_system_bindings( self.registry, emacs_mode_enabled & system_prompt_enabled) load_vi_bindings(self.registry, self.vi_state, vi_mode_enabled) load_vi_open_in_editor_bindings( self.registry, self.vi_state, vi_mode_enabled & open_in_editor_enabled) load_vi_search_bindings( self.registry, self.vi_state, vi_mode_enabled & search_enabled) load_vi_system_bindings( self.registry, self.vi_state, vi_mode_enabled & system_prompt_enabled)
def load_checkbox_bindings(): registry = Registry() handle = registry.add_binding @handle('a') def _select_all(event): event.current_buffer.select_all() @handle('i') def _invert_selection(event): event.current_buffer.invert_selection() @handle(' ') def _select(event): buf = event.current_buffer buf.toggle(buf.cursor) @handle(Keys.Enter) def _accept_selection(event): buf = event.current_buffer buf.accept_action.validate_and_handle(event.cli, buf) return registry
def load_scroll_bindings(): registry = Registry() handle = registry.add_binding @handle('k') @handle('K') @handle(Keys.Up) def _list_cursor_up(event): event.current_buffer.list_cursor_up() @handle('j') @handle('J') @handle(Keys.Down) def _list_cursor_down(event): event.current_buffer.list_cursor_down() for key in string.digits[1:]: def _set_cursor(event, num): event.current_buffer.cursor = num handle('%s' % key)(partial(_set_cursor, num=int(key) - 1)) return registry
def __init__( self, registry=None, enable_vi_mode=None, # (`enable_vi_mode` is deprecated.) get_search_state=None, enable_abort_and_exit_bindings=False, enable_system_bindings=False, enable_search=False, enable_open_in_editor=False, enable_extra_page_navigation=False, enable_auto_suggest_bindings=False, enable_all=True): assert registry is None or isinstance(registry, Registry) assert get_search_state is None or callable(get_search_state) # Create registry. self.registry = registry or Registry() # Accept both Filters and booleans as input. enable_abort_and_exit_bindings = to_cli_filter( enable_abort_and_exit_bindings) enable_system_bindings = to_cli_filter(enable_system_bindings) enable_search = to_cli_filter(enable_search) enable_open_in_editor = to_cli_filter(enable_open_in_editor) enable_extra_page_navigation = to_cli_filter( enable_extra_page_navigation) enable_auto_suggest_bindings = to_cli_filter( enable_auto_suggest_bindings) enable_all = to_cli_filter(enable_all) # Load basic bindings. load_basic_bindings(self.registry, enable_all) load_mouse_bindings(self.registry, enable_all) load_abort_and_exit_bindings( self.registry, enable_abort_and_exit_bindings & enable_all) load_basic_system_bindings(self.registry, enable_system_bindings & enable_all) # Load emacs bindings. load_emacs_bindings(self.registry, enable_all) load_emacs_open_in_editor_bindings(self.registry, enable_open_in_editor & enable_all) load_emacs_search_bindings(self.registry, filter=enable_search & enable_all, get_search_state=get_search_state) load_emacs_system_bindings(self.registry, enable_system_bindings & enable_all) load_extra_emacs_page_navigation_bindings( self.registry, enable_extra_page_navigation & enable_all) # Load Vi bindings. load_vi_bindings(self.registry, enable_visual_key=~enable_open_in_editor, filter=enable_all, get_search_state=get_search_state) load_vi_open_in_editor_bindings(self.registry, enable_open_in_editor & enable_all) load_vi_search_bindings(self.registry, filter=enable_search & enable_all, get_search_state=get_search_state) load_vi_system_bindings(self.registry, enable_system_bindings & enable_all) load_extra_vi_page_navigation_bindings( self.registry, enable_extra_page_navigation & enable_all) # Suggestion bindings. # (This has to come at the end, because the Vi bindings also have an # implementation for the "right arrow", but we really want the # suggestion binding when a suggestion is available.) load_auto_suggestion_bindings( self.registry, enable_auto_suggest_bindings & enable_all)
def __init__(self, registry=None, enable_vi_mode=False, get_vi_state=None, get_search_state=None, enable_abort_and_exit_bindings=False, enable_system_bindings=False, enable_search=False, enable_open_in_editor=False, enable_extra_page_navigation=False, enable_auto_suggest_bindings=False, enable_all=True): assert registry is None or isinstance(registry, Registry) assert get_vi_state is None or callable(get_vi_state) assert get_search_state is None or callable(get_search_state) # Create registry. self.registry = registry or Registry() # Vi state. (Object to keep track of in which Vi mode we are.) if get_vi_state is None: vi_state = ViState( ) # Stateful. Should be defined outside the function below. def get_vi_state(cli): return vi_state self.get_vi_state = get_vi_state # Accept both Filters and booleans as input. enable_vi_mode = to_cli_filter(enable_vi_mode) enable_abort_and_exit_bindings = to_cli_filter( enable_abort_and_exit_bindings) enable_system_bindings = to_cli_filter(enable_system_bindings) enable_search = to_cli_filter(enable_search) enable_open_in_editor = to_cli_filter(enable_open_in_editor) enable_extra_page_navigation = to_cli_filter( enable_extra_page_navigation) enable_auto_suggest_bindings = to_cli_filter( enable_auto_suggest_bindings) enable_all = to_cli_filter(enable_all) # Emacs mode filter is the opposite of Vi mode. enable_emacs_mode = ~enable_vi_mode # Load basic bindings. load_basic_bindings(self.registry, enable_all) load_abort_and_exit_bindings( self.registry, enable_abort_and_exit_bindings & enable_all) load_basic_system_bindings(self.registry, enable_system_bindings & enable_all) # Load emacs bindings. load_emacs_bindings(self.registry, enable_emacs_mode & enable_all) load_emacs_open_in_editor_bindings( self.registry, enable_emacs_mode & enable_open_in_editor & enable_all) load_emacs_search_bindings(self.registry, filter=enable_emacs_mode & enable_search & enable_all, get_search_state=get_search_state) load_emacs_system_bindings( self.registry, enable_emacs_mode & enable_system_bindings & enable_all) load_extra_emacs_page_navigation_bindings( self.registry, enable_emacs_mode & enable_extra_page_navigation & enable_all) # Load Vi bindings. load_vi_bindings(self.registry, self.get_vi_state, enable_visual_key=~enable_open_in_editor, filter=enable_vi_mode & enable_all, get_search_state=get_search_state) load_vi_open_in_editor_bindings( self.registry, self.get_vi_state, enable_vi_mode & enable_open_in_editor & enable_all) load_vi_search_bindings(self.registry, self.get_vi_state, filter=enable_vi_mode & enable_search & enable_all, get_search_state=get_search_state) load_vi_system_bindings( self.registry, self.get_vi_state, enable_vi_mode & enable_system_bindings & enable_all) load_extra_vi_page_navigation_bindings( self.registry, enable_vi_mode & enable_extra_page_navigation & enable_all) # Suggestion bindings. # (This has to come at the end, because the Vi bindings also have an # implementation for the "right arrow", but we really want the # suggestion binding when a suggestion is available.) load_auto_suggestion_bindings( self.registry, enable_auto_suggest_bindings & enable_all)
class KeyBindingTest(unittest.TestCase): def setUp(self): class Handlers(object): def __init__(self): self.called = [] def __getattr__(self, name): def func(event): self.called.append(name) return func self.handlers = Handlers() self.registry = Registry() self.registry.add_binding(Keys.ControlX, Keys.ControlC)(self.handlers.controlx_controlc) self.registry.add_binding(Keys.ControlX)(self.handlers.control_x) self.registry.add_binding(Keys.ControlD)(self.handlers.control_d) self.registry.add_binding(Keys.ControlSquareClose, Keys.Any)(self.handlers.control_square_close_any) self.processor = InputProcessor(self.registry, lambda: None) def test_feed_simple(self): self.processor.feed_key(KeyPress(Keys.ControlX, '\x18')) self.processor.feed_key(KeyPress(Keys.ControlC, '\x03')) self.assertEqual(self.handlers.called, ['controlx_controlc']) def test_feed_several(self): # First an unknown key first. self.processor.feed_key(KeyPress(Keys.ControlQ, '')) self.assertEqual(self.handlers.called, []) # Followed by a know key sequence. self.processor.feed_key(KeyPress(Keys.ControlX, '')) self.processor.feed_key(KeyPress(Keys.ControlC, '')) self.assertEqual(self.handlers.called, ['controlx_controlc']) # Followed by another unknown sequence. self.processor.feed_key(KeyPress(Keys.ControlR, '')) self.processor.feed_key(KeyPress(Keys.ControlS, '')) # Followed again by a know key sequence. self.processor.feed_key(KeyPress(Keys.ControlD, '')) self.assertEqual(self.handlers.called, ['controlx_controlc', 'control_d']) def test_control_square_closed_any(self): self.processor.feed_key(KeyPress(Keys.ControlSquareClose, '')) self.processor.feed_key(KeyPress('C', 'C')) self.assertEqual(self.handlers.called, ['control_square_close_any']) def test_common_prefix(self): # Sending Control_X should not yet do anything, because there is # another sequence starting with that as well. self.processor.feed_key(KeyPress(Keys.ControlX, '')) self.assertEqual(self.handlers.called, []) # When another key is pressed, we know that we did not meant the longer # "ControlX ControlC" sequence and the callbacks are called. self.processor.feed_key(KeyPress(Keys.ControlD, '')) self.assertEqual(self.handlers.called, ['control_x', 'control_d'])
def load_python_bindings(python_input): """ Custom key bindings. """ registry = Registry() sidebar_visible = Condition(lambda cli: python_input.show_sidebar) handle = registry.add_binding has_selection = HasSelection() vi_mode_enabled = Condition(lambda cli: python_input.vi_mode) @handle(Keys.ControlL) def _(event): """ Clear whole screen and render again -- also when the sidebar is visible. """ event.cli.renderer.clear() @handle(Keys.F2) def _(event): """ Show/hide sidebar. """ python_input.show_sidebar = not python_input.show_sidebar @handle(Keys.F3) def _(event): """ Select from the history. """ python_input.enter_history(event.cli) @handle(Keys.F4) def _(event): """ Toggle between Vi and Emacs mode. """ python_input.vi_mode = not python_input.vi_mode @handle(Keys.F6) def _(event): """ Enable/Disable paste mode. """ python_input.paste_mode = not python_input.paste_mode @handle(Keys.Tab, filter=~sidebar_visible & ~has_selection & TabShouldInsertWhitespaceFilter()) def _(event): """ When tab should insert whitespace, do that instead of completion. """ event.cli.current_buffer.insert_text(' ') @handle(Keys.ControlJ, filter=~sidebar_visible & ~has_selection & (ViInsertMode() | EmacsInsertMode()) & HasFocus(DEFAULT_BUFFER) & IsMultiline()) def _(event): """ Behaviour of the Enter key. Auto indent after newline/Enter. (When not in Vi navigaton mode, and when multiline is enabled.) """ b = event.current_buffer empty_lines_required = python_input.accept_input_on_enter or 10000 def at_the_end(b): """ we consider the cursor at the end when there is no text after the cursor, or only whitespace. """ text = b.document.text_after_cursor return text == '' or (text.isspace() and not '\n' in text) if python_input.paste_mode: # In paste mode, always insert text. b.insert_text('\n') elif at_the_end(b) and b.document.text.replace(' ', '').endswith( '\n' * (empty_lines_required - 1)): if b.validate(): # When the cursor is at the end, and we have an empty line: # drop the empty lines, but return the value. b.document = Document(text=b.text.rstrip(), cursor_position=len(b.text.rstrip())) b.accept_action.validate_and_handle(event.cli, b) else: auto_newline(b) @handle( Keys.ControlD, filter=~sidebar_visible & Condition(lambda cli: # Only when the `confirm_exit` flag is set. python_input.confirm_exit and # And the current buffer is empty. cli.current_buffer_name == DEFAULT_BUFFER and not cli. current_buffer.text)) def _(event): """ Override Control-D exit, to ask for confirmation. """ python_input.show_exit_confirmation = True return registry
def _load_search_bindings(pymux): """ Load the key bindings for searching. (Vi and Emacs) This is different from the ones of prompt_toolkit, because we have a individual search buffers for each pane. """ registry = Registry() is_searching = InScrollBufferSearching(pymux) in_scroll_buffer_not_searching = InScrollBufferNotSearching(pymux) def search_buffer_is_empty(cli): """ Returns True when the search buffer is empty. """ return pymux.arrangement.get_active_pane(cli).search_buffer.text == '' @registry.add_binding(Keys.ControlG, filter=is_searching) @registry.add_binding(Keys.ControlC, filter=is_searching) @registry.add_binding(Keys.Backspace, filter=is_searching & Condition(search_buffer_is_empty)) def _(event): """ Abort an incremental search and restore the original line. """ pane = pymux.arrangement.get_active_pane(event.cli) pane.search_buffer.reset() pane.is_searching = False @registry.add_binding(Keys.ControlJ, filter=is_searching) def _(event): """ When enter pressed in isearch, accept search. """ pane = pymux.arrangement.get_active_pane(event.cli) input_buffer = pane.scroll_buffer search_buffer = pane.search_buffer # Update search state. if search_buffer.text: pane.search_state.text = search_buffer.text # Apply search. input_buffer.apply_search(pane.search_state, include_current_position=True) # Add query to history of search line. search_buffer.append_to_history() # Focus previous document again. pane.search_buffer.reset() pane.is_searching = False def enter_search(cli): cli.vi_state.input_mode = InputMode.INSERT pane = pymux.arrangement.get_active_pane(cli) pane.is_searching = True return pane.search_state @registry.add_binding(Keys.ControlR, filter=in_scroll_buffer_not_searching) @registry.add_binding('?', filter=in_scroll_buffer_not_searching) def _(event): " Enter reverse search. " search_state = enter_search(event.cli) search_state.direction = IncrementalSearchDirection.BACKWARD @registry.add_binding(Keys.ControlS, filter=in_scroll_buffer_not_searching) @registry.add_binding('/', filter=in_scroll_buffer_not_searching) def _(event): " Enter forward search. " search_state = enter_search(event.cli) search_state.direction = IncrementalSearchDirection.FORWARD @registry.add_binding(Keys.ControlR, filter=is_searching) @registry.add_binding(Keys.Up, filter=is_searching) def _(event): " Repeat reverse search. (While searching.) " pane = pymux.arrangement.get_active_pane(event.cli) # Update search_state. search_state = pane.search_state direction_changed = search_state.direction != IncrementalSearchDirection.BACKWARD search_state.text = pane.search_buffer.text search_state.direction = IncrementalSearchDirection.BACKWARD # Apply search to current buffer. if not direction_changed: pane.scroll_buffer.apply_search(pane.search_state, include_current_position=False, count=event.arg) @registry.add_binding(Keys.ControlS, filter=is_searching) @registry.add_binding(Keys.Down, filter=is_searching) def _(event): " Repeat forward search. (While searching.) " pane = pymux.arrangement.get_active_pane(event.cli) # Update search_state. search_state = pane.search_state direction_changed = search_state.direction != IncrementalSearchDirection.FORWARD search_state.text = pane.search_buffer.text search_state.direction = IncrementalSearchDirection.FORWARD # Apply search to current buffer. if not direction_changed: pane.scroll_buffer.apply_search(pane.search_state, include_current_position=False, count=event.arg) return registry
def _load_builtins(self): """ Fill the Registry with the hard coded key bindings. """ pymux = self.pymux registry = Registry() # Create filters. has_prefix = HasPrefix(pymux) waits_for_confirmation = WaitsForConfirmation(pymux) prompt_or_command_focus = HasFocus(COMMAND) | HasFocus(PROMPT) display_pane_numbers = Condition( lambda cli: pymux.display_pane_numbers) in_scroll_buffer_not_searching = InScrollBufferNotSearching(pymux) pane_input_allowed = ~(prompt_or_command_focus | has_prefix | waits_for_confirmation | display_pane_numbers | InScrollBuffer(pymux)) @registry.add_binding(Keys.Any, filter=pane_input_allowed, invalidate_ui=False) def _(event): """ When a pane has the focus, key bindings are redirected to the process running inside the pane. """ # NOTE: we don't invalidate the UI, because for pymux itself, # nothing in the output changes yet. It's the application in # the pane that will probably echo back the typed characters. # When we receive them, they are draw to the UI and it's # invalidated. w = pymux.arrangement.get_active_window(event.cli) pane = w.active_pane if pane.clock_mode: # Leave clock mode on key press. pane.clock_mode = False pymux.invalidate() else: # Write input to pane. If 'synchronize_panes' is on, write # input to all panes in the current window. panes = w.panes if w.synchronize_panes else [pane] for p in panes: p.process.write_key(event.key_sequence[0].key) @registry.add_binding(Keys.BracketedPaste, filter=pane_input_allowed, invalidate_ui=False) def _(event): """ Pasting to the active pane. (Using bracketed paste.) """ w = pymux.arrangement.get_active_window(event.cli) pane = w.active_pane if not pane.clock_mode: # Paste input to pane. If 'synchronize_panes' is on, paste # input to all panes in the current window. panes = w.panes if w.synchronize_panes else [pane] for p in panes: p.process.write_input(event.data, paste=True) @registry.add_binding(Keys.Any, filter=has_prefix) def _(event): " Ignore unknown Ctrl-B prefixed key sequences. " pymux.get_client_state(event.cli).has_prefix = False @registry.add_binding(Keys.ControlC, filter=prompt_or_command_focus & ~has_prefix) @registry.add_binding(Keys.ControlG, filter=prompt_or_command_focus & ~has_prefix) @registry.add_binding( Keys.Backspace, filter=HasFocus(COMMAND) & ~has_prefix & Condition(lambda cli: cli.buffers[COMMAND].text == '')) def _(event): " Leave command mode. " pymux.leave_command_mode(event.cli, append_to_history=False) @registry.add_binding('y', filter=waits_for_confirmation) @registry.add_binding('Y', filter=waits_for_confirmation) def _(event): """ Confirm command. """ client_state = pymux.get_client_state(event.cli) command = client_state.confirm_command client_state.confirm_command = None client_state.confirm_text = None pymux.handle_command(event.cli, command) @registry.add_binding('n', filter=waits_for_confirmation) @registry.add_binding('N', filter=waits_for_confirmation) @registry.add_binding(Keys.ControlC, filter=waits_for_confirmation) def _(event): """ Cancel command. """ client_state = pymux.get_client_state(event.cli) client_state.confirm_command = None client_state.confirm_text = None @registry.add_binding(Keys.ControlC, filter=in_scroll_buffer_not_searching) @registry.add_binding(Keys.ControlJ, filter=in_scroll_buffer_not_searching) @registry.add_binding('q', filter=in_scroll_buffer_not_searching) def _(event): " Exit scroll buffer. " pane = pymux.arrangement.get_active_pane(event.cli) pane.exit_scroll_buffer() @registry.add_binding(' ', filter=in_scroll_buffer_not_searching) def _(event): " Enter selection mode when pressing space in copy mode. " event.current_buffer.start_selection( selection_type=SelectionType.CHARACTERS) @registry.add_binding(Keys.ControlJ, filter=in_scroll_buffer_not_searching & HasSelection()) def _(event): " Copy selection when pressing Enter. " clipboard_data = event.current_buffer.copy_selection() event.cli.clipboard.set_data(clipboard_data) @registry.add_binding('v', filter=in_scroll_buffer_not_searching & HasSelection()) def _(event): " Toggle between selection types. " types = [ SelectionType.LINES, SelectionType.BLOCK, SelectionType.CHARACTERS ] selection_state = event.current_buffer.selection_state try: index = types.index(selection_state.type) except ValueError: # Not in list. index = 0 selection_state.type = types[(index + 1) % len(types)] @registry.add_binding(Keys.Any, filter=display_pane_numbers) def _(event): " When the pane numbers are shown. Any key press should hide them. " pymux.display_pane_numbers = False return registry
class KeyBindingTest(unittest.TestCase): def setUp(self): class Handlers(object): def __init__(self): self.called = [] def __getattr__(self, name): def func(event): self.called.append(name) return func self.handlers = Handlers() self.registry = Registry() self.registry.add_binding(Keys.ControlX, Keys.ControlC)( self.handlers.controlx_controlc) self.registry.add_binding(Keys.ControlX)(self.handlers.control_x) self.registry.add_binding(Keys.ControlD)(self.handlers.control_d) self.registry.add_binding(Keys.ControlSquareClose, Keys.Any)( self.handlers.control_square_close_any) self.processor = InputProcessor(self.registry, lambda: None) def test_feed_simple(self): self.processor.feed_key(KeyPress(Keys.ControlX, '\x18')) self.processor.feed_key(KeyPress(Keys.ControlC, '\x03')) self.assertEqual(self.handlers.called, ['controlx_controlc']) def test_feed_several(self): # First an unknown key first. self.processor.feed_key(KeyPress(Keys.ControlQ, '')) self.assertEqual(self.handlers.called, []) # Followed by a know key sequence. self.processor.feed_key(KeyPress(Keys.ControlX, '')) self.processor.feed_key(KeyPress(Keys.ControlC, '')) self.assertEqual(self.handlers.called, ['controlx_controlc']) # Followed by another unknown sequence. self.processor.feed_key(KeyPress(Keys.ControlR, '')) self.processor.feed_key(KeyPress(Keys.ControlS, '')) # Followed again by a know key sequence. self.processor.feed_key(KeyPress(Keys.ControlD, '')) self.assertEqual(self.handlers.called, ['controlx_controlc', 'control_d']) def test_control_square_closed_any(self): self.processor.feed_key(KeyPress(Keys.ControlSquareClose, '')) self.processor.feed_key(KeyPress('C', 'C')) self.assertEqual(self.handlers.called, ['control_square_close_any']) def test_common_prefix(self): # Sending Control_X should not yet do anything, because there is # another sequence starting with that as well. self.processor.feed_key(KeyPress(Keys.ControlX, '')) self.assertEqual(self.handlers.called, []) # When another key is pressed, we know that we did not meant the longer # "ControlX ControlC" sequence and the callbacks are called. self.processor.feed_key(KeyPress(Keys.ControlD, '')) self.assertEqual(self.handlers.called, ['control_x', 'control_d'])
def _gen_bindings(self): registry = Registry() bind = registry.add_binding def bind_with_help(*args, name, info='', **kwargs): def dec(func): _info = func.__doc__ or info self._help_items.append(HelpItem(name, *args, info=_info)) return bind(*args, **kwargs)(func) return dec def ensure_cursor_bounds(buffer, pos, valids=None): buffer_stat = self.stat_buffer_state.current_stat if not buffer_stat: return if valids is None: valids = self.stat_constraints.get_cursor_bounds(buffer_stat) if pos not in valids: valids = sorted(valids) pos_index = bisect.bisect_left(valids, pos) requested_pos = pos pos = valids[min(pos_index, len(valids) - 1)] # if we wind up at the same spot, check to see if there's a non-sequential spot if buffer.cursor_position == pos: moving_left = requested_pos < pos if moving_left and pos > valids[0]: pos = valids[max(0, pos_index - 1)] if not moving_left and pos < valids[-1]: pos = valids[min(pos_index + 1, len(valids) - 1)] buffer.cursor_position = pos @Condition def _in_stat_buffer(cli): return cli.current_buffer_name.endswith("_STAT_BUFFER") @Condition def _in_normal_stat_buffer(cli): return not cli.current_buffer_name.startswith( tuple(name.upper() for group in statinfo.groups[2:] for name in group)) # Navigation binds @bind(Keys.Left) @self.stat_constraints.listen def _(event): buff = event.current_buffer new_pos = buff.cursor_position + buff.document.get_cursor_left_position( count=event.arg) ensure_cursor_bounds(buff, new_pos) @bind(Keys.Right) @self.stat_constraints.listen def _(event): buff = event.current_buffer new_pos = buff.cursor_position + buff.document.get_cursor_right_position( count=event.arg) ensure_cursor_bounds(buff, new_pos) @bind(Keys.Up) @self.stat_constraints.listen def _(event): current_buffer = event.cli.current_buffer from_stat_buff = _in_normal_stat_buffer(event.cli) self._focus(self.stat_buffer_state.up()) buff = event.cli.current_buffer ensure_cursor_bounds(buff, buff.cursor_position) if _in_normal_stat_buffer(event.cli) and from_stat_buff: buff.cursor_position = current_buffer.cursor_position @bind(Keys.Down) @self.stat_constraints.listen def _(event): current_buffer = event.cli.current_buffer from_stat_buff = _in_normal_stat_buffer(event.cli) self._focus(self.stat_buffer_state.down()) buff = event.cli.current_buffer ensure_cursor_bounds(buff, buff.cursor_position) if _in_normal_stat_buffer(event.cli) and from_stat_buff: buff.cursor_position = current_buffer.cursor_position @bind(Keys.Enter, filter=_in_stat_buffer) @self.stat_constraints.listen def _(event): pass # Control binds @bind(Keys.ControlD) # @bind(Keys.ControlC) def _(event): event.cli.set_return_value(None) @bind(Keys.PageUp) def _(event): self._scroll_up() @bind(Keys.PageDown) def _(event): self._scroll_down() @bind_with_help('?', name='Help', info="Shows the help screen") def _(event): if self._help_showing: self._help_showing = False self._update_info_text() return self.set_info_text(help_text) self._help_showing = True @bind_with_help('n', name='Reroll') def _(event): l = self.reroll() @bind_with_help('y', name='Accept Stats', info="Accept current stats in game") def _(event): ... # TODO @bind_with_help('r', name='Refresh stats') def _(event): self.set_stats(**self.hook.zip(self.hook.read_all())) @bind_with_help(Keys.ControlZ, name='Undo', info="TODO: undo buffer") def _(event): self.print("I'll get to writing undo eventually") @bind_with_help(Keys.ControlY, name='Redo', info="TODO: undo buffer") def _(event): ... # TODO # Testing/Debug binds @bind_with_help('`', name='Embed IPython') def _(event): def do(): # noinspection PyStatementEffect self, event # behold the magic of closures and scope __import__('IPython').embed() os.system('cls') event.cli.run_in_terminal(do) @bind_with_help('t', name='Reroll test') def _(event): def do(): self.print("Running reroll test") num = 50 self.hook.reset_reroll_count() rrbase = self.hook._read_rerolls() t0 = time.time() for x in range(num): self.reroll() self.print("Rerolled") t1 = time.time() rrcount = self.hook._read_rerolls() - rrbase self.print(f'Rolled {num} ({rrcount}) times in {t1-t0:.4f}', 'sec') self.run_in_executor(do) @bind(',') def _(event): self.print("Showing cursor") memhook.Cursor.show() @bind('.') def _(event): self.print("Hiding cursor") memhook.Cursor.hide() @bind('-') def _(event): self.print("got random stats") self.set_stats(**memhook.get_random_stats()) return registry