The standard Python readline extension statically linked against the GNU readline library.
—
Programmable tab completion system with customizable word breaking and completion functions. The gnureadline completion system provides context-aware completion with full control over behavior and display.
Set up and manage the function that provides completion suggestions.
def set_completer(function):
"""
Set the completion function.
Parameters:
- function: Completion function(text: str, state: int) -> str or None
Should return the state-th completion for text, or None when done
"""
def get_completer():
"""
Get the current completion function.
Returns:
callable or None: Current completion function
"""import gnureadline
import os
def file_completer(text: str, state: int) -> str:
"""Complete file and directory names."""
if state == 0:
# First call - generate list of matches
if not hasattr(file_completer, 'matches'):
file_completer.matches = []
# Get directory to search in
dirname = os.path.dirname(text) or '.'
basename = os.path.basename(text)
try:
# Find matching files
file_completer.matches = []
for filename in os.listdir(dirname):
if filename.startswith(basename):
full_path = os.path.join(dirname, filename)
if os.path.isdir(full_path):
file_completer.matches.append(filename + '/')
else:
file_completer.matches.append(filename)
except OSError:
file_completer.matches = []
# Return the state-th match
try:
return file_completer.matches[state]
except IndexError:
return None
# Set up file completion
gnureadline.set_completer(file_completer)
gnureadline.parse_and_bind("tab: complete")
# Test the completer
current_completer = gnureadline.get_completer()
print(f"Current completer: {current_completer.__name__}")Control how readline splits the input into words for completion.
def set_completer_delims(string: str):
"""
Set the word break characters for completion.
Parameters:
- string: Characters that separate words for completion
"""
def get_completer_delims() -> str:
"""
Get the current word break characters for completion.
Returns:
str: Current word break characters
"""import gnureadline
# Default delimiters include space, tab, newline and many punctuation chars
default_delims = gnureadline.get_completer_delims()
print(f"Default delimiters: '{default_delims}'")
# Set custom delimiters for specific applications
# For shell-like completion, keep most punctuation as delimiters
gnureadline.set_completer_delims(' \t\n`!@#$%^&*()=+[{]}\\|;:\'",<>?')
# For Python identifier completion, use fewer delimiters
gnureadline.set_completer_delims(' \t\n`!@#$%^&*()=+[{]}\\|;:\'",<>?/')
# For path completion, don't break on forward slashes
gnureadline.set_completer_delims(' \t\n`!@#$%^&*()=+[{]}\\|;:\'",<>?')
print(f"Updated delimiters: '{gnureadline.get_completer_delims()}'")Access information about the current completion context.
def get_completion_type() -> int:
"""
Get the type of completion being attempted.
Returns:
int: Completion type constant
"""
def get_begidx() -> int:
"""
Get the beginning index of the completion.
Returns:
int: Start index of text being completed
"""
def get_endidx() -> int:
"""
Get the ending index of the completion.
Returns:
int: End index of text being completed
"""import gnureadline
def context_aware_completer(text: str, state: int) -> str:
"""Completer that uses context information."""
if state == 0:
# Get completion context
begidx = gnureadline.get_begidx()
endidx = gnureadline.get_endidx()
completion_type = gnureadline.get_completion_type()
line_buffer = gnureadline.get_line_buffer()
print(f"\nCompletion context:")
print(f" Text: '{text}'")
print(f" Begin: {begidx}, End: {endidx}")
print(f" Type: {completion_type}")
print(f" Buffer: '{line_buffer}'")
print(f" Before: '{line_buffer[:begidx]}'")
print(f" After: '{line_buffer[endidx:]}'")
# Generate completions based on context
before_text = line_buffer[:begidx].strip()
if not before_text:
# At beginning of line - complete commands
context_aware_completer.matches = [
cmd for cmd in ['help', 'list', 'show', 'quit', 'exit']
if cmd.startswith(text)
]
elif before_text.endswith('--'):
# Complete long options
context_aware_completer.matches = [
opt for opt in ['--help', '--version', '--verbose', '--quiet']
if opt.startswith(text)
]
else:
# Complete filenames
import os
try:
context_aware_completer.matches = [
f for f in os.listdir('.')
if f.startswith(text)
]
except OSError:
context_aware_completer.matches = []
try:
return context_aware_completer.matches[state]
except (IndexError, AttributeError):
return None
gnureadline.set_completer(context_aware_completer)
gnureadline.parse_and_bind("tab: complete")import gnureadline
import shlex
class CommandCompleter:
def __init__(self):
self.commands = {
'ls': self._complete_files,
'cd': self._complete_directories,
'cat': self._complete_files,
'grep': self._complete_grep,
'help': self._complete_help_topics,
}
self.help_topics = ['commands', 'completion', 'history', 'configuration']
def complete(self, text: str, state: int) -> str:
"""Main completion entry point."""
if state == 0:
line_buffer = gnureadline.get_line_buffer()
begidx = gnureadline.get_begidx()
# Parse the command line
try:
tokens = shlex.split(line_buffer[:begidx])
except ValueError:
tokens = line_buffer[:begidx].split()
if not tokens:
# Complete command names
self.matches = [cmd for cmd in self.commands.keys() if cmd.startswith(text)]
else:
command = tokens[0]
if command in self.commands:
# Command-specific completion
self.matches = self.commands[command](text, tokens)
else:
# Default to file completion
self.matches = self._complete_files(text, tokens)
try:
return self.matches[state]
except (IndexError, AttributeError):
return None
def _complete_files(self, text: str, tokens: list) -> list:
"""Complete file names."""
import os
import glob
if text:
pattern = text + '*'
else:
pattern = '*'
matches = glob.glob(pattern)
return [match + ('/' if os.path.isdir(match) else '') for match in matches]
def _complete_directories(self, text: str, tokens: list) -> list:
"""Complete directory names only."""
import os
import glob
pattern = text + '*' if text else '*'
matches = glob.glob(pattern)
return [match + '/' for match in matches if os.path.isdir(match)]
def _complete_grep(self, text: str, tokens: list) -> list:
"""Complete grep command with patterns and files."""
if len(tokens) < 2:
# First argument - could be a pattern or option
if text.startswith('-'):
return [opt for opt in ['-i', '-v', '-r', '-n', '-l'] if opt.startswith(text)]
else:
# Return some common patterns
patterns = ['error', 'warning', 'TODO', 'FIXME', 'import', 'def ', 'class ']
return [p for p in patterns if p.startswith(text)]
else:
# Additional arguments - complete files
return self._complete_files(text, tokens)
def _complete_help_topics(self, text: str, tokens: list) -> list:
"""Complete help topics."""
return [topic for topic in self.help_topics if topic.startswith(text)]
# Set up the command completer
completer = CommandCompleter()
gnureadline.set_completer(completer.complete)
gnureadline.parse_and_bind("tab: complete")
# Configure word delimiters for shell-like behavior
gnureadline.set_completer_delims(' \t\n`!@#$%^&*()=+[{]}\\|;:\'",<>?')import gnureadline
import keyword
import builtins
class PythonCompleter:
def __init__(self):
self.namespace = {
**builtins.__dict__,
**globals(),
**locals()
}
def complete(self, text: str, state: int) -> str:
"""Complete Python identifiers and keywords."""
if state == 0:
self.matches = []
# Get the current line context
line = gnureadline.get_line_buffer()
begidx = gnureadline.get_begidx()
# Simple completion logic
if '.' in text:
# Attribute completion
parts = text.split('.')
obj_name = '.'.join(parts[:-1])
attr_prefix = parts[-1]
try:
obj = eval(obj_name, self.namespace)
attrs = [attr for attr in dir(obj)
if not attr.startswith('_') and attr.startswith(attr_prefix)]
self.matches = [f"{obj_name}.{attr}" for attr in attrs]
except:
self.matches = []
else:
# Name completion
candidates = []
# Python keywords
candidates.extend(keyword.kwlist)
# Built-in functions and types
candidates.extend(dir(builtins))
# Names in current namespace
candidates.extend(self.namespace.keys())
# Filter matches
self.matches = [name for name in candidates
if name.startswith(text) and not name.startswith('_')]
# Sort matches
self.matches.sort()
try:
return self.matches[state]
except (IndexError, AttributeError):
return None
def update_namespace(self, new_namespace: dict):
"""Update the completion namespace."""
self.namespace.update(new_namespace)
# Example usage in a Python REPL
python_completer = PythonCompleter()
gnureadline.set_completer(python_completer.complete)
gnureadline.parse_and_bind("tab: complete")
# For Python code, use different delimiters
gnureadline.set_completer_delims(' \t\n`!@#$%^&*()=+[{]}\\|;:\'",<>?/')
# Update namespace as variables are defined
python_completer.update_namespace({'my_variable': 42, 'my_function': lambda x: x})For advanced completion display options, see the Hook Functions documentation, specifically the set_completion_display_matches_hook function which allows customization of how completion matches are presented to the user.
Install with Tessl CLI
npx tessl i tessl/pypi-gnureadline