def test_feed_simple(processor, handlers): with set_dummy_app(): processor.feed(KeyPress(Keys.ControlX, '\x18')) processor.feed(KeyPress(Keys.ControlC, '\x03')) processor.process_keys() assert handlers.called == ['controlx_controlc']
def test_control_square_closed_any(processor, handlers): with set_dummy_app(): processor.feed(KeyPress(Keys.ControlSquareClose, '')) processor.feed(KeyPress('C', 'C')) processor.process_keys() assert handlers.called == ['control_square_close_any']
def test_common_prefix(processor, handlers): with set_dummy_app(): # Sending Control_X should not yet do anything, because there is # another sequence starting with that as well. processor.feed(KeyPress(Keys.ControlX, '')) processor.process_keys() assert handlers.called == [] # When another key is pressed, we know that we did not meant the longer # "ControlX ControlC" sequence and the callbacks are called. processor.feed(KeyPress(Keys.ControlD, '')) processor.process_keys() assert handlers.called == ['control_x', 'control_d']
def _merge_paired_surrogates( key_presses: List[KeyPress]) -> Iterator[KeyPress]: """ Combines consecutive KeyPresses with high and low surrogates into single characters """ buffered_high_surrogate = None for key in key_presses: is_text = not isinstance(key.key, Keys) is_high_surrogate = is_text and "\uD800" <= key.key <= "\uDBFF" is_low_surrogate = is_text and "\uDC00" <= key.key <= "\uDFFF" if buffered_high_surrogate: if is_low_surrogate: # convert high surrogate + low surrogate to single character fullchar = ((buffered_high_surrogate.key + key.key).encode( "utf-16-le", "surrogatepass").decode("utf-16-le")) key = KeyPress(fullchar, fullchar) else: yield buffered_high_surrogate buffered_high_surrogate = None if is_high_surrogate: buffered_high_surrogate = key else: yield key if buffered_high_surrogate: yield buffered_high_surrogate
def _scroll_up(event: E) -> None: """ Scroll up event without cursor position. """ # We don't receive a cursor position, so we don't know which window to # scroll. Just send an 'up' key press instead. event.key_processor.feed(KeyPress(Keys.Up), first=True)
def simulate_key(menu, key): """ Simulate passing `key` to a menu """ control = to_container(menu).content key_bindings = control.key_bindings key_processor = KeyProcessor(key_bindings) key_processor.feed(KeyPress(key)) key_processor.process_keys()
def _newline2(event: E) -> None: r""" By default, handle \n as if it were a \r (enter). (It appears that some terminals send \n instead of \r when pressing enter. - at least the Linux subsystem for Windows.) """ event.key_processor.feed(KeyPress(Keys.ControlM, "\r"), first=True)
def read(self) -> Iterable[KeyPress]: """ Return a list of `KeyPress` instances. It won't return anything when there was nothing to read. (This function doesn't block.) http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx """ max_count = 2048 # Max events to read at the same time. read = DWORD(0) arrtype = INPUT_RECORD * max_count input_records = arrtype() # Check whether there is some input to read. `ReadConsoleInputW` would # block otherwise. # (Actually, the event loop is responsible to make sure that this # function is only called when there is something to read, but for some # reason this happened in the asyncio_win32 loop, and it's better to be # safe anyway.) if not wait_for_handles([self.handle], timeout=0): return # Get next batch of input event. windll.kernel32.ReadConsoleInputW(self.handle, pointer(input_records), max_count, pointer(read)) # First, get all the keys from the input buffer, in order to determine # whether we should consider this a paste event or not. all_keys = list(self._get_keys(read, input_records)) # Fill in 'data' for key presses. all_keys = [self._insert_key_data(key) for key in all_keys] # Correct non-bmp characters that are passed as separate surrogate codes all_keys = list(self._merge_paired_surrogates(all_keys)) if self.recognize_paste and self._is_paste(all_keys): gen = iter(all_keys) k: Optional[KeyPress] for k in gen: # Pasting: if the current key consists of text or \n, turn it # into a BracketedPaste. data = [] while k and (not isinstance(k.key, Keys) or k.key == Keys.ControlJ): data.append(k.data) try: k = next(gen) except StopIteration: k = None if data: yield KeyPress(Keys.BracketedPaste, "".join(data)) if k is not None: yield k else: for k2 in all_keys: yield k2
def _insert_key_data(self, key_press): """ Insert KeyPress data, for vt100 compatibility. """ if key_press.data: return key_press data = REVERSE_ANSI_SEQUENCES.get(key_press.key, '') return KeyPress(key_press.key, data)
def _handle_mouse(self, ev): """ Handle mouse events. Return a list of KeyPress instances. """ FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 FROM_LEFT_2ND_BUTTON_PRESSED = 0x2 MOUSE_WHEEL_EFLAG = 0x4 result = [] # Check event type. if ev.ButtonState in [ FROM_LEFT_1ST_BUTTON_PRESSED, FROM_LEFT_2ND_BUTTON_PRESSED ]: # On a key press, generate both the mouse down and up event. for event_type in [ MouseEventType.MOUSE_DOWN, MouseEventType.MOUSE_UP ]: try: data = ";".join([ event_type.value, str(ev.MousePosition.X), str(ev.MousePosition.Y) ] if not isinstance(event_type, str) else [ event_type, str(ev.MousePosition.X), str(ev.MousePosition.Y) ]) result.append(KeyPress(Keys.WindowsMouseEvent, data)) except Exception: break # it's a scroll elif ev.EventFlags == MOUSE_WHEEL_EFLAG: # as described here: # https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput # ButtonState is mouseData member if ev.ButtonState > 0: result.append(KeyPress(Keys.ScrollUp)) if ev.ButtonState < 0: result.append(KeyPress(Keys.ScrollDown)) return result
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 prefix_meta(event): """ Metafy the next character typed. This is for keyboards without a meta key. Sometimes people also want to bind other keys to Meta, e.g. 'jj':: key_bindings.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta) """ # ('first' should be true, because we want to insert it at the current # position in the queue.) event.app.key_processor.feed(KeyPress(Keys.Escape), first=True)
def _insert_key_data(self, key_press: KeyPress) -> KeyPress: """ Insert KeyPress data, for vt100 compatibility. """ if key_press.data: return key_press if isinstance(key_press.key, Keys): data = REVERSE_ANSI_SEQUENCES.get(key_press.key, "") else: data = "" return KeyPress(key_press.key, data)
def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> List[KeyPress]: """ Handle mouse events. Return a list of KeyPress instances. """ event_flags = ev.EventFlags button_state = ev.ButtonState event_type: Optional[MouseEventType] = None button: MouseButton = MouseButton.NONE # Scroll events. if event_flags & MOUSE_WHEELED: if button_state > 0: event_type = MouseEventType.SCROLL_UP else: event_type = MouseEventType.SCROLL_DOWN else: # Handle button state for non-scroll events. if button_state == FROM_LEFT_1ST_BUTTON_PRESSED: button = MouseButton.LEFT elif button_state == RIGHTMOST_BUTTON_PRESSED: button = MouseButton.RIGHT # Move events. if event_flags & MOUSE_MOVED: event_type = MouseEventType.MOUSE_MOVE # No key pressed anymore: mouse up. if event_type is None: if button_state > 0: # Some button pressed. event_type = MouseEventType.MOUSE_DOWN else: # No button pressed. event_type = MouseEventType.MOUSE_UP data = ";".join( [ button.value, event_type.value, str(ev.MousePosition.X), str(ev.MousePosition.Y), ] ) return [KeyPress(Keys.WindowsMouseEvent, data)]
def _handle_mouse(self, ev): """ Handle mouse events. Return a list of KeyPress instances. """ FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 result = [] # Check event type. if ev.ButtonState == FROM_LEFT_1ST_BUTTON_PRESSED: # On a key press, generate both the mouse down and up event. for event_type in [MouseEventType.MOUSE_DOWN, MouseEventType.MOUSE_UP]: data = ";".join( [str(event_type), str(ev.MousePosition.X), str(ev.MousePosition.Y)] ) result.append(KeyPress(Keys.WindowsMouseEvent, data)) return result
def _handle_mouse(self, ev: MOUSE_EVENT_RECORD) -> List[KeyPress]: """ Handle mouse events. Return a list of KeyPress instances. """ FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 MOUSE_MOVED = 0x0001 MOUSE_WHEELED = 0x0004 event_flags = ev.EventFlags button_state = ev.ButtonState result = [] event_type: Optional[MouseEventType] = None # Move events. if event_flags & MOUSE_MOVED: if button_state == FROM_LEFT_1ST_BUTTON_PRESSED: event_type = MouseEventType.MOUSE_DOWN_MOVE # Scroll events. elif event_flags & MOUSE_WHEELED: if button_state > 0: event_type = MouseEventType.SCROLL_UP else: event_type = MouseEventType.SCROLL_DOWN # Mouse down (left button). elif button_state == FROM_LEFT_1ST_BUTTON_PRESSED: event_type = MouseEventType.MOUSE_DOWN # No key pressed anymore: mouse up. else: event_type = MouseEventType.MOUSE_UP if event_type is not None: data = ";".join([ event_type.value, str(ev.MousePosition.X), str(ev.MousePosition.Y) ]) result.append(KeyPress(Keys.WindowsMouseEvent, data)) return result
def test_feed_several(processor, handlers): with set_dummy_app(): # First an unknown key first. processor.feed(KeyPress(Keys.ControlQ, '')) processor.process_keys() assert handlers.called == [] # Followed by a know key sequence. processor.feed(KeyPress(Keys.ControlX, '')) processor.feed(KeyPress(Keys.ControlC, '')) processor.process_keys() assert handlers.called == ['controlx_controlc'] # Followed by another unknown sequence. processor.feed(KeyPress(Keys.ControlR, '')) processor.feed(KeyPress(Keys.ControlS, '')) # Followed again by a know key sequence. processor.feed(KeyPress(Keys.ControlD, '')) processor.process_keys() assert handlers.called == ['controlx_controlc', 'control_d']
def _(event): " Map 'kj' to Escape. " event.cli.key_processor.feed(KeyPress(Keys.Escape))
def _(event): " Map 'eu' to Escape. " event.cli.key_processor.feed(KeyPress(Keys("escape")))
def _(event): """ Typing 'kj' in Insert mode, should go back to navigation mode. """ _logger.debug('Detected kj keys.') event.cli.key_processor.feed(KeyPress(Keys.Escape))
def _scroll_down(event: E) -> None: """ Scroll down event without cursor position. """ event.key_processor.feed(KeyPress(Keys.Down), first=True)
def _(event): " Map 'jj' to Escape. " event.cli.input_processor.feed(KeyPress(Keys.Escape))
def _(event): event.cli.key_processor.feed(KeyPress(Keys.Escape))
def _(event): event.cli.key_processor.feed_multiple([KeyPress("g"), KeyPress("_")])
def _event_to_key_presses(self, ev: KEY_EVENT_RECORD) -> List[KeyPress]: """ For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. """ assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown result: Optional[KeyPress] = None control_key_state = ev.ControlKeyState u_char = ev.uChar.UnicodeChar # Use surrogatepass because u_char may be an unmatched surrogate ascii_char = u_char.encode("utf-8", "surrogatepass") # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be the # unicode code point truncated to 1 byte. See also: # https://github.com/ipython/ipython/issues/10004 # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389 if u_char == "\x00": if ev.VirtualKeyCode in self.keycodes: result = KeyPress(self.keycodes[ev.VirtualKeyCode], "") else: if ascii_char in self.mappings: if self.mappings[ascii_char] == Keys.ControlJ: u_char = ( "\n" # Windows sends \n, turn into \r for unix compatibility. ) result = KeyPress(self.mappings[ascii_char], u_char) else: result = KeyPress(u_char, u_char) # First we handle Shift-Control-Arrow/Home/End (need to do this first) if ((control_key_state & self.LEFT_CTRL_PRESSED or control_key_state & self.RIGHT_CTRL_PRESSED) and control_key_state & self.SHIFT_PRESSED and result): mapping: Dict[str, str] = { Keys.Left: Keys.ControlShiftLeft, Keys.Right: Keys.ControlShiftRight, Keys.Up: Keys.ControlShiftUp, Keys.Down: Keys.ControlShiftDown, Keys.Home: Keys.ControlShiftHome, Keys.End: Keys.ControlShiftEnd, Keys.Insert: Keys.ControlShiftInsert, Keys.PageUp: Keys.ControlShiftPageUp, Keys.PageDown: Keys.ControlShiftPageDown, } result.key = mapping.get(result.key, result.key) # Correctly handle Control-Arrow/Home/End and Control-Insert/Delete keys. if (control_key_state & self.LEFT_CTRL_PRESSED or control_key_state & self.RIGHT_CTRL_PRESSED) and result: mapping = { Keys.Left: Keys.ControlLeft, Keys.Right: Keys.ControlRight, Keys.Up: Keys.ControlUp, Keys.Down: Keys.ControlDown, Keys.Home: Keys.ControlHome, Keys.End: Keys.ControlEnd, Keys.Insert: Keys.ControlInsert, Keys.Delete: Keys.ControlDelete, Keys.PageUp: Keys.ControlPageUp, Keys.PageDown: Keys.ControlPageDown, } result.key = mapping.get(result.key, result.key) # Turn 'Tab' into 'BackTab' when shift was pressed. # Also handle other shift-key combination if control_key_state & self.SHIFT_PRESSED and result: mapping = { Keys.Tab: Keys.BackTab, Keys.Left: Keys.ShiftLeft, Keys.Right: Keys.ShiftRight, Keys.Up: Keys.ShiftUp, Keys.Down: Keys.ShiftDown, Keys.Home: Keys.ShiftHome, Keys.End: Keys.ShiftEnd, Keys.Insert: Keys.ShiftInsert, Keys.Delete: Keys.ShiftDelete, Keys.PageUp: Keys.ShiftPageUp, Keys.PageDown: Keys.ShiftPageDown, } result.key = mapping.get(result.key, result.key) # Turn 'Space' into 'ControlSpace' when control was pressed. if ((control_key_state & self.LEFT_CTRL_PRESSED or control_key_state & self.RIGHT_CTRL_PRESSED) and result and result.data == " "): result = KeyPress(Keys.ControlSpace, " ") # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot # detect this combination. But it's really practical on Windows.) if ((control_key_state & self.LEFT_CTRL_PRESSED or control_key_state & self.RIGHT_CTRL_PRESSED) and result and result.key == Keys.ControlJ): return [KeyPress(Keys.Escape, ""), result] # Return result. If alt was pressed, prefix the result with an # 'Escape' key, just like unix VT100 terminals do. # NOTE: Only replace the left alt with escape. The right alt key often # acts as altgr and is used in many non US keyboard layouts for # typing some special characters, like a backslash. We don't want # all backslashes to be prefixed with escape. (Esc-\ has a # meaning in E-macs, for instance.) if result: meta_pressed = control_key_state & self.LEFT_ALT_PRESSED if meta_pressed: return [KeyPress(Keys.Escape, ""), result] else: return [result] else: return []
def _(event): " Scroll down event without cursor position. " event.key_processor.feed(KeyPress(Keys.Down), first=True)
def _event_to_key_presses(self, ev): """ For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. """ assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown result = None u_char = ev.uChar.UnicodeChar ascii_char = u_char.encode('utf-8') # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be latin-1 # encoded. See also: # https://github.com/ipython/ipython/issues/10004 # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389 if u_char == '\x00': if ev.VirtualKeyCode in self.keycodes: result = KeyPress(self.keycodes[ev.VirtualKeyCode], '') else: if ascii_char in self.mappings: if self.mappings[ascii_char] == Keys.ControlJ: u_char = '\n' # Windows sends \n, turn into \r for unix compatibility. result = KeyPress(self.mappings[ascii_char], u_char) else: result = KeyPress(u_char, u_char) # Correctly handle Control-Arrow keys. if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result: if result.key == Keys.Left: result.key = Keys.ControlLeft if result.key == Keys.Right: result.key = Keys.ControlRight if result.key == Keys.Up: result.key = Keys.ControlUp if result.key == Keys.Down: result.key = Keys.ControlDown # Turn 'Tab' into 'BackTab' when shift was pressed. if ev.ControlKeyState & self.SHIFT_PRESSED and result: if result.key == Keys.Tab: result.key = Keys.BackTab # Turn 'Space' into 'ControlSpace' when control was pressed. if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and result.data == ' ': result = KeyPress(Keys.ControlSpace, ' ') # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot # detect this combination. But it's really practical on Windows.) if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and \ result.key == Keys.ControlJ: return [KeyPress(Keys.Escape, ''), result] # Return result. If alt was pressed, prefix the result with an # 'Escape' key, just like unix VT100 terminals do. # NOTE: Only replace the left alt with escape. The right alt key often # acts as altgr and is used in many non US keyboard layouts for # typing some special characters, like a backslash. We don't want # all backslashes to be prefixed with escape. (Esc-\ has a # meaning in E-macs, for instance.) if result: meta_pressed = ev.ControlKeyState & self.LEFT_ALT_PRESSED if meta_pressed: return [KeyPress(Keys.Escape, ''), result] else: return [result] else: return []
def _(event): event.cli.key_processor.feed(KeyPress(Keys.Up))
def vi_movement_mode(event): """Exit vi mode.""" event.cli.key_processor.feed(KeyPress(Keys.Escape))
def _(event): event.cli.key_processor.feed(KeyPress("0"))