CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-prompt-toolkit

Library for building powerful interactive command lines in Python

Pending
Overview
Eval results
Files

key-bindings.mddocs/

Key Bindings and Input

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.

Capabilities

Key Binding Containers

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
    """

Key Press Events

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."""

Key Constants

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)
]

Built-in Key Binding Sets

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
    """

Key Processing

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
    """

Vi Mode State

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"

Usage Examples

Basic Key Bindings

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()

Key Sequences and Combinations

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")

Conditional Key Bindings

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")

Buffer Key Bindings

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
    pass

Vi Key Bindings

from 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
    pass

Dynamic Key Bindings

from 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)

Key Binding with Arguments

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

docs

application.md

completion.md

index.md

key-bindings.md

layout.md

prompts.md

styling.md

tile.json