Claude Code for curses.textpad: Python Terminal Text Input Widget — Claude Skills 360 Blog
Blog / AI / Claude Code for curses.textpad: Python Terminal Text Input Widget
AI

Claude Code for curses.textpad: Python Terminal Text Input Widget

Published: February 5, 2029
Read time: 5 min read
By: Claude Skills 360

Python’s curses.textpad module provides a simple interactive text-editing widget for curses TUI applications. from curses.textpad import Textbox, rectangle. Textbox(win, insert_mode=False) — wraps a curses window as an editable text field; insert_mode=True enables character-insert mode (otherwise overwrite). box.edit(validator=None) — enter the interactive editing loop; the optional validator callable receives each keystroke and returns the (possibly modified) key to process; the loop ends when the validator returns a non-truthy value or the user presses Ctrl+G (default terminator). box.gather() — return the buffer contents as a str, stripping trailing spaces per line if box.stripspaces is True (default). box.do_command(ch) — dispatch a single character command; useful for programmatic input or testing. rectangle(win, uly, ulx, lry, lrx) — draw a rectangular border using ACS_* box-drawing characters. Default keybindings: Ctrl+A = start-of-line; Ctrl+B = left; Ctrl+D = delete char; Ctrl+E = end-of-line; Ctrl+F = right; Ctrl+G = terminate; Ctrl+H = backspace; Ctrl+J = newline/terminate; Ctrl+K = kill-to-EOL; Ctrl+L = refresh; Ctrl+N = down; Ctrl+O = insert blank line; Ctrl+P = up. Claude Code generates terminal form fields, inline editors, search bars, confirmation dialogs, multi-line text editors, and interactive TUI input pipelines.

CLAUDE.md for curses.textpad

## curses.textpad Stack
- Stdlib: from curses.textpad import Textbox, rectangle
- Create: box = Textbox(win, insert_mode=False)
- Edit:   text = box.edit(validator)   # blocks; Ctrl+G to end
- Gather: text = box.gather()          # returns buffer as str
- Frame:  rectangle(stdscr, y1, x1, y2, x2)   # border around field
- Keys:   Ctrl+G = done; Ctrl+H/^? = backspace; Ctrl+A/E = home/end
-         Ctrl+B/F = left/right; Ctrl+N/P = down/up
-         Ctrl+D = del char; Ctrl+K = kill to EOL
- validator(ch) → int: return 0/False to stop, return ch to process normally

curses.textpad Terminal Input Pipeline

# app/cursestextpadutil.py — single-line, multi-line, validator, search, form
from __future__ import annotations

import curses
import curses.ascii
from curses.textpad import Textbox, rectangle
from dataclasses import dataclass, field
from typing import Any, Callable


# ─────────────────────────────────────────────────────────────────────────────
# 1. Validator factories
# ─────────────────────────────────────────────────────────────────────────────

def make_enter_validator() -> Callable[[int], int]:
    """
    Return a validator that accepts Enter (Ctrl+J or Ctrl+M) to finish editing.
    By default Textbox uses Ctrl+G; this adds Enter as a terminator.

    Example:
        box = Textbox(win)
        text = box.edit(make_enter_validator())
    """
    def validator(ch: int) -> int:
        if ch in (curses.ascii.ctrl(ord('J')),   # Ctrl+J = \n
                  curses.ascii.ctrl(ord('M'))):   # Ctrl+M = \r
            return curses.ascii.BEL   # BEL = Ctrl+G terminates
        return ch
    return validator


def make_maxlen_validator(maxlen: int) -> Callable[[int], int]:
    """
    Return a validator that prevents input beyond maxlen printable characters.

    Example:
        box = Textbox(win)
        text = box.edit(make_maxlen_validator(20))
    """
    _count: list[int] = [0]

    def validator(ch: int) -> int:
        if curses.ascii.isprint(ch):
            if _count[0] >= maxlen:
                return 0   # ignore — suppress the character
            _count[0] += 1
        elif ch in (curses.KEY_BACKSPACE if hasattr(curses, 'KEY_BACKSPACE') else 263,
                    curses.ascii.ctrl(ord('H'))):  # backspace
            _count[0] = max(0, _count[0] - 1)
        return ch
    return validator


