GNU readline support for Python on platforms without readline
—
Comprehensive tab completion system with customizable completion functions, delimiter configuration, and completion context access. These functions enable sophisticated autocompletion behavior for interactive command-line applications.
Functions for setting up and managing custom completion behavior, allowing applications to provide context-aware autocompletion.
def set_completer(function=None):
"""
Set or remove the completer function.
Parameters:
- function (callable, optional): Completer function called as function(text, state)
for state in 0, 1, 2, ... until it returns None.
If None, removes current completer.
Returns:
None
"""
def get_completer():
"""
Return current completer function.
Returns:
callable or None: Current completer function, or None if no completer is set
"""Usage Example:
import readline
import os
import glob
def file_completer(text, state):
"""Simple file completion example"""
# Get all files/directories that start with text
matches = glob.glob(text + '*')
# Add trailing slash to directories
matches = [match + '/' if os.path.isdir(match) else match for match in matches]
# Return the match for this state, or None when exhausted
return matches[state] if state < len(matches) else None
# Set the completer
readline.set_completer(file_completer)
# Enable tab completion
readline.parse_and_bind('tab: complete')
# Check current completer
current_completer = readline.get_completer()
print(f"Current completer: {current_completer}")
# Remove completer
readline.set_completer(None)import readline
import keyword
import builtins
class PythonCompleter:
def __init__(self):
self.matches = []
def complete(self, text, state):
"""Complete Python keywords, builtins, and local variables"""
if state == 0: # First call, generate matches
self.matches = []
# Add Python keywords
self.matches.extend([kw for kw in keyword.kwlist if kw.startswith(text)])
# Add built-in functions
self.matches.extend([name for name in dir(builtins) if name.startswith(text)])
# Add local variables (simplified)
import __main__
if hasattr(__main__, '__dict__'):
local_vars = [name for name in __main__.__dict__.keys()
if name.startswith(text) and not name.startswith('_')]
self.matches.extend(local_vars)
return self.matches[state] if state < len(self.matches) else None
# Set up Python completion
python_completer = PythonCompleter()
readline.set_completer(python_completer.complete)Functions for accessing information about the current completion attempt, enabling context-sensitive completion behavior.
def get_completion_type():
"""
Get the type of completion being attempted.
Returns:
int: Completion type code indicating the kind of completion
"""
def get_begidx():
"""
Get the beginning index of the readline tab-completion scope.
Returns:
int: Starting index of text being completed within the line buffer
"""
def get_endidx():
"""
Get the ending index of the readline tab-completion scope.
Returns:
int: Ending index of text being completed within the line buffer
"""Usage Example:
import readline
def context_aware_completer(text, state):
"""Completer that uses completion context"""
if state == 0: # First call, analyze context
# Get completion boundaries
begidx = readline.get_begidx()
endidx = readline.get_endidx()
# Get the full line being edited
line_buffer = readline.get_line_buffer()
# Extract the word being completed and its context
before_cursor = line_buffer[:begidx]
completion_text = line_buffer[begidx:endidx]
print(f"\nCompletion context:")
print(f" Line: '{line_buffer}'")
print(f" Before cursor: '{before_cursor}'")
print(f" Completing: '{completion_text}'")
print(f" Indexes: {begidx}-{endidx}")
print(f" Completion type: {readline.get_completion_type()}")
# Simple completion for demonstration
options = ['hello', 'help', 'history', 'home']
matches = [opt for opt in options if opt.startswith(text)]
return matches[state] if state < len(matches) else None
readline.set_completer(context_aware_completer)Functions for controlling which characters are treated as word boundaries during tab completion.
def set_completer_delims(string):
"""
Set the readline word delimiters for tab-completion.
Parameters:
- string (str): Characters to treat as word delimiters
Returns:
None
"""
def get_completer_delims():
"""
Get the readline word delimiters for tab-completion.
Returns:
str: Current word delimiter characters
"""Usage Example:
import readline
# Check current delimiters
current_delims = readline.get_completer_delims()
print(f"Current delimiters: '{current_delims}'")
# Set custom delimiters (default is usually ' \t\n`@$><=;|&{(')
readline.set_completer_delims(' \t\n') # Only space, tab, and newline
# For file path completion, you might want to exclude '/' and '.'
readline.set_completer_delims(' \t\n=')
# For programming language completion, include more programming symbols
readline.set_completer_delims(' \t\n`@$><=;|&{()}[].,')import readline
import os
import subprocess
class CommandCompleter:
def __init__(self):
self.commands = {
'cd': self._complete_directories,
'ls': self._complete_files,
'python': self._complete_python_files,
'git': self._complete_git_commands,
}
def complete(self, text, state):
line_buffer = readline.get_line_buffer()
words = line_buffer.split()
if not words:
return None
command = words[0]
if command in self.commands:
return self.commands[command](text, state, words)
# Default file completion
return self._complete_files(text, state, words)
def _complete_directories(self, text, state, words):
"""Complete only directories for cd command"""
import glob
matches = [d for d in glob.glob(text + '*') if os.path.isdir(d)]
matches = [d + '/' for d in matches] # Add trailing slash
return matches[state] if state < len(matches) else None
def _complete_files(self, text, state, words):
"""Complete files and directories"""
import glob
matches = glob.glob(text + '*')
matches = [m + '/' if os.path.isdir(m) else m for m in matches]
return matches[state] if state < len(matches) else None
def _complete_python_files(self, text, state, words):
"""Complete .py files"""
import glob
matches = glob.glob(text + '*.py')
return matches[state] if state < len(matches) else None
def _complete_git_commands(self, text, state, words):
"""Complete git subcommands"""
if len(words) == 1: # Completing git subcommand
git_commands = ['add', 'commit', 'push', 'pull', 'status', 'log', 'diff', 'branch']
matches = [cmd for cmd in git_commands if cmd.startswith(text)]
return matches[state] if state < len(matches) else None
return None
# Set up command-specific completion
command_completer = CommandCompleter()
readline.set_completer(command_completer.complete)import readline
class HierarchicalCompleter:
def __init__(self):
self.completions = {
'show': {
'interface': ['eth0', 'eth1', 'lo', 'wlan0'],
'route': ['default', 'table', 'all'],
'ip': ['route', 'addr', 'link'],
},
'set': {
'interface': ['eth0', 'eth1', 'wlan0'],
'route': ['add', 'del', 'change'],
}
}
def complete(self, text, state):
line_buffer = readline.get_line_buffer()
words = line_buffer.split()
if not words:
return None
# Navigate through completion hierarchy
current_level = self.completions
for word in words[:-1]: # All words except the one being completed
if word in current_level and isinstance(current_level[word], dict):
current_level = current_level[word]
else:
return None
# Get matches at current level
if isinstance(current_level, dict):
matches = [key for key in current_level.keys() if key.startswith(text)]
elif isinstance(current_level, list):
matches = [item for item in current_level if item.startswith(text)]
else:
matches = []
return matches[state] if state < len(matches) else None
# Set up hierarchical completion
hierarchical_completer = HierarchicalCompleter()
readline.set_completer(hierarchical_completer.complete)Install with Tessl CLI
npx tessl i tessl/pypi-readline