GNU readline support for Python on platforms without readline
—
Advanced customization through event hooks that execute at specific points in the readline process, enabling custom display and input handling. These hooks provide fine-grained control over readline's behavior at key moments in the input cycle.
Function executed just before readline prints the first prompt, allowing for initialization and setup before user interaction begins.
def set_startup_hook(function=None):
"""
Set or remove the startup_hook function.
Parameters:
- function (callable, optional): Function called with no arguments just before
readline prints the first prompt. If None, removes hook.
Returns:
None
"""Usage Example:
import readline
import os
import time
def startup_initialization():
"""Initialize readline environment at startup"""
# Load custom history file
history_file = os.path.expanduser('~/.myapp_history')
try:
readline.read_history_file(history_file)
except FileNotFoundError:
pass
# Set up custom completion
readline.parse_and_bind('tab: complete')
# Display welcome message
print(f"Welcome! Loaded {readline.get_current_history_length()} history entries.")
print("Type 'help' for available commands.")
# Set startup hook
readline.set_startup_hook(startup_initialization)
# The hook will be called before the first input() call
user_input = input("Command: ")Function executed after the first prompt has been printed but before readline starts reading input characters, useful for pre-populating the command line or performing just-in-time setup.
def set_pre_input_hook(function=None):
"""
Set or remove the pre_input_hook function.
Parameters:
- function (callable, optional): Function called with no arguments after the first
prompt has been printed and just before readline starts
reading input characters. If None, removes hook.
Returns:
None
Note: Only available when compiled with HAVE_RL_PRE_INPUT_HOOK
"""Usage Example:
import readline
import datetime
def pre_input_setup():
"""Set up command line before input begins"""
# Insert timestamp prefix
timestamp = datetime.datetime.now().strftime("%H:%M ")
readline.insert_text(f"[{timestamp}] ")
# Or insert last command for editing
history_length = readline.get_current_history_length()
if history_length > 0:
last_command = readline.get_history_item(history_length)
if last_command and last_command.startswith("repeat:"):
# Remove "repeat:" prefix and insert the command
command = last_command[7:] # Remove "repeat:" prefix
readline.insert_text(command)
# Set pre-input hook
readline.set_pre_input_hook(pre_input_setup)
# Hook executes after prompt is shown but before input starts
command = input("CMD> ")import readline
import os
class SmartPreInput:
def __init__(self):
self.context_aware = True
self.auto_suggest = True
def setup_input(self):
"""Intelligent pre-input setup based on context"""
if not self.context_aware:
return
# Check current directory for context clues
current_dir = os.getcwd()
files = os.listdir(current_dir)
# Auto-suggest based on directory contents
if 'Makefile' in files:
readline.insert_text("make ")
elif 'package.json' in files:
readline.insert_text("npm ")
elif 'requirements.txt' in files:
readline.insert_text("pip install -r requirements.txt")
elif 'setup.py' in files:
readline.insert_text("python setup.py ")
elif any(f.endswith('.py') for f in files):
py_files = [f for f in files if f.endswith('.py')]
if 'main.py' in py_files:
readline.insert_text("python main.py ")
else:
readline.insert_text("python ")
smart_input = SmartPreInput()
readline.set_pre_input_hook(smart_input.setup_input)Function for customizing how completion matches are displayed to the user, allowing for enhanced completion interfaces and custom formatting.
def set_completion_display_matches_hook(function=None):
"""
Set or remove the completion display function.
Parameters:
- function (callable, optional): Function called as function(substitution, matches,
longest_match_length) when displaying completion matches.
If None, removes hook.
Returns:
None
"""Usage Example:
import readline
def custom_completion_display(substitution, matches, longest_match_length):
"""Custom completion display with enhanced formatting"""
print(f"\n📋 {len(matches)} completions for '{substitution}':")
# Group matches by type or category
files = []
dirs = []
commands = []
for match in matches:
if match.endswith('/'):
dirs.append(match)
elif '.' in match:
files.append(match)
else:
commands.append(match)
# Display grouped results
if dirs:
print(" 📁 Directories:")
for d in dirs[:5]: # Show first 5
print(f" {d}")
if len(dirs) > 5:
print(f" ... and {len(dirs) - 5} more directories")
if files:
print(" 📄 Files:")
for f in files[:5]:
print(f" {f}")
if len(files) > 5:
print(f" ... and {len(files) - 5} more files")
if commands:
print(" ⚡ Commands:")
for c in commands[:5]:
print(f" {c}")
if len(commands) > 5:
print(f" ... and {len(commands) - 5} more commands")
print() # Extra line for readability
# Set custom display hook
readline.set_completion_display_matches_hook(custom_completion_display)import readline
import os
import stat
def detailed_completion_display(substitution, matches, longest_match_length):
"""Detailed completion display with file information"""
print(f"\n🔍 Completions for '{substitution}' ({len(matches)} matches):")
# Sort matches: directories first, then files
dirs = [m for m in matches if m.endswith('/')]
files = [m for m in matches if not m.endswith('/')]
sorted_matches = dirs + files
# Display with details
for i, match in enumerate(sorted_matches[:10]): # Show first 10
if match.endswith('/'):
icon = "📁"
details = "directory"
else:
icon = "📄"
try:
# Get file size and modification time
stats = os.stat(match)
size = stats.st_size
mtime = stats.st_mtime
# Format size
if size < 1024:
size_str = f"{size}B"
elif size < 1024 * 1024:
size_str = f"{size // 1024}KB"
else:
size_str = f"{size // (1024 * 1024)}MB"
details = f"{size_str}"
# Check if executable
if os.access(match, os.X_OK):
icon = "⚡"
details += " (executable)"
except OSError:
details = "file"
print(f" {i+1:2d}. {icon} {match:<{longest_match_length + 2}} {details}")
if len(matches) > 10:
print(f" ... and {len(matches) - 10} more matches")
print()
readline.set_completion_display_matches_hook(detailed_completion_display)import readline
import atexit
import os
class InteractiveShell:
def __init__(self):
self.history_file = os.path.expanduser('~/.myshell_history')
self.setup_readline()
def setup_readline(self):
"""Complete readline setup with all hooks"""
# Set all hooks
readline.set_startup_hook(self.startup_hook)
readline.set_pre_input_hook(self.pre_input_hook)
readline.set_completion_display_matches_hook(self.display_hook)
# Configure completion
readline.set_completer(self.completer)
readline.parse_and_bind('tab: complete')
# Set up history
try:
readline.read_history_file(self.history_file)
except FileNotFoundError:
pass
atexit.register(self.cleanup)
def startup_hook(self):
"""Initialization at startup"""
print("🚀 Interactive shell ready!")
readline.set_history_length(1000)
def pre_input_hook(self):
"""Pre-input setup"""
# Could insert context-aware prefixes here
pass
def display_hook(self, substitution, matches, longest_match_length):
"""Custom completion display"""
if len(matches) > 1:
print(f"\n💡 {len(matches)} options for '{substitution}':")
for i, match in enumerate(matches[:5]):
print(f" {i+1}. {match}")
if len(matches) > 5:
print(f" ... and {len(matches) - 5} more")
print()
def completer(self, text, state):
"""Simple file completion"""
import glob
matches = glob.glob(text + '*')
return matches[state] if state < len(matches) else None
def cleanup(self):
"""Cleanup when exiting"""
readline.write_history_file(self.history_file)
# Set up interactive shell
shell = InteractiveShell()Note: The set_pre_input_hook() function is only available when compiled with HAVE_RL_PRE_INPUT_HOOK. It may not be available on all systems or readline installations.
Install with Tessl CLI
npx tessl i tessl/pypi-readline