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
nrows:Control.move(y=-n)generates\x1b[{n}AviaControlType.CURSOR_UP - Move down
nrows:Control.move(y=n)generates\x1b[{n}BviaControlType.CURSOR_DOWN - Move right
ncolumns:Control.move(x=n)generates\x1b[{n}CviaControlType.CURSOR_FORWARD - Move left
ncolumns:Control.move(x=-n)generates\x1b[{n}DviaControlType.CURSOR_BACKWARD - Jump to column
x:Control.move_to_column(x)generates\x1b[{x}GviaControlType.CURSOR_MOVE_TO_COLUMN, using one-based coordinates native to the ANSIGcommand - Jump to position
(x, y):Control.move_to(x, y)accepts zero-based coordinates and generates\x1b[{y+1};{x+1}HviaControlType.CURSOR_MOVE_TO, automatically converting to one-based ANSI coordinates - Return home:
Control.home()generates\x1b[HviaControlType.HOME
Screen-Clearing Control Codes
Rich provides declarative methods for manipulating the terminal screen buffer.
- Clear entire screen:
Control.clear()produces\x1b[2JviaControlType.CLEAR, erasing all visible cells while preserving cursor position - Enable alternate screen:
Control.alt_screen(True)produces\x1b[?1049hviaControlType.ENABLE_ALT_SCREEN, switching to an off-screen buffer used by full-screen applications - Disable alternate screen:
Control.alt_screen(False)produces\x1b[?1049lviaControlType.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
Controlclass, which converts high-level intents into raw byte sequences during instantiation - The
ControlTypeenum andCONTROL_CODES_FORMATmapping inrich/segment.pyandrich/control.pycentralize 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
Segmentinstances 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →