prettypretty

Prettypretty’s color support is hefty enough to warrant its own subpackage with its own colorful documentation page. Prettypretty also comes with a couple of demo scripts. They are much less hefty but do command their own documentation

prettypretty.ansi

Low-level support for assembling ANSI escape sequences

prettypretty.ansi.is_default(color: ColorSpec) bool[source]

Determine whether the color specification represents the default color.

prettypretty.ansi.DEFAULT_COLOR = ColorSpec(tag='ansi', coordinates=(-1,))

The default color for terminals. ANSI escape sequences actually support two default colors, one for the foreground and one for the background. Prettypretty does not support converting the default color into any other color. But it does represent it as either an ANSI or 8-bit color with -1 as its only component.

class prettypretty.ansi.Layer(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

The display layer.

TEXT

is in front, in the foreground

BACKGROUND

is behind the text

The enumeration constant values capture the fact that the SGR parameters for setting background color are the same as those for setting text color shifted by 10, i.e., from 30–39 and 90–97 to 40–49 and 100–107. Hence the value of TEXT is 0 and that of BACKGROUND is 10.

class prettypretty.ansi.Ansi(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

An enumeration of ANSI escape sequence components.

ESC

is the escape character by itself

CSI, DCS, OSC

start ANSI escape sequences; they are defined with the two-character C0 sequences and not the one-character C1 sequences, since the latter conflict with UTF-8

BEL, ST

are interchangeable and terminate OSC sequences; the latter, again, is defined using the two-character C0 sequence.

All enumeration constants are strings and hence can be directly used when assembling ANSI escape sequences. For example:

print(f"{Ansi.CSI}1m" "Parrot!" f"{Ansi.CSI}m")

The example prints a bold excited parrot to the terminal. It leverages Python’s support for splitting string literals into more than one successive literal for clarity and the fact that missing ANSI parameters default to 0 in the second ANSI escape sequence.

Since ANSI escape sequences usually are more complex than the above, this class also defines color_parameters() for converting colors to ANSI escape sequence parameters and fuse() for fusing all these parts into a complete ANSI escape sequence. Using the methods, we can improve on the above example as follows:

print(
    Ansi.fuse(Ansi.CSI, *Ansi.color_parameters(40), 1, 'm'),
    'Parrot!',
    Ansi.fuse(Ansi.CSI, 'm'),
)

The example prints a green, bold, excited parrot thanks to the 8-bit color 40. Fuse not only joins its arguments, but it also inserts semicolons between parameters. In other words, the above example prints the same as the following statement:

print("\x1b[38;5;40;1m" "Parrot!" "\x1b[m")

The example is, again, leveraging Python’s support for multiple consecutive string literals for improved clarity. Ironically, the Sphinx documentation processor mangles an escaped escape character \x1b, which is now written with an escaped backslash in the docstring source. Apparently, there is value in hiding escape characters behind enumeration constants…

static color_parameters(layer: Layer, color: int, /, use_ansi: bool = ...) tuple[int, ...][source]
static color_parameters(layer: Layer, r: int, g: int, b: int, /) tuple[int, ...]

Convert the 8-bit color, RGB256 coordinates, or default color to parameters for an SGR ANSI escape sequence. The default color takes on the code point before the start of the 8-bit color code points, i.e., -1. The layer argument determines whether the resulting parameters update the foreground or background color. To maximize compatibility, this method uses 30–37, 40–47, 90–97, and 100–107 as parameters for setting the 16 extended ANSI color and the triple 38, 5, color only for the remaining 240 8-bit colors.

static fuse(*fragments: None | int | str) str[source]

Fuse the ANSI escape sequence fragments into a single string. This method treats None as a default parameter and replaces it with an empty string. It also inserts semicolons between parameters, i.e., when two successive arguments are either None or an integer.

class prettypretty.ansi.RawAnsi(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

An enumeration of raw ANSi escape sequence components.

This is a simpler, bytes-valued version of Ansi; see its documentation for more details.

BEL
CSI
DCS
ST
static fuse(*fragments: bytes) bytes[source]

Fuse the bytes fragments together.

prettypretty.darkmode

prettypretty.darkmode.is_dark_theme(theme: None | Theme = None) bool[source]

Determine whether the given terminal theme is a dark theme. If not theme is provided, this function uses the current theme. To detect dark themes, this function converts the default foreground and background colors to the XYZ color space and then compares their Y or luminance components. If the foreground has higher luminance than the background, the theme is a dark theme.

prettypretty.darkmode.is_dark_mode() None | bool[source]

Determine whether the operating system is in dark mode.

Returns:

True for dark mode, False for light mode, and None if the mode could not be determined.

The implementation builds on answers to [this StackOverflow question](https://stackoverflow.com/questions/65294987/detect-os-dark-mode-in-python) and [the darkdetect package](https://github.com/albertosottile/darkdetect). The latter seems both over- and under-engineered. In contrast, this module provides the one interesting bit, whether the system is in dark mode, if available and nothing else.

prettypretty.fidelity

class prettypretty.fidelity.Fidelity(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

A terminal’s color fidelity.

PLAIN
NOCOLOR
ANSI
EIGHT_BIT
RGB256

When it comes to terminal color support, there are four levels, starting with no-color, then the 16 extended ANSI colors, then 8-bit colors, which directly incorporate ANSI colors, and finally “truecolor,” i.e., RGB256. Since no-color still allows for styling text with ANSI escape codes, fidelity also includes the plain level, which prohibits the use of escapes.

Even though there is no established mapping between ANSI and RGB256 colors, the latter nonetheless subsumes ANSI, most certainly when it comes to display hardware. Historically, no-color was the norm for terminals. In fact, even though DEC’s VT100 series of terminals popularized ANSI escape codes, none of the models in that series had color monitors. Nowadays, no-color is important for modelling restricted runtime environments, e.g., as found on continuous integration services. Meanwhile plain is important when producing machine-readable output. Furthermore, both no-color and plain can capture user preferences.

The original use case for fidelity levels is serving as bounds that restrict renderable colors—and renderable styles in case of the plain level. Unless the bound is plain or no-color, color formats and spaces outside the bound need to be converted to one of the formats within the bound. In almost all cases, that means converting a color to the bound’s format (denoted by the bound’s name in lower case).

However, there are three complications.

  1. When the fidelity level is RGB256, the original color may very well be outside of RGB256’s gamut as well. Since gamut mapping is more accurate with floating point coordinates, preparing colors for RGB256 requires first conversion to sRGB, then gamut mapping, and finally conversion to RGB256.

  2. Since RGB6 is a three-component version of (part of) 8-bit color, converting to 8-bit color suffices for fidelity levels of 8-bit and RGB256. In the latter case, 8-bit color may not be the bound’s format but it certainly is within the bound. Converting RGB6 to 8-bit is a reasonable first step for ANSI fidelity, too.

  3. When the fidelity level is ANSI and the original color is 8-bit with a component value between -1 and 15 inclusive, a trivial re-tagging of the color suffices. Technically, -1 stands for the default color. But that color is part of the core ANSI colors, too.

A second use case helps avoid repeated inspections and attempted conversions when colors may be shared and reused through style objects. It is based on the observation that a fidelity level can also serve as a concise summary of past color conversions. Here, NOCOLOR implies a lack of colors and no fidelity implies a lack of inspection.

Fidelity levels form a total order and support Python’s comparison operators.

property tag: str

The corresponding color format or space tag.

classmethod from_tag(tag: Literal['plain', 'nocolor', 'ansi', 'eight_bit', 'rgb256'] | Self) Self[source]

Instantiate the fidelity level from a tag.

classmethod from_color(color: None | ColorSpec) None | Fidelity[source]

Determine the fidelity required for the given color specification. If the color is None, the required fidelity is NOCOLOR. However, if the color has a tag other than ansi, rgb6, eight_bit, or rgb256, this method returns None, indicating that no fidelity can accommodate the given color.

is_renderable(tag: str) bool[source]

Determine whether the given color format or space is renderable at this terminal fidelity without further preparation including conversion.

If the color format or space is not renderable at this fidelity, colors should be converted to the fidelity level, except that RGB6 should be converted to 8-bit color for RGB256 fidelity.

prepare_to_render(color: None | ColorSpec) None | ColorSpec[source]

Prepare the color for rendering at this fidelity level. This method accepts null colors and, if this fidelity level is NOCOLOR, returns null colors.

This method correctly handles the three corner cases for color preparation. First, independent of fidelity level, it converts RGB6 to 8-bit before considering further conversions. Second, instead of directly converting to RGB256, it first converts to sRGB, then gamut-maps the result, and only then converts to RGB256. Finally, when converting to ANSI, it simply re-tags 8-bit colors between -1 and 15.

prettypretty.fidelity.environment_fidelity(is_tty: bool) Fidelity[source]

Determine the current terminal’s fidelity based on the environment variables for this process. This function incorporates logic from the supports-color package. The istty argument indicates whether the terminal’s output is a TTY.

prettypretty.ident

prettypretty.ident.identify_terminal() None | tuple[str, str][source]

Identify the current terminal.

This function implements the fallback strategies for Terminal.request_terminal_identity().

prettypretty.ident.normalize_terminal_name(name: str) str[source]

Normalize the terminal name or bundle ID.

If the given name is an alias for a well-known terminal, this function returns the canonical name. Otherwise, it just returns the given name.

prettypretty.ident.lookup_term_program() None | str[source]

Look up the term program.

prettypretty.ident.lookup_term_program_version() None | str[source]

Look up the term program version.

prettypretty.ident.lookup_macos_bundle_id() None | str[source]

Look up the macOS bundle identifier for the current terminal.

prettypretty.ident.lookup_macos_bundle_version(bundle: str) None | str[source]

Look up the macOS bundle version for the given bundle ID.

This function only runs on macOS.

prettypretty.style

High-level support for terminal styles.

A terminal style covers the full repertoire of text attributes controllable via ANSI escape codes. In addition to a fluent interface for assembling styles in the first place, terminal styles support an algebra of inversion and difference to easily compute the minimal changes necessary for restoring the default appearance or transitioning to another appearance.

Reifying terminal styles this way not only simplifies updating the terminal, but it also encourages the definition of terminal styles in a dedicated module. Such an application-wide style registry greatly simplifies the reuse of styles. They all are defined in the same module after all. Centralization also helps with tuning styles so that they harmonize with each other and the design guidelines. In case where no such guidelines exist, a central module may just help define them.

class prettypretty.style.TextAttribute(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

The superclass of all enumerations representing what should be orthogonal stylistic choices. Each enumeration represents a choice of values, which is binary in the common case and ternary for Weight. It also encodes the default state by aliasing the DEFAULT attribute. Finally, it encodes SGR parameters, which are the values of the enumeration constants.

__invert__() uses the above information to automatically determine which text attributes must be set to restore the default appearance of terminal output again.

property is_default: bool

Determine whether the text attribute is the default value.

class prettypretty.style.Weight(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

The font weight.

REGULAR

is the regular, medium weight

DEFAULT

marks the regular weight as the default

BOLD

is a heavier weight

LIGHT

is a lighter weight

class prettypretty.style.Slant(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

The slant.

UPRIGHT

is text without slant

DEFAULT

marks upright text as the default

ITALIC

is text that is very much slanted

class prettypretty.style.Underline(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Underlined or not.

NOT_UNDERLINED

has no inferior line

DEFAULT

marks the lack of lines as the default

UNDERLINED

has inferior line

class prettypretty.style.Overline(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Overlined or not.

NOT_OVERLINED

has no superior line

DEFAULT

marks the lack of lines as the default

OVERLINED

has superior line

class prettypretty.style.Strikeline(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Stricken or not.

NOT_STRICKEN

has no line through

DEFAULT

marks the lack of lines as the default

STRICKEN

has a line through

class prettypretty.style.Coloring(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

Reversed or not.

NOT_REVERSED

is the inconspicuous, regular order

DEFAULT

marks the regular order as the default

REVERSED

reverses foreground and background colors

class prettypretty.style.Visibility(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

The visibility of text.

NOT_HIDDEN

makes text visible

DEFAULT

marks visible text as the default

HIDDEN

makes text invisible

prettypretty.style.invert_attr(attr: None | TA) None | TA[source]

Invert the given text attribute.

prettypretty.style.invert_color(color: None | ColorSpec) None | ColorSpec[source]

Invert the given color.

class prettypretty.style.Instruction[source]

The superclass of all terminal instructions available to rich text.

class prettypretty.style.Style(*, weight: None | Weight = None, slant: None | Slant = None, underline: None | Underline = None, overline: None | Overline = None, strikeline: None | Strikeline = None, coloring: None | Coloring = None, visibility: None | Visibility = None, foreground: None | ColorSpec = None, background: None | ColorSpec = None)[source]

A terminal style.

weight

for font weight

Type:

None | Weight

slant

for font slant

Type:

None | Slant

underline

for inferior lines

Type:

None | Underline

overline

for superior lines

Type:

None | Overline

strikeline

for lines through

Type:

None | Strikeline

coloring

for color order

Type:

None | Coloring

visibility

for visibility

Type:

None | Visibility

foreground

for foreground color

Type:

None | ColorSpec

background

for background color

Type:

None | ColorSpec

fidelity

is the minimum color fidelity and computed automatically, with None indicating unbounded fidelity

Type:

None | Fidelity

This class captures the state of all text attributes controllable through ANSI escape sequences. It distinguishes between:

Since colors may just have to be downsampled or discarded for rendering even if styles only allow color formats directly supported by ANSI escape sequences, this class accepts all color formats and spaces while having robust facilities for adjusting formats as needed.

The fidelity attribute is automatically computed during initialization and identifies the minimum fidelity level needed for rendering this style. A null fidelity indicates that this style contains arbitrary colors and hence has unbounded fidelity. Meanwhile Fidelity.NOCOLOR indicates that the style does not contain any colors and Fidelity.PLAIN indicates an empty style.

Note

Styles overload Python’s inversion operator. The result of that operation is another style that restores the terminal to its default appearance. Styles also overload Python’s subtraction operator, which returns the style that incrementally transitions from the second to the first style. Finally, the string representation of styles is the corresponding SGR ANSI escape sequence.

Instances of this class are immutable.

property plain: bool

The flag for an empty style specification.

prepare(fidelity: Fidelity | Literal['plain', 'nocolor', 'ansi', 'eight_bit', 'rgb256']) Self[source]

Adjust this style specification for rendering with the given fidelity.

sgr_parameters() list[int][source]

Convert this style to the equivalent SGR parameters.

sgr() str[source]

Convert this style specification into an SGR ANSI escape sequence.

A hyperlink.

text
Type:

str

href
Type:

str

id
Type:

None | str

class prettypretty.style.PlaceCursor(row: None | int = None, column: None | int = None)[source]

Cursor placement by row and column.

row
Type:

None | int

column
Type:

None | int

class prettypretty.style.MoveCursor(move: Literal['up', 'down', 'left', 'right', 'column'], offset: None | int = None)[source]

Cursor movement along one dimension.

move
Type:

Literal[‘up’, ‘down’, ‘left’, ‘right’, ‘column’]

offset
Type:

None | int

prettypretty.style.RichTextElement: TypeAlias = str | prettypretty.style.Instruction

The type of all rich text elements.

class prettypretty.style.RichText(fragments: tuple[str | Instruction, ...])[source]

The terminal version of rich text mixes text, which is to be rendered literally, and style specifications, which are to be rendered as SGR ANSI escape codes. The terminal version of rich text also tracks the current fidelity level and can be easily prepared for a different fidelity.

classmethod of(*fragments: str | Instruction) Self[source]

Create a rich text object from the the given fragments.

prepare(fidelity: Fidelity) Self[source]

Prepare this rich text for rendering at the given fidelity.

class prettypretty.style.rich(incremental: bool = False)[source]

A rich builder.

This class helps create styles and rich text through fluent property accesses and method invocations. In addition to text attributes and colors, it also tracks cursor movements and hyperlinks.

A rich builder can have isolated or incremental styles. Isolated styles are the default for builders created by rich(), whereas builders created by rich.incremental() have incremental styles. The trade-off is that, with isolated styles, you don’t need to worry about undoing styles because this class does that for you. However, you do need to worry about all styles standing on their own and cannot rely on the previous style contributing attributes. It’s just the opposite for incremental styles. You can rely on the attributes of the previous style and incrementally add to them. But you also need to undo your own styles.

For isolated and incremental styles alike, if you want to undo the current style without setting a new style, you can just call undo_style() and let prettypretty figure out the needed antistyle. For isolated styles, that always is the empty style. For incremental styles, that style may just depend on all preceding styles.

classmethod incremental() Self[source]

Create a new builder with incremental styles.

style() Style[source]
style(style: Style) Self

Get or add a style.

If invoked without arguments, this method returns the last updated style. It does not consider any preceding styles, even if this rich builder is in incremental mode.

If invoked with a style argument, this method adds the style to the rich text sequence being built. Whether the style is treated as isolated or incremental depends on this rich builder’s mode.

undo() Self[source]

Undo the currently effective style.

emit(close_style: bool = True) RichText[source]

Emit the built rich text sequence.

This method computes effective styles to fill locations that undo the current style. As long as the argument allows, it also adds a closing style to restore the terminal’s default appearance if there isn’t one.

property regular: Self

Update style with regular weight.

property light: Self

Update style with light weight.

property bold: Self

Update style with bold weight.

property upright: Self

Update style with upright.

property italic: Self

Update style with italic.

property not_underlined: Self

Update style with not underlined.

property underlined: Self

Update style with underlined.

property not_overlined: Self

Update style with not overlined.

property overlined: Self

Update style with overlined.

property not_stricken: Self

Update style with not stricken.

property stricken: Self

Update style with stricken.

property not_reversed: Self

Update style with background and foreground colors reversed.

property reversed: Self

Update style with background and foreground colors reversed.

property not_hidden: Self

Update style with not hidden.

property hidden: Self

Update style with hidden.

fg(color: int, /) Self[source]
fg(c1: int, c2: int, c3: int, /) Self
fg(color: ColorSpec, /) Self
fg(tag: str, c: int, /) Self
fg(tag: str, coordinates: CoordinateSpec, /) Self
fg(tag: str, c1: float, c2: float, c3: float, /) Self

Update style with foreground color.

bg(color: int, /) Self[source]
bg(c1: int, c2: int, c3: int, /) Self
bg(color: ColorSpec, /) Self
bg(tag: str, c: int, /) Self
bg(tag: str, coordinates: CoordinateSpec, /) Self
bg(tag: str, c1: float, c2: float, c3: float, /) Self

Update style with background color.

Add hyperlink.

up(offset: None | int = None) Self[source]

Move cursor up.

down(offset: None | int = None) Self[source]

“Move cursor down.

left(offset: None | int = None) Self[source]

Move cursor left.

right(offset: None | int = None) Self[source]

Move cursor right.

column(offset: None | int = None) Self[source]

Move cursor to the given column.

at(row: None | int = None, column: None | int = None) Self[source]

Move cursor to the given position.

text(text: str = '') Self[source]

Add the given text.

prettypretty.terminal

class prettypretty.terminal.BatchMode(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]

A terminal’s batch mode.

NOT_SUPPORTED

indicates that the terminal does not support batching

ENABLED

indicates that the terminal is currently batching

DISABLED

indicates that the terminal is not currently batching

UNDEFINED

really is permanently enabled, which makes no sense

PERMANENTLY_DISABLED

effectively is the same as the terminal not supporting batch mode

Since three out of five status codes for batching more (NOT_SUPPORTED, PERMANENTLY_DISABLED) or less (UNDEFINED) imply that the terminal doesn’t support batching, this enumeration also defines the more precise computed properties is_supported, is_enabled, and is_disabled.

property is_supported: bool

Determine whether the terminal supports batching.

property is_enabled: bool

Determine whether the terminal is currently batching.

property is_disabled: bool

Determine whether the terminal is currently not batching.

class prettypretty.terminal.TerminalContextManager(terminal: Terminal)[source]

A context manager for terminal state.

This class manages operations that update and restore terminal state. It ensures that all updates are applied on entry and restored on exit, that updates are applied in registration order and restored in the opposite order, and that terminal output is flushed after applying and also after reverting all updates.

An update may be a pair of escape sequences or a function that instantiates a context manager. In case of the former, the first sequence is written to the output upon entry and the second sequence upon exit. In case of the latter, the function and its result’s __enter__() method are invoked on entry and the result’s __exit__() method is invoked on exit. Currently, there is no public interface for registering the latter.

This class is reentrant and reusable, though an instance does nothing on nested invocations.

Terminal has several methods with the same names and signatures as this class. They are the preferred way of creating terminal context manager instances because they are far more convenient. For example, to get started using prettypretty, you might write:

terminal = Terminal()
with TerminalContextManager(terminal).terminal_theme().scoped_style():
    ...

is equivalent to this far nicer alternative

with Terminal().alternate_screen().hidden_cursor() as terminal:
    ...
register(do: str, undo: str) Self[source]

Register an update with this terminal context manager. Both do and undo should be ANSI escape sequences, with undo restoring the terminal to the state from before do.

cbreak_mode() Self[source]

Put the terminal into cbreak mode.

If the terminal is not yet in cbreak mode, the context manager sets cbreak mode upon entry and restores the previous mode upon exit. If the terminal is in cbreak mode already, the context manager does not modify the terminal mode, but it still restores the previous mode upon exit. Mode changes only take effect after all queued output has been written but queued input is discarded.

terminal_theme(theme: None | Theme = None) Self[source]

Use the terminal’s color theme. Unless a theme is provided as argument, the context manager puts the terminal temporarily into cbreak mode and determines the current theme colors upon entry. It then makes that theme the current theme until exit.

window_title(title: str) Self[source]

Update the window title.

alternate_screen() Self[source]

Switch to the terminal’s alternate (unbuffered) screen.

hidden_cursor() Self[source]

Make cursor invisible.

batched_output() Self[source]

Batch terminal output.

While batching, a terminal temporarily delays updating the screen by buffering output. It avoids visual artifacts when rapidly updating the screen.

bracketed_paste() Self[source]

Enable bracketed pasting.

scoped_style() Self[source]

Scope style changes by resetting the style on exit.

class prettypretty.terminal.Terminal(input: None | TextIO = None, output: None | TextIO = None, fidelity: None | Fidelity | Literal['plain', 'nocolor', 'ansi', 'eight_bit', 'rgb256'] = None)[source]

Terminal input/output.

This class manages terminal input and output. It encapsulates the nitty gritty of managing the terminal with ANSI escape codes behind methods with meaningful, human-readable names instead of cryptic mnemonics (what does ED do again and how does it differ from DECSED?). Furthermore, many operations that would leave the terminal in an unusable state upon

This class supports the following features:

Terminal window size

Terminal caches the width and height of the terminal. It only updates the cached values if an application explicitly polls for changes. That way, the application is hopefully prepared to accommodate a terminal size change as well.

See width, height, request_size(), update_size(), and check_same_size().

Cbreak mode

In this mode, the terminal does not support line editing; instead, it immediately forwards bytes. As such cbreak mode facilitates request/response interactions between application and terminal. Getting authoritative information from the terminal sure beats other, more indirect ways, such as environment variables, that help surmise specific conditions.

See is_cbreak_mode(), check_cbreak_mode(), and cbreak_mode().

Writing to terminal output

This class exposes different methods for writing text and for writing control sequences. The latter automatically fuses fragments together, adding semicolons between empty and numeric parameters. It also checks that the terminal supports ANSI escapes. Finally, it provides a convenient hook for intercepting them.

See write(), writeln():, write_control(), and flush(); also fidelity, check_output_tty(), and check_tty().

Reading terminal input and ingesting ANSI escapes

Python’s standard library has extensive support for reading from streams, but only blocking calls including for line-oriented input are convenient to use. This class makes up for that by implementing support for character-oriented, non-blocking input as well as for ANSI escape sequences. The latter require three levels of parsing:

  1. Parse individual character to read an entire control sequence, no less, no more.

  2. Parse message to to separate the integral or textual payload from message header and tail.

  3. Parse text to extract terminal name, colors, etc.

See read() and read_control(); also make_raw_request(), parse_textual_response(), and parse_numeric_response(); also request_terminal_identity(), request_cursor_position(), request_batch_mode(), request_ansi_color(), request_dynamic_color(), and request_theme().

Scoped changes of terminal state

To more easily update, restore, and flush terminal states, Terminal delegates to a terminal context manager. It makes it possible to fluently queue up all restorable updates in a single with statement without worrying about many of the implementation details.

See cbreak_mode(), terminal_theme(), window_title(), alternate_screen(), hidden_cursor(), batched_output(), bracketed_paste(), and scoped_style().

Simple updates of terminal state

Some terminal updates, notably for positioning the cursor and for erasing (parts of) the screen need not or can not be easily undone but still are eminently useful. You can also move the cursor and write links in rich text.

See up(), down(), left(), right(), set_position(), set_column(), erase_screen(), erase_line(), and link().

Setting terminal styles

What’s the point of integrating terminal colors with robust color management? Styling terminal output, of course! This class has methods to set bold, italic, or plain text and to set the fore/background colors. Those methods do not, however, adjust to the runtime context. For that, you want to use prettypretty’s Style objects and rich() text.

See reset_style(), rich_text(), bold(), italic(), fg(), and bg().

property fidelity: Fidelity

This terminal’s color fidelity.

check_output_tty() Self[source]

Check that the output is a TTY.

check_tty() Self[source]

Check that both input and output are TTYs.

property width: int

The cached terminal width.

property height: int

The cached terminal height.

request_size() None | tuple[int, int][source]

Determine the terminal’s size in fixed-width columns and rows. If the underlying platform hook fails for both input and output, typically because both input and output have been redirected, this method returns None.

update_size() Self[source]

Update the width and height cached by this class.

check_same_size() Self[source]

Check that the terminal size has not changed since the last update.

is_cbreak_mode(mode: None | list[Any] = None) bool[source]

” Determine whether cbreak mode is enabled. This method inspects the current terminal mode to see whether characters are not echoed (ECHO is not set), line editing is disabled (ICANON is not set), and reads return upon the first available character (VMIN is 1, VTIME is 0).

Since raw mode makes the same changes and then some, this method detects raw mode as cbreak mode. That’s just fine for its intended purpose, which is checking whether the terminal is prepared for handling ANSI escape sequences that require ANSI escape sequences as responses.

check_cbreak_mode() Self[source]

Check that cbreak mode is enabled. THis method signals an exception if cbreak mode is not enabled.

cbreak_mode() TerminalContextManager[source]

Put the terminal into cbreak mode.

If the terminal is not yet in cbreak mode, the context manager sets cbreak mode upon entry and restores the previous mode upon exit. If the terminal is in cbreak mode already, the context manager does not modify the terminal mode, but it still restores the previous mode upon exit. Mode changes only take effect after all queued output has been written but queued input is discarded.

write(*fragments: str) Self[source]

Write the string fragments to this terminal’s output. This method does not flush the output.

writeln(*fragments: str) Self[source]

Write the string fragments followed by a line terminator to this terminal’s output. This method does not flush the output.

write_paragraph(text: str) Self[source]

Write the paragraph to this terminal’s output.

This method strips all leading and trailing white space from each line of the text. It then treats each span of consecutive, non-empty lines as a paragraph and rewraps it to fit into the terminal width while still being convenient to read. Finally, it writes the resulting text to this terminal’s output. This method does not flush the output.

write_control(*fragments: None | int | str) Self[source]

Write a control sequence to this terminal.

This method fuses the fragments of the inline control sequence (i.e., ANSI escape sequence) into a string and writes that string to this terminal’s output. This method does not flush the terminal’s output.

This terminal’s fidelity must not be Fidelity.PLAIN, which is the case if the output is not a TTY. That restriction applies to all methods that write control sequences, since they always delegate to this method.

flush() Self[source]

Flush this terminal’s output.

read(*, length: int = 3, timeout: float = 0) bytes[source]

Read raw bytes from this terminal.

This method reads up to length bytes from this terminal. If the timeout is 0, this method does not wait for input and immediately returns, possibly with an empty byte string. If the timeout is greater than 0, this method does wait for input, up to as many seconds, using select().

This terminal must be in cbreak mode.

read_control() bytes[source]

Read a complete ANSI escape sequence from this terminal.

This method implements a reasonable but not entirely complete state machine for parsing ANSI escape sequences and keeps calling read() for more bytes as necessary. It uses ESCAPE_TIMEOUT as timeout.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

make_raw_request(*query: None | int | str) None | bytes[source]

Make a request to this terminal. This method writes an ANSI escape sequence to this terminal as a query and then reads an ANSI escape sequence as the response.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

parse_textual_response(response: None | bytes, prefix: str, suffix: str) None | str[source]

Parse the terminal’s textual response to an ANSI escape query.

This method converts the response to a string, checks that is starts with the prefix and ends with the suffix, and then returns the text between prefix and suffix. If the suffix is ST, this method also allows BEL, as both are used interchangeably for terminating DSC/OSC.

If the response is None or malformed, this method returns None.

parse_numeric_response(response: None | bytes, prefix: bytes, suffix: bytes) list[int][source]

Parse the terminal’s numeric response to an ANSI escape query.

This method checks that the given response starts with the prefix and ends with the suffix, splits the bytes between prefix and suffix by semicolons, and parses the resulting byte fragments as integers. Empty byte fragments are parsed as -1. If the suffix ends with ST, this method also allows BEL, as both are used interchangeably for terminating DSC/OSC.

If the response is None or malformed, this method returns an empty list.

request_terminal_identity() None | tuple[str, str][source]

Request the terminal name and version.

Since support for the CSI >q escape sequence for querying a terminal for its name and version is far from universal, this method employs the following strategies:

  1. Use CSI >q escape sequence to query terminal.

  2. Inspect the TERMINAL_PROGRAM and TERMINAL_PROGRAM_VERSION environment variables.

  3. On macOS only, get the bundle identifier from the __CFBundleIdentifier environment variable and then use the mdfind and mdls command line tools to extract the bundle’s version.

If any of these methods is successful, this method normalizes the terminal name based on a list of known aliases. That includes bundle identifiers for Linux and macOS. It also caches the result and returns it for future invocations.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

request_cursor_position() None | tuple[int, int][source]

Request the cursor position in (x, y) order from this terminal.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

request_batch_mode() BatchMode[source]

Determine the terminal’s current batch mode.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

request_active_style() list[int][source]

Request the terminal’s current style settings.

The returned list contains the corresponding SGR parameters. Terminals differ significantly in their support for this query. Since just this query would help determine color support levels, that is rather ironic. For instance, macOS Terminal.app does not handle the query, whereas Visual Studio Code’s builtin terminal and iTerm 2 both respond with well-formed styles, which are completely wrong in case of Visual Studio Code.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

request_color_support() None | Fidelity[source]

Request the terminal’s color support.

This method uses style queries to test for 24-bit and 8-bit color.

The terminal must have TTYs for input and output. It also must be in cbreak mode. This method resets the current style.

request_ansi_color(color: int) ColorSpec[source]

Determine the color for the given extended ANSI color. This method queries the terminal, parses the result, which by convention uses four hexadecimal digits per component, and normalizes it to sRGB.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

request_dynamic_color(code: int) ColorSpec[source]

Determine the color for the user interface element identified by code:

  • 10 is the foreground or text color

  • 11 is the background color

This method queries the terminal, parses the result, which by convention uses four hexadecimal digits per component, and normalizes it to sRGB.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

request_theme() Theme

Request all theme colors from the terminal.

Currently, there are three different implementations of this method:

  1. The first version completely processes one color at a time. It writes the query, then reads the response, and then parses the response.

  2. The second version operates in two phases: It first writes all 18 queries and then reads and parses all 18 responses.

  3. The third version operates in three phases: It first writes all 18 queries, then reads all 18 responses, and finally parses all 18 responses.

In my measurements, the second and third version take only half the time of the first version and, usually, the third version is a bit faster still. But I have seen a couple of spurious failures for terminal queries and hence expect the need for some retry logic, which would complicate things. So for now, I am not ready to commit to either of those three versions. If you feel like experimenting, you can run the microbenchmarks by running this module:

python -m prettypretty.terminal

You can also switch between versions by updating the assignment above this documentation comment in the source code. In either case, please report back about your experiences by filing an issue.

The terminal must have TTYs for input and output. It also must be in cbreak mode.

terminal_theme(theme: None | Theme = None) TerminalContextManager[source]

Use a different color theme. Unless a theme argument is provided, the implementation queries the terminal for its current theme, while temporarily putting the terminal in cbreak mode.

window_title(title: str) TerminalContextManager[source]

Use a different window title.

alternate_screen() TerminalContextManager[source]

Switch to the terminal’s alternate (unbuffered) screen.

hidden_cursor() TerminalContextManager[source]

Make cursor invisible.

batched_output() TerminalContextManager[source]

Batch terminal output.

bracketed_paste() TerminalContextManager[source]

Enable bracketed pasting.

scoped_style() TerminalContextManager[source]

Scope style changes by resetting the style on exit. The style() context helps protect against unwanted style leakage upon unexpected exceptions or signals.

up(rows: None | int = None) Self[source]

Move cursor up.

down(rows: None | int = None) Self[source]

Move cursor down.

left(columns: None | int = None) Self[source]

Move cursor left.

right(columns: None | int = None) Self[source]

Move cursor right.

at(row: None | int = None, column: None | int = None) Self[source]

Move the cursor to the given row and column.

column(column: None | int = None) Self[source]

Move the cursor to the given column

erase_screen() Self[source]

Erase the entire screen.

erase_line() Self[source]

Erase the entire current line.

Mark a hyperlink.

Underlined text should only be used for hyperlinks, in terminal emulators just as much as in documents and on web pages. That’s just why this class does not have a separate method for styling text as underlined. If that’s too stringent for your use case, please do open an issue.

reset_style() Self[source]

Reset all styles.

rich_text(fragments: Sequence[RichTextElement]) Self[source]
rich_text(*fragments: RichTextElement) Self

Write rich text to terminal output

bold() Self[source]

Set bold style.

italic() Self[source]

Set italic style.

fg(color: ColorSpec, /) Self[source]
fg(color: int, /) Self
fg(c1: int, c2: int, c3: int, /) Self
fg(tag: str, c: int, /) Self
fg(tag: str, coordinates: CoordinateSpec, /) Self
fg(tag: str, c1: float, c2: float, c3: float, /) Self

Set the foreground color.

bg(color: ColorSpec, /) Self[source]
bg(color: int, /) Self
bg(c1: int, c2: int, c3: int, /) Self
bg(tag: str, c: int, /) Self
bg(tag: str, coordinates: CoordinateSpec, /) Self
bg(tag: str, c1: float, c2: float, c3: float, /) Self

Set the background color.