def make_numeric_validator() -> Callable[[int], int]:
    """
    Return a validator that only allows digit characters and backspace/delete.

    Example:
        box = Textbox(win)
        amount_str = box.edit(make_numeric_validator())
    """
    def validator(ch: int) -> int:
        if curses.ascii.isdigit(ch):
            return ch
        if ch in (curses.ascii.ctrl(ord('H')),   # Ctrl+H = backspace
                  curses.ascii.ctrl(ord('G')),   # terminator
                  curses.ascii.ctrl(ord('J')),   # newline
                  curses.ascii.ctrl(ord('A')),   # home
                  curses.ascii.ctrl(ord('E')),   # end
                  curses.ascii.ctrl(ord('B')),   # left
                  curses.ascii.ctrl(ord('F'))):  # right
            return ch
        return 0   # swallow non-digit printable chars
    return validator


# ─────────────────────────────────────────────────────────────────────────────
# 2. Framed input field helpers
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class InputField:
    label:    str
    y:        int        # top-left row of the frame
    x:        int        # top-left column of the frame
    width:    int        # inner editing width
    height:   int = 1   # inner editing height (1 = single line)


def draw_field(stdscr: Any, field: InputField) -> Textbox:
    """
    Draw a labelled, framed input field on stdscr and return a Textbox.
    Framing adds 2 to width and height (border).

    Example:
        box = draw_field(stdscr, InputField("Name:", 2, 2, 30))
        text = box.edit(make_enter_validator()).strip()
    """
    # Label
    stdscr.addstr(field.y, field.x, field.label)
    label_len = len(field.label) + 1
    # Rectangle border
    uly = field.y
    ulx = field.x + label_len
    lry = field.y + field.height + 1
    lrx = field.x + label_len + field.width + 1
    rectangle(stdscr, uly, ulx, lry, lrx)
    stdscr.refresh()
    # Inner editing window
    edit_win = curses.newwin(field.height, field.width,
                              field.y + 1, field.x + label_len + 1)
    return Textbox(edit_win, insert_mode=True)


# ─────────────────────────────────────────────────────────────────────────────
# 3. Simple prompted input (wraps curses.wrapper)
# ─────────────────────────────────────────────────────────────────────────────

def prompt_input(prompt: str,
                 width: int = 40,
                 validator: "Callable[[int], int] | None" = None,
                 y: int = 2,
                 x: int = 2) -> str:
    """
    Display a prompted input field in a curses session and return the entered text.
    Blocks until the user finishes (Ctrl+G or Enter via enter_validator).

    Example:
        name = prompt_input("Enter your name: ", width=30)
        print(f"Hello, {name}!")
    """
    result: list[str] = [""]

    def _inner(stdscr: Any) -> None:
        curses.curs_set(1)
        stdscr.clear()
        field = InputField(label=prompt, y=y, x=x, width=width)
        box = draw_field(stdscr, field)
        val = validator or make_enter_validator()
        raw = box.edit(val)
        result[0] = raw.strip()

    curses.wrapper(_inner)
    return result[0]


# ─────────────────────────────────────────────────────────────────────────────
# 4. Multi-field form
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class FormField:
    name:     str
    label:    str
    width:    int = 30
    numeric:  bool = False
    maxlen:   int = 0    # 0 = unlimited


def run_form(fields: list[FormField]) -> dict[str, str]:
    """
    Display a multi-field curses form and return a dict of field_name → entered_text.
    Each field is stacked vertically. Press Enter to advance between fields.

    Example:
        data = run_form([
            FormField("name", "Full name:", width=30),
            FormField("age",  "Age:      ", width=5, numeric=True, maxlen=3),
            FormField("city", "City:    ", width=20),
        ])
        print(data)
    """
    results: dict[str, str] = {}

    def _inner(stdscr: Any) -> None:
        curses.curs_set(1)
        stdscr.clear()
        y = 1
        for f in fields:
            field_obj = InputField(label=f.label, y=y, x=2, width=f.width)
            box = draw_field(stdscr, field_obj)
            if f.numeric:
                val_fn: Callable[[int], int] = make_numeric_validator()
            elif f.maxlen > 0:
                val_fn = make_maxlen_validator(f.maxlen)
            else:
                val_fn = make_enter_validator()
            raw = box.edit(val_fn)
            results[f.name] = raw.strip()
            y += 3   # spacing between fields

    curses.wrapper(_inner)
    return results


