Library for building powerful interactive command lines in Python
—
Configurable key binding system supporting Vi and Emacs editing modes with customizable key mappings. The key binding system provides the foundation for all keyboard interaction in prompt-toolkit applications.
Core classes for managing and organizing key bindings.
class KeyBindings:
def __init__(self):
"""Create mutable key binding container."""
def add(self, *keys, **kwargs):
"""
Decorator to add key binding.
Parameters:
- *keys: Key sequence (e.g., 'c-c', 'a', 'escape c-c')
- filter: Filter determining when binding is active
- eager: bool, execute immediately without waiting for more keys
- is_global: bool, binding applies globally
- save_before: Function to call before executing
- record_in_macro: bool, record action in Vi macro
Returns:
Decorator function
"""
def remove(self, *keys):
"""
Remove key binding.
Parameters:
- *keys: Key sequence to remove
"""
def get_bindings_for_keys(self, keys):
"""
Get bindings matching key sequence.
Parameters:
- keys: Tuple of key presses
Returns:
List of matching bindings
"""
def get_bindings_starting_with_keys(self, keys):
"""
Get bindings that start with key sequence.
Parameters:
- keys: Tuple of key presses
Returns:
List of bindings starting with keys
"""
class KeyBindingsBase:
"""Abstract base class for key binding containers."""
def get_bindings_for_keys(self, keys):
"""Get bindings for key sequence."""
def get_bindings_starting_with_keys(self, keys):
"""Get bindings starting with key sequence."""
class ConditionalKeyBindings(KeyBindingsBase):
def __init__(self, key_bindings, filter):
"""
Key bindings active only when filter is true.
Parameters:
- key_bindings: KeyBindings instance to wrap
- filter: Filter determining when bindings are active
"""
class DynamicKeyBindings(KeyBindingsBase):
def __init__(self, get_key_bindings):
"""
Dynamic key bindings based on function.
Parameters:
- get_key_bindings: Function returning KeyBindings instance
"""
def merge_key_bindings(bindings_list):
"""
Merge multiple key binding objects.
Parameters:
- bindings_list: List of KeyBindings instances
Returns:
Merged KeyBindings instance
"""Classes representing key press events and their context.
class KeyPress:
def __init__(self, key, data=""):
"""
Represent a single key press.
Parameters:
- key: str, key identifier
- data: str, additional key data
"""
@property
def key(self):
"""str: The key identifier."""
@property
def data(self):
"""str: Additional key data."""
class KeyPressEvent:
def __init__(self, key_press, previous_key_sequence=None):
"""
Key press event with application context.
Parameters:
- key_press: KeyPress instance
- previous_key_sequence: Previous key sequence
"""
@property
def key_sequence(self):
"""List[KeyPress]: Complete key sequence."""
@property
def data(self):
"""str: Key data from current key press."""
@property
def app(self):
"""Application: Current application instance."""
@property
def current_buffer(self):
"""Buffer: Currently focused buffer."""
@property
def arg(self):
"""int: Numeric argument if provided."""
@property
def is_repeat(self):
"""bool: True if this is a repeat event."""Enumeration and constants for key identifiers.
class Keys:
"""Key constant definitions."""
# Control keys
ControlA = "c-a"
ControlB = "c-b"
ControlC = "c-c"
ControlD = "c-d"
ControlE = "c-e"
ControlF = "c-f"
ControlG = "c-g"
ControlH = "c-h"
ControlI = "c-i"
ControlJ = "c-j"
ControlK = "c-k"
ControlL = "c-l"
ControlM = "c-m"
ControlN = "c-n"
ControlO = "c-o"
ControlP = "c-p"
ControlQ = "c-q"
ControlR = "c-r"
ControlS = "c-s"
ControlT = "c-t"
ControlU = "c-u"
ControlV = "c-v"
ControlW = "c-w"
ControlX = "c-x"
ControlY = "c-y"
ControlZ = "c-z"
# Special keys
Enter = "\r"
Escape = "\x1b"
Backspace = "\x7f"
Delete = "\x1b[3~"
Insert = "\x1b[2~"
Home = "\x1b[H"
End = "\x1b[F"
PageUp = "\x1b[5~"
PageDown = "\x1b[6~"
# Arrow keys
Up = "\x1b[A"
Down = "\x1b[B"
Right = "\x1b[C"
Left = "\x1b[D"
# Function keys
F1 = "\x1bOP"
F2 = "\x1bOQ"
F3 = "\x1bOR"
F4 = "\x1bOS"
F5 = "\x1b[15~"
F6 = "\x1b[17~"
F7 = "\x1b[18~"
F8 = "\x1b[19~"
F9 = "\x1b[20~"
F10 = "\x1b[21~"
F11 = "\x1b[23~"
F12 = "\x1b[24~"
# Meta/Alt keys
Escape = "\x1b"
# Shift combinations
ShiftTab = "\x1b[Z"
# Special characters
Tab = "\t"
Space = " "
ALL_KEYS = [
# List of all available key constants
Keys.ControlA, Keys.ControlB, Keys.ControlC,
# ... (complete list of all keys)
]Pre-configured key binding sets for common editing modes.
def load_vi_bindings():
"""
Load Vi-style key bindings.
Returns:
KeyBindings instance with Vi key mappings
Key bindings include:
- Normal mode: h/j/k/l navigation, i/a/o insert modes
- Insert mode: standard text editing
- Command mode: search, replace, file operations
- Visual mode: text selection and manipulation
"""
def load_emacs_bindings():
"""
Load Emacs-style key bindings.
Returns:
KeyBindings instance with Emacs key mappings
Key bindings include:
- Cursor movement: C-f/C-b/C-n/C-p
- Text editing: C-k/C-y/C-d/C-h
- Search: C-s/C-r for incremental search
- Word operations: M-f/M-b/M-d
"""
def load_basic_bindings():
"""
Load basic key bindings for simple editing.
Returns:
KeyBindings instance with basic key mappings
"""
def load_auto_suggest_bindings():
"""
Load key bindings for auto-suggestion features.
Returns:
KeyBindings instance for auto-suggest interaction
"""
def load_open_in_editor_bindings():
"""
Load key bindings for opening content in external editor.
Returns:
KeyBindings instance for editor integration
"""
def load_page_navigation_bindings():
"""
Load key bindings for page navigation.
Returns:
KeyBindings instance for page up/down navigation
"""Classes for processing key sequences and executing bindings.
class KeyProcessor:
def __init__(self, key_bindings):
"""
Process key sequences and execute bindings.
Parameters:
- key_bindings: KeyBindings instance
"""
def feed(self, key_press):
"""
Feed key press to processor.
Parameters:
- key_press: KeyPress instance
Returns:
List of actions to execute
"""
def reset(self):
"""Reset key processor state."""
def empty(self):
"""
Check if processor has pending keys.
Returns:
bool: True if no pending keys
"""
def generate_completions(key_processor):
"""
Generate possible key completions.
Parameters:
- key_processor: KeyProcessor instance
Yields:
Possible key completion sequences
"""Classes for managing Vi editor mode state.
class ViState:
def __init__(self):
"""Vi editor mode state management."""
@property
def input_mode(self):
"""InputMode: Current Vi input mode."""
@input_mode.setter
def input_mode(self, value):
"""Set Vi input mode."""
@property
def waiting_for_digraph(self):
"""bool: True if waiting for digraph input."""
@property
def operator_func(self):
"""Function: Current operator function."""
@property
def yank_buffer(self):
"""str: Current yank buffer content."""
class InputMode(Enum):
"""Vi input modes."""
INSERT = "vi-insert"
NAVIGATION = "vi-navigation"
REPLACE = "vi-replace"
REPLACE_SINGLE = "vi-replace-single"
INSERT_MULTIPLE = "vi-insert-multiple"from prompt_toolkit.application import Application
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout import Layout
from prompt_toolkit.layout.controls import FormattedTextControl
# Create key bindings
kb = KeyBindings()
@kb.add('c-c')
def exit_app(event):
"""Exit on Ctrl-C."""
event.app.exit()
@kb.add('c-h')
def show_help(event):
"""Show help on Ctrl-H."""
print("Help: Press Ctrl-C to exit")
@kb.add('c-l')
def clear_screen(event):
"""Clear screen on Ctrl-L."""
event.app.invalidate()
# Create application with key bindings
app = Application(
layout=Layout(FormattedTextControl('Press Ctrl-H for help, Ctrl-C to exit')),
key_bindings=kb,
full_screen=True
)
app.run()from prompt_toolkit.key_binding import KeyBindings
kb = KeyBindings()
# Single key
@kb.add('q')
def quit_app(event):
event.app.exit()
# Control key combination
@kb.add('c-x', 'c-c')
def exit_emacs_style(event):
"""Emacs-style exit sequence."""
event.app.exit()
# Escape sequences
@kb.add('escape', 'q')
def escape_quit(event):
"""Exit with Escape+q."""
event.app.exit()
# Function key
@kb.add('f1')
def show_help(event):
"""Show help on F1."""
print("F1 Help")
# Multiple key sequence
@kb.add('c-x', 's')
def save_file(event):
"""Save file with Ctrl-X, S."""
print("Saving file...")
# Arrow keys
@kb.add('up')
def move_up(event):
"""Move cursor up."""
print("Moving up")
@kb.add('down')
def move_down(event):
"""Move cursor down."""
print("Moving down")from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.filters import has_focus, vi_mode, emacs_mode
kb = KeyBindings()
# Only active when specific buffer has focus
@kb.add('c-t', filter=has_focus('main'))
def toggle_feature(event):
"""Toggle feature when main buffer focused."""
print("Feature toggled")
# Vi mode specific bindings
@kb.add('i', filter=vi_mode)
def vi_insert_mode(event):
"""Enter Vi insert mode."""
from prompt_toolkit.key_binding.vi_state import InputMode
event.app.vi_state.input_mode = InputMode.INSERT
# Emacs mode specific bindings
@kb.add('c-n', filter=emacs_mode)
def emacs_next_line(event):
"""Emacs next line."""
buffer = event.current_buffer
buffer.cursor_down()
# Custom filter
from prompt_toolkit.filters import Condition
@Condition
def custom_condition():
return some_application_state
@kb.add('c-k', filter=custom_condition)
def custom_action(event):
"""Action only when custom condition is true."""
print("Custom action executed")from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.filters import has_focus
kb = KeyBindings()
@kb.add('c-a')
def move_to_start(event):
"""Move cursor to start of line."""
event.current_buffer.cursor_position = 0
@kb.add('c-e')
def move_to_end(event):
"""Move cursor to end of line."""
buffer = event.current_buffer
buffer.cursor_position = len(buffer.text)
@kb.add('c-k')
def kill_line(event):
"""Kill from cursor to end of line."""
buffer = event.current_buffer
buffer.delete(count=len(buffer.text) - buffer.cursor_position)
@kb.add('c-w')
def kill_word(event):
"""Kill word backwards."""
buffer = event.current_buffer
pos = buffer.document.find_start_of_word(count=-1)
if pos:
buffer.delete_before_cursor(count=-pos)
@kb.add('c-y')
def yank(event):
"""Yank (paste) text."""
# Implementation would restore previously killed text
passfrom prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.bindings.vi import load_vi_bindings
from prompt_toolkit.filters import vi_mode, vi_navigation_mode, vi_insert_mode
# Load base Vi bindings
kb = load_vi_bindings()
# Add custom Vi bindings
@kb.add('j', 'j', filter=vi_insert_mode)
def jj_escape(event):
"""Exit insert mode with 'jj'."""
from prompt_toolkit.key_binding.vi_state import InputMode
event.app.vi_state.input_mode = InputMode.NAVIGATION
@kb.add('g', 'g', filter=vi_navigation_mode)
def go_to_top(event):
"""Go to top of buffer with 'gg'."""
event.current_buffer.cursor_position = 0
@kb.add('G', filter=vi_navigation_mode)
def go_to_bottom(event):
"""Go to bottom of buffer with 'G'."""
buffer = event.current_buffer
buffer.cursor_position = len(buffer.text)
# Custom Vi command
@kb.add('c-x', 'c-e', filter=vi_mode)
def edit_in_editor(event):
"""Edit buffer in external editor."""
# Implementation would open external editor
passfrom prompt_toolkit.key_binding import KeyBindings, DynamicKeyBindings
from prompt_toolkit.filters import Condition
# Different key binding sets
basic_bindings = KeyBindings()
advanced_bindings = KeyBindings()
@basic_bindings.add('c-c')
def basic_exit(event):
event.app.exit()
@advanced_bindings.add('c-c')
def advanced_exit(event):
print("Advanced exit")
event.app.exit()
@advanced_bindings.add('f1')
def advanced_help(event):
print("Advanced help")
# Dynamic selection based on application state
def get_current_bindings():
if application_in_advanced_mode:
return advanced_bindings
else:
return basic_bindings
# Use dynamic bindings
dynamic_kb = DynamicKeyBindings(get_current_bindings)from prompt_toolkit.key_binding import KeyBindings
kb = KeyBindings()
@kb.add('c-u')
def universal_argument(event):
"""Set universal argument."""
# This would set a numeric argument for next command
event.app.key_processor.arg = event.arg or 4
@kb.add('c-k')
def kill_line_with_arg(event):
"""Kill lines with numeric argument."""
buffer = event.current_buffer
count = event.arg or 1
for _ in range(count):
# Kill one line
buffer.delete(count=len(buffer.document.current_line_after_cursor))
if buffer.document.cursor_position < len(buffer.text):
buffer.delete(count=1) # Delete newline
@kb.add('c-d')
def delete_with_arg(event):
"""Delete characters with numeric argument."""
buffer = event.current_buffer
count = event.arg or 1
buffer.delete(count=count)Install with Tessl CLI
npx tessl i tessl/pypi-prompt-toolkit