AI Skill Report Card

Implementing Readline Features

A-85·Aug 14, 2026·Source: Web
14 / 15
Python
import 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
14 / 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

Python
try: import readline except ImportError: readline = None # Windows fallback; input() still works, just without history/completion

2. History management

Python
histfile = 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.

Python
def 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

Python
readline.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
15 / 20

Example 1: Filename completion Input: User types open ./sr<TAB> in a custom shell. Output:

Python
import 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:

Python
histfile = 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
  • 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=0 call if avoidable).
  • Use readline.set_pre_input_hook() sparingly — it's rarely needed and can cause subtle bugs.
  • Prefer parse_and_bind strings 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.
  • Assuming readline is available everywhere: it's absent on stock Windows Python; always import-guard it.
  • Forgetting state semantics: a completer must return None once 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_file instead of write_history_file when 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.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
14/15
Examples
15/20
Completeness
18/20
Format
14/15
Conciseness
14/15