# ─────────────────────────────────────────────────────────────────────────────
# 5. Programmatic Textbox testing helper
# ─────────────────────────────────────────────────────────────────────────────

def simulate_input(text: str,
                   win_height: int = 3,
                   win_width: int = 40) -> str:
    """
    Simulate typing text into a Textbox and return the gathered result.
    Uses do_command() to inject keystrokes without a live terminal.
    Useful for unit-testing Textbox behaviour.

    Example:
        result = simulate_input("hello world")
        assert result.strip() == "hello world"
    """
    result: list[str] = [""]

    def _inner(stdscr: Any) -> None:
        win = curses.newwin(win_height, win_width, 0, 0)
        box = Textbox(win, insert_mode=True)
        for ch in text:
            box.do_command(ord(ch))
        # Terminate
        box.do_command(curses.ascii.BEL)
        result[0] = box.gather()

    curses.wrapper(_inner)
    return result[0]


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== curses.textpad demo ===")
    print()
    print("This module requires a live terminal.  Run the examples below")
    print("in an interactive Python session or a real terminal:")
    print()
    print("  # 1. Single prompt (Enter to confirm):")
    print("  from cursestextpadutil import prompt_input")
    print("  name = prompt_input('Your name: ', width=20)")
    print("  print(f'Hello, {name}!')")
    print()
    print("  # 2. Multi-field form:")
    print("  from cursestextpadutil import run_form, FormField")
    print("  data = run_form([")
    print("      FormField('name', 'Full name:', width=30),")
    print("      FormField('age',  'Age:      ', width=5, numeric=True, maxlen=3),")
    print("  ])")
    print("  print(data)")
    print()
    print("  # 3. Simulate input (no terminal needed):")
    print("  from cursestextpadutil import simulate_input")
    print("  result = simulate_input('hello world')")
    print("  print(repr(result.strip()))")
    print()

    # ── Demonstrate validators without a terminal ─────────────────────────
    print("--- validator demo (no terminal) ---")

    # max-length validator: characters beyond limit return 0
    maxlen_val = make_maxlen_validator(5)
    print("  maxlen_val  (5 chars):")
    for ch in "hello world":
        out = maxlen_val(ord(ch))
        print(f"    input={ch!r:3s}  output={out:3d}  {'accepted' if out else 'rejected'}")

    # numeric validator: non-digits rejected
    numeric_val = make_numeric_validator()
    print("\n  numeric_val:")
    for ch in "12a3b4":
        out = numeric_val(ord(ch))
        print(f"    input={ch!r:3s}  output={out:3d}  {'accepted' if out else 'rejected'}")

    print("\n=== done ===")

For the urwid (PyPI) alternative — urwid.Edit(caption="Name: ") and urwid.IntEdit() provide richer text-input widgets with Unicode support, focus handling, and a signal system in the urwid TUI framework — use urwid for full-featured TUI applications with multiple widgets, focus traversal, scrolling lists, and pop-up dialogs; use curses.textpad.Textbox for lightweight, zero-dependency text input fields embedded in curses applications or scripts. For the prompt_toolkit (PyPI) alternative — prompt_toolkit.prompt("Enter name: ") provides readline-like input with history, auto-completion, syntax highlighting, and multi-line editing in both synchronous and asyncio contexts — use prompt_toolkit for REPL-style input at the command line; use curses.textpad for form-style input embedded inside a full-screen curses TUI. The Claude Skills 360 bundle includes curses.textpad skill sets covering make_enter_validator()/make_maxlen_validator()/make_numeric_validator() validator factories, InputField/draw_field() framed field builder, prompt_input() single-prompt helper, FormField/run_form() multi-field form runner, and simulate_input() programmatic testing helper. Start with the free tier to try terminal input patterns and curses.textpad pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free