Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd2/argparse_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ def __init__(self, value: object, descriptive_data: Sequence[Any], *args: Any) -
# Make sure all objects are renderable by a Rich table.
renderable_data = [obj if is_renderable(obj) else str(obj) for obj in descriptive_data]

# Convert objects with ANSI styles to Rich Text for correct display width.
# Convert strings containing ANSI style sequences to Rich Text objects for correct display width.
self.descriptive_data = ru.prepare_objects_for_rendering(*renderable_data)

# Save the original value to support CompletionItems as argparse choices.
Expand Down
10 changes: 5 additions & 5 deletions cmd2/cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1191,12 +1191,12 @@ def _completion_supported(self) -> bool:

@property
def visible_prompt(self) -> str:
"""Read-only property to get the visible prompt with any ANSI style escape codes stripped.
"""Read-only property to get the visible prompt with any ANSI style sequences stripped.

Used by transcript testing to make it easier and more reliable when users are doing things like coloring the
prompt using ANSI color codes.
Used by transcript testing to make it easier and more reliable when users are doing things like
coloring the prompt.

:return: prompt stripped of any ANSI escape codes
:return: the stripped prompt
"""
return su.strip_style(self.prompt)

Expand Down Expand Up @@ -4214,7 +4214,7 @@ def _print_documented_command_topics(self, header: str, cmds: list[str], verbose
def render_columns(self, str_list: list[str] | None, display_width: int = 80) -> str:
"""Render a list of single-line strings as a compact set of columns.

This method correctly handles strings containing ANSI escape codes and
This method correctly handles strings containing ANSI style sequences and
full-width characters (like those used in CJK languages). Each column is
only as wide as necessary and columns are separated by two spaces.

Expand Down
28 changes: 15 additions & 13 deletions cmd2/rich_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Provides common utilities to support Rich in cmd2-based applications."""

import re
from collections.abc import Mapping
from enum import Enum
from typing import (
Expand Down Expand Up @@ -28,13 +29,16 @@

from .styles import DEFAULT_CMD2_STYLES

# A compiled regular expression to detect ANSI style sequences.
ANSI_STYLE_SEQUENCE_RE = re.compile(r"\x1b\[[0-9;?]*m")


class AllowStyle(Enum):
"""Values for ``cmd2.rich_utils.ALLOW_STYLE``."""

ALWAYS = 'Always' # Always output ANSI style sequences
NEVER = 'Never' # Remove ANSI style sequences from all output
TERMINAL = 'Terminal' # Remove ANSI style sequences if the output is not going to the terminal
ALWAYS = "Always" # Always output ANSI style sequences
NEVER = "Never" # Remove ANSI style sequences from all output
TERMINAL = "Terminal" # Remove ANSI style sequences if the output is not going to the terminal

def __str__(self) -> str:
"""Return value instead of enum name for printing in cmd2's set command."""
Expand Down Expand Up @@ -234,7 +238,7 @@ def rich_text_to_string(text: Text) -> str:
"""Convert a Rich Text object to a string.

This function's purpose is to render a Rich Text object, including any styles (e.g., color, bold),
to a plain Python string with ANSI escape codes. It differs from `text.plain`, which strips
to a plain Python string with ANSI style sequences. It differs from `text.plain`, which strips
all formatting.

:param text: the text object to convert
Expand All @@ -259,7 +263,7 @@ def rich_text_to_string(text: Text) -> str:


def string_to_rich_text(text: str) -> Text:
r"""Create a Text object from a string which can contain ANSI escape codes.
r"""Create a Rich Text object from a string which can contain ANSI style sequences.

This wraps rich.Text.from_ansi() to handle an issue where it removes the
trailing line break from a string (e.g. "Hello\n" becomes "Hello").
Expand Down Expand Up @@ -323,9 +327,9 @@ def prepare_objects_for_rendering(*objects: Any) -> tuple[Any, ...]:
"""Prepare a tuple of objects for printing by Rich's Console.print().

This function converts any non-Rich object whose string representation contains
ANSI style codes into a rich.Text object. This ensures correct display width
calculation, as Rich can then properly parse and account for the non-printing
ANSI codes. All other objects are left untouched, allowing Rich's native
ANSI style sequences into a Rich Text object. This ensures correct display width
calculation, as Rich can then properly parse and account for these non-printing
codes. All other objects are left untouched, allowing Rich's native
renderers to handle them.

:param objects: objects to prepare
Expand All @@ -342,12 +346,10 @@ def prepare_objects_for_rendering(*objects: Any) -> tuple[Any, ...]:
if isinstance(renderable, ConsoleRenderable):
continue

# Check if the object's string representation contains ANSI styles, and if so,
# replace it with a Rich Text object for correct width calculation.
renderable_as_str = str(renderable)
renderable_as_text = string_to_rich_text(renderable_as_str)

if renderable_as_text.plain != renderable_as_str:
object_list[i] = renderable_as_text
# Check for any ANSI style sequences in the string.
if ANSI_STYLE_SEQUENCE_RE.search(renderable_as_str):
object_list[i] = string_to_rich_text(renderable_as_str)

return tuple(object_list)
9 changes: 4 additions & 5 deletions cmd2/string_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Provides string utility functions.

This module offers a collection of string utility functions built on the Rich library.
These utilities are designed to correctly handle strings with ANSI escape codes and
These utilities are designed to correctly handle strings with ANSI style sequences and
full-width characters (like those used in CJK languages).
"""

Expand Down Expand Up @@ -94,13 +94,12 @@ def stylize(val: str, style: StyleType) -> str:


def strip_style(val: str) -> str:
"""Strip all ANSI styles from a string.
"""Strip all ANSI style sequences from a string.

:param val: string to be stripped
:return: the stripped string
"""
text = ru.string_to_rich_text(val)
return text.plain
return ru.ANSI_STYLE_SEQUENCE_RE.sub("", val)


def str_width(val: str) -> int:
Expand Down Expand Up @@ -163,4 +162,4 @@ def norm_fold(val: str) -> str:
"""
import unicodedata

return unicodedata.normalize('NFC', val).casefold()
return unicodedata.normalize("NFC", val).casefold()
Loading