How Control Codes Work for Cursor Movement and Screen Clearing in Rich

Rich implements terminal control codes as Control renderables that translate high-level intents like cursor positioning and screen clearing into precise ANSI escape sequences.

The Textualize/rich library provides granular control over terminal output through a specialized Control class. Understanding how these control codes function is essential for building interactive CLI applications that manipulate cursor position and screen state programmatically.

Core Architecture

Rich encapsulates ANSI escape sequences through a layered architecture that separates symbolic intents from raw byte generation.

ControlType Enum

In rich/segment.py (lines 35-53), the ControlType IntEnum defines every non-printable control operation. This enumeration includes symbolic names for cursor up/down/forward/backward movements, absolute positioning, screen clearing, and alternate screen buffer management.

CONTROL_CODES_FORMAT Mapping

The rich/control.py file (lines 28-45) defines CONTROL_CODES_FORMAT, a dictionary mapping each ControlType to a callable that constructs the raw escape string. This centralization allows the library to maintain all ANSI sequences in a single location, simplifying extensions and customizations.

The Control Class

Defined in rich/control.py (lines 48-66, 78-107), the Control class serves as the public API. Its constructor accepts one or more (ControlType, ...) tuples, looks up the corresponding formatters in CONTROL_CODES_FORMAT, concatenates the results, and stores the final sequence within a Segment instance.

Segment Integration

The Segment namedtuple in rich/segment.py (lines 70-79) acts as the basic renderable unit. When a Segment contains a control payload, the console writes the stored escape string directly to the terminal without applying style processing. This isolation guarantees that control codes emit as atomic, non-styled fragments distinct from text rendering.

Console.control() Method

The ergonomic entry point resides in rich/console.py (lines 1150-1165). The Console.control() method queues a Control renderable onto the console's output stream, providing the primary interface for users (e.g., console.control(Control.move(2, 0))).

Cursor Movement Control Codes

Rich translates high-level movement intents into standard ANSI cursor positioning codes.

  • Move up n rows: Control.move(y=-n) generates \x1b[{n}A via ControlType.CURSOR_UP
  • Move down n rows: Control.move(y=n) generates \x1b[{n}B via ControlType.CURSOR_DOWN
  • Move right n columns: Control.move(x=n) generates \x1b[{n}C via ControlType.CURSOR_FORWARD
  • Move left n columns: Control.move(x=-n) generates \x1b[{n}D via ControlType.CURSOR_BACKWARD
  • Jump to column x: Control.move_to_column(x) generates \x1b[{x}G via ControlType.CURSOR_MOVE_TO_COLUMN, using one-based coordinates native to the ANSI G command
  • Jump to position (x, y): Control.move_to(x, y) accepts zero-based coordinates and generates \x1b[{y+1};{x+1}H via ControlType.CURSOR_MOVE_TO, automatically converting to one-based ANSI coordinates
  • Return home: Control.home() generates \x1b[H via ControlType.HOME

Screen-Clearing Control Codes

Rich provides declarative methods for manipulating the terminal screen buffer.

  • Clear entire screen: Control.clear() produces \x1b[2J via ControlType.CLEAR, erasing all visible cells while preserving cursor position
  • Enable alternate screen: Control.alt_screen(True) produces \x1b[?1049h via ControlType.ENABLE_ALT_SCREEN, switching to an off-screen buffer used by full-screen applications
  • Disable alternate screen: Control.alt_screen(False) produces \x1b[?1049l via ControlType.DISABLE_ALT_SCREEN, restoring the original buffer

Practical Code Examples

The following examples demonstrate common control code patterns using rich.console and rich.control.

from rich.console import Console
from rich.control import Control

console = Console()

# Move cursor down 3 rows and right 5 columns

console.print("First line")
console.control(Control.move(x=5, y=3))
console.print("Below and indented")

# Clear the screen then print a heading at the top-left corner

console.control(Control.clear())
console.control(Control.home())
console.print("[bold cyan]Welcome to Rich![/]")

# Use absolute positioning to write at column 10, row 5

# Zero-based arguments: (9, 4) maps to column 10, row 5 in ANSI coordinates

console.control(Control.move_to(9, 4))
console.print("Cell content")

# Hide cursor during animation, then restore it

console.control(Control.show_cursor(False))

# ... render progress bar ...

console.control(Control.show_cursor(True))

# Compose multiple movements into a single control sequence

console.control(
    Control.move(y=-2) + Control.move_to_column(19)
)

Summary

  • Rich abstracts ANSI escape sequences through the Control class, which converts high-level intents into raw byte sequences during instantiation
  • The ControlType enum and CONTROL_CODES_FORMAT mapping in rich/segment.py and rich/control.py centralize all terminal operation definitions
  • Cursor movement leverages standard ANSI A/B/C/D/G/H codes, while screen clearing and alternate buffer management use J and ?1049h/l sequences
  • Control codes are stored in Segment instances and emitted verbatim by the console, bypassing style processing to ensure terminal compatibility
  • The Console.control() method provides the primary interface for queuing control operations alongside regular output

Frequently Asked Questions

What are control codes in Rich?

Control codes are non-printable ANSI escape sequences that instruct the terminal to perform actions like moving the cursor or clearing the screen. In Rich, these are encapsulated as Control objects that generate the appropriate escape strings when constructed, ensuring type-safe terminal manipulation without manual byte string construction.

How do I move the cursor to a specific screen position?

Use Control.move_to(x, y) where x and y are zero-based coordinates. This method generates the ANSI sequence \x1b[{y+1};{x+1}H, translating Rich's zero-based indexing to the terminal's one-based coordinate system. For column-only movement, use Control.move_to_column(x) which emits \x1b[{x}G using the one-based column addressing native to ANSI terminals.

Are control codes buffered or emitted immediately?

Control codes are queued through Console.control(), which adds them to the console's output stream buffer. They are emitted during the next render cycle alongside text segments, ensuring proper sequencing with styled output. For immediate emission, ensure the console is not buffering or explicitly flush the output.

Can I combine multiple cursor movements into one operation?

Yes, Control objects support concatenation using the + operator. This combines multiple control sequences into a single Segment, reducing the number of write operations to the terminal. For example, Control.move(y=-2) + Control.move_to_column(19) moves the cursor up two rows then to column 20 in one atomic operation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →