AI Skill Report Card
Implementing Readline Features
Quick Start14 / 15
Pythonimport readline import atexit import os histfile = os.path.join(os.path.expanduser("~"), ".myapp_history") try: readline.read_history_file(histfile) readline.set_history_length(1000) except FileNotFoundError: pass atexit.register(readline.write_history_file, histfile) def completer(text, state): options = [cmd for cmd in COMMANDS if cmd.startswith(text)] return options[state] if state < len(options) else None readline.set_completer(completer) readline.parse_and_bind("tab: complete") while True: line = input("myapp> ") if line == "quit": break
Note: readline is POSIX-only (Unix/macOS). On Windows, use pyreadline3 as a drop-in substitute, guarded by a try/except import.
Recommendation▾
Add a third example showing hierarchical/subcommand completion using get_line_buffer() to fully demonstrate the technique mentioned in section 3
Workflow14 / 15
Progress:
- Determine platform support (wrap import in try/except for Windows)
- Set up history persistence (load on start, save on exit)
- Implement a completer function if tab-completion is needed
- Bind completer with
parse_and_bind - Configure delimiters if completing paths/dotted names
- Test interactively — history navigation (up/down), tab completion, Ctrl-R search
1. Cross-platform import guard
Pythontry: import readline except ImportError: readline = None # Windows fallback; input() still works, just without history/completion
2. History management
Pythonhistfile = os.path.expanduser("~/.myapp_history") try: readline.read_history_file(histfile) except (FileNotFoundError, PermissionError): pass readline.set_history_length(1000) atexit.register(readline.write_history_file, histfile)
Use readline.append_history_file(nlines, filename) for multi-session apps that shouldn't overwrite others' entries.
3. Writing a completer
A completer is called repeatedly with increasing state (0, 1, 2, ...) until it returns None.
Pythondef make_completer(options): def completer(text, state): matches = [o for o in options if o.startswith(text)] try: return matches[state] except IndexError: return None return completer readline.set_completer(make_completer(["get", "set", "list", "quit"])) readline.parse_and_bind("tab: complete")
For hierarchical completion (subcommands, file paths), inspect readline.get_line_buffer() inside the completer to determine context.
4. Delimiters for completion
Python# Default includes many punctuation chars; narrow it for path-like completion readline.set_completer_delims(' \t\n')
5. Key bindings
Pythonreadline.parse_and_bind("tab: complete") readline.parse_and_bind("set editing-mode vi") # or "emacs" (default) readline.parse_and_bind('"\e[A": history-search-backward')
Recommendation▾
Show a concrete before/after example of the libedit vs GNU readline binding syntax difference on macOS since it's flagged as a common pitfall but not illustrated
Examples15 / 20
Example 1: Filename completion
Input: User types open ./sr<TAB> in a custom shell.
Output:
Pythonimport glob def path_completer(text, state): matches = glob.glob(text + '*') return matches[state] if state < len(matches) else None readline.set_completer_delims(' \t\n/') readline.set_completer(path_completer) readline.parse_and_bind("tab: complete")
Example 2: Persistent history across sessions Input: App needs history preserved between runs, capped at 500 entries. Output:
Pythonhistfile = os.path.expanduser("~/.app_history") try: readline.read_history_file(histfile) except FileNotFoundError: open(histfile, 'wb').close() readline.set_history_length(500) atexit.register(readline.write_history_file, histfile)
Recommendation▾
Include a brief example of pyreadline3 usage for Windows to make the cross-platform guidance fully actionable rather than just mentioned
Best Practices
- Always guard history file I/O with try/except (
FileNotFoundError,PermissionError). - Keep completer functions pure and fast — they're called on every keystroke-tab.
- Cache expensive completion candidate lists (e.g., don't re-scan the filesystem on every
state=0call if avoidable). - Use
readline.set_pre_input_hook()sparingly — it's rarely needed and can cause subtle bugs. - Prefer
parse_and_bindstrings over deprecated individual binding functions for portability with inputrc-style config. - On libedit-based systems (macOS default Python builds historically), check
readline.backend(3.13+) or"libedit" in readline.__doc__to adjust binding syntax, since libedit uses a different config format than GNU readline.
Common Pitfalls
- Assuming readline is available everywhere: it's absent on stock Windows Python; always import-guard it.
- Forgetting
statesemantics: a completer must returnNoneonce exhausted, not raise or loop forever. - Not deduplicating completions: if multiple sources match the same string, users see duplicate tab-cycling entries — dedupe before returning.
- Overwriting shared history files: use
append_history_fileinstead ofwrite_history_filewhen multiple concurrent sessions write to the same file. - Ignoring the libedit/GNU readline binding syntax difference on macOS, leading to bindings silently not working.
- Blocking in the completer: network calls or slow I/O inside a completer freezes the prompt on every tab press.