Python’s readline module wraps the GNU Readline library to add line editing, persistent history, and tab completion to interactive Python programs. import readline. parse_and_bind: readline.parse_and_bind("tab: complete") — bind Tab to completion; "set editing-mode vi" for vi keys. set_completer: readline.set_completer(fn) — fn(text, state) returns the state-th completion or None. get_line_buffer: readline.get_line_buffer() — current typed line during completion. get_begidx/get_endidx: cursor word boundaries for completion context. History: readline.read_history_file("~/.myapp_history") — load; readline.write_history_file("~/.myapp_history") — save. set_history_length: readline.set_history_length(1000) — limit entries (-1 = unlimited). add_history: readline.add_history("prev command") — inject into history. get_history_length / get_current_history_length: history size. get_history_item: readline.get_history_item(1) — 1-indexed. clear_history: readline.clear_history(). set_completion_display_matches_hook: custom display for completions. set_startup_hook / set_pre_input_hook: callbacks before prompt. readline is available on macOS (using libedit, quirks in parse_and_bind syntax) and Linux; not available on Windows. Claude Code generates persistent REPL histories, keyword completers, path completers, and custom readline-enabled shells.
CLAUDE.md for readline
## readline Stack
- Stdlib: import readline
- Bind: readline.parse_and_bind("tab: complete")
- Complete: readline.set_completer(lambda text, state: matches[state])
- History: readline.read_history_file(hist); readline.write_history_file(hist)
- Limit: readline.set_history_length(1000)
- Buffer: readline.get_line_buffer() # current typed text in completion fn
readline Interactive Shell Pipeline
# app/readlineutil.py — history, completion, path, keyword, shell REPL
from __future__ import annotations
import os
import readline
import atexit
from pathlib import Path
from typing import Callable
# ─────────────────────────────────────────────────────────────────────────────
# 1. History management
# ─────────────────────────────────────────────────────────────────────────────
def setup_history(
history_file: str | Path,
max_entries: int = 1000,
) -> None:
"""
Load history from file, register atexit save, and limit history length.
Call once at application startup.
Example:
setup_history("~/.myapp_history")
while True:
line = input(">>> ")
"""
hist_path = Path(history_file).expanduser()
if hist_path.exists():
try:
readline.read_history_file(str(hist_path))
except OSError:
pass
readline.set_history_length(max_entries)
atexit.register(readline.write_history_file, str(hist_path))
def save_history(history_file: str | Path) -> None:
"""Manually save history to a file."""
readline.write_history_file(str(Path(history_file).expanduser()))
def get_history() -> list[str]:
"""Return all history entries as a list of strings."""
n = readline.get_current_history_length()
return [readline.get_history_item(i + 1) for i in range(n)]
def clear_history() -> None:
"""Clear all history entries."""
readline.clear_history()
def inject_history(entries: list[str]) -> None:
"""
Inject a list of strings into readline history.
Example:
inject_history(["help", "list", "quit"])
"""
for entry in entries:
readline.add_history(entry)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Completion helpers
# ─────────────────────────────────────────────────────────────────────────────
def enable_tab_completion() -> None:
"""Enable Tab key for completion (must call set_completer first)."""
readline.parse_and_bind("tab: complete")
def set_completer(fn: Callable[[str, int], str | None]) -> None:
"""
Register a completion function fn(text, state) → str | None.
Tab calls fn repeatedly with state=0,1,2,... until None is returned.
Example:
set_completer(keyword_completer(["help", "list", "quit"]))
enable_tab_completion()
"""
readline.set_completer(fn)
def get_line_buffer() -> str:
"""Return the current readline input buffer (usable inside completers)."""
return readline.get_line_buffer()
# ─────────────────────────────────────────────────────────────────────────────
# 3. Completer factories
# ─────────────────────────────────────────────────────────────────────────────
def keyword_completer(keywords: list[str]) -> Callable[[str, int], str | None]:
"""
Create a completer that matches against a fixed keyword list.
Example:
set_completer(keyword_completer(["help", "list", "add", "remove", "quit"]))
enable_tab_completion()
"""
def _complete(text: str, state: int) -> str | None:
matches = [k for k in keywords if k.startswith(text)]
return matches[state] if state < len(matches) else None
return _complete
def path_completer(only_dirs: bool = False) -> Callable[[str, int], str | None]:
"""
Create a completer that expands filesystem paths.
Example:
set_completer(path_completer())
enable_tab_completion()
# Tab-complete file paths at the prompt
"""
_matches: list[str] = []
def _complete(text: str, state: int) -> str | None:
nonlocal _matches
if state == 0:
expanded = os.path.expanduser(text)
if os.path.isdir(expanded):
prefix = expanded if expanded.endswith(os.sep) else expanded + os.sep
raw_text_prefix = text if text.endswith(os.sep) else text + os.sep
else:
prefix = os.path.dirname(expanded) or "."
raw_text_prefix = os.path.dirname(text) + os.sep if os.path.dirname(text) else ""
try:
entries = os.listdir(prefix)
except OSError:
entries = []
base = os.path.basename(expanded)
results: list[str] = []
for entry in entries:
if entry.startswith(base):
full = os.path.join(prefix, entry)
suffix = os.sep if os.path.isdir(full) else ""
if not only_dirs or os.path.isdir(full):
results.append(raw_text_prefix + entry + suffix)
_matches = sorted(results)
return _matches[state] if state < len(_matches) else None
return _complete
def dict_completer(
data: dict,
separator: str = ".",
) -> Callable[[str, int], str | None]:
"""
Create a completer that navigates a nested dict by separator-delimited keys.
Useful for config key completion.
Example:
cfg = {"server": {"host": "localhost", "port": 8080}, "debug": True}
set_completer(dict_completer(cfg))
enable_tab_completion()
# "server." → completes to "server.host", "server.port"
"""
def _all_keys(obj: dict, prefix: str) -> list[str]:
keys: list[str] = []
for k, v in obj.items():
full = prefix + k if prefix else k
keys.append(full)
if isinstance(v, dict):
keys.extend(_all_keys(v, full + separator))
return keys
all_keys = _all_keys(data, "")
def _complete(text: str, state: int) -> str | None:
matches = [k for k in all_keys if k.startswith(text)]
return matches[state] if state < len(matches) else None
return _complete
# ─────────────────────────────────────────────────────────────────────────────
# 4. Composing completers
# ─────────────────────────────────────────────────────────────────────────────
def dispatch_completer(
command_completers: dict[str, Callable[[str, int], str | None]],
commands: list[str],
) -> Callable[[str, int], str | None]:
"""
A top-level completer that:
- completes the first word from commands
- dispatches to a per-command completer for arguments
Example:
cmpl = dispatch_completer(
{"open": path_completer(), "set": dict_completer(config)},
commands=["help", "open", "set", "quit"],
)
set_completer(cmpl); enable_tab_completion()
"""
_cache: list[str] = []
def _complete(text: str, state: int) -> str | None:
nonlocal _cache
if state == 0:
buf = readline.get_line_buffer()
parts = buf.lstrip().split()
# Completing the first word
if len(parts) == 0 or (len(parts) == 1 and not buf.endswith(" ")):
_cache = [c for c in commands if c.startswith(text)]
else:
cmd = parts[0]
sub = command_completers.get(cmd)
if sub is not None:
_cache = []
i = 0
while True:
m = sub(text, i)
if m is None:
break
_cache.append(m)
i += 1
else:
_cache = []
return _cache[state] if state < len(_cache) else None
return _complete
# ─────────────────────────────────────────────────────────────────────────────
# 5. Simple REPL harness
# ─────────────────────────────────────────────────────────────────────────────
def simple_repl(
prompt: str = ">>> ",
handler: Callable[[str], str | None] | None = None,
history_file: str | Path | None = None,
completer: Callable | None = None,
banner: str = "",
) -> None:
"""
Run a simple readline-enabled REPL.
handler(line) → response string or None; return "EXIT" to quit.
Example:
def handle(line):
if line.lower() == "quit": return "EXIT"
return f"You typed: {line!r}"
simple_repl(
prompt="myapp> ",
handler=handle,
history_file="~/.myapp_history",
)
"""
if history_file:
setup_history(history_file)
if completer:
set_completer(completer)
enable_tab_completion()
if banner:
print(banner)
while True:
try:
line = input(prompt)
except (EOFError, KeyboardInterrupt):
print()
break
line = line.strip()
if not line:
continue
if handler:
result = handler(line)
if result == "EXIT":
break
if result is not None:
print(result)
# ─────────────────────────────────────────────────────────────────────────────
# Demo (non-interactive — demonstrates the API without blocking)
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== readline demo ===")
# ── inject and inspect history ────────────────────────────────────────────
print("\n--- history inject / get ---")
clear_history()
inject_history(["help", "list all", "add item", "quit"])
hist = get_history()
print(f" history ({len(hist)} entries): {hist}")
# ── keyword completer ─────────────────────────────────────────────────────
print("\n--- keyword_completer ---")
cmpl = keyword_completer(["add", "remove", "list", "help", "quit"])
matches = [cmpl("l", i) for i in range(5) if cmpl("l", i) is not None]
print(f" completions for 'l': {matches}")
# ── dict completer ────────────────────────────────────────────────────────
print("\n--- dict_completer ---")
cfg = {
"server": {"host": "localhost", "port": 8080},
"database": {"url": "sqlite:///app.db"},
"debug": True,
}
dc = dict_completer(cfg)
for prefix in ("s", "server.", "d"):
matches = [dc(prefix, i) for i in range(10) if dc(prefix, i) is not None]
print(f" '{prefix}' → {matches}")
# ── path completer smoke test ──────────────────────────────────────────────
print("\n--- path_completer ---")
pc = path_completer()
# complete "/" → should find /tmp etc.
m = pc("/tm", 0)
print(f" path_completer('/tm', 0) → {m!r}")
# ── history file round-trip ────────────────────────────────────────────────
print("\n--- history file ---")
with tempfile.TemporaryDirectory() as tmpdir:
hfile = Path(tmpdir) / "hist.txt"
clear_history()
inject_history(["cmd1", "cmd2", "cmd3"])
save_history(hfile)
print(f" saved to {hfile.name} ({hfile.stat().st_size} bytes)")
clear_history()
setup_history(hfile)
print(f" reloaded: {get_history()}")
print("\n=== done ===")
For the prompt_toolkit alternative — prompt_toolkit (PyPI) provides a full-featured readline replacement with multi-line editing, syntax highlighting, fuzzy completion, async support, and cross-platform Windows support (which readline lacks) — use prompt_toolkit for polished interactive applications, modern TUI shells, or when you need Windows compatibility; use readline when you want a lightweight stdlib-only solution for Unix command-line tools where GNU Readline is already installed, or when you’re adding basic history and Tab completion to an existing input()-based CLI without adding dependencies. For the cmd alternative — cmd.Cmd (stdlib) is a higher-level framework that wraps readline internally and provides do_* method dispatch, built-in help system, and complete_* method hooks for per-command Tab completion — use cmd.Cmd when building a structured multi-command shell with a do_help/do_list method pattern; use readline directly when you need custom completion logic, history management, or integration with a non-cmd.Cmd-based input loop. The Claude Skills 360 bundle includes readline skill sets covering setup_history()/save_history()/get_history()/clear_history()/inject_history() history management, enable_tab_completion()/set_completer() setup, keyword_completer()/path_completer()/dict_completer() completion factories, dispatch_completer() multi-command router, and simple_repl() complete REPL harness. Start with the free tier to try interactive shell patterns and readline pipeline code generation.