summaryrefslogtreecommitdiff
path: root/cmd2/ansi.py
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2021-10-11 15:20:46 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2021-10-18 13:06:20 -0400
commitf57b08672af97f9d973148b6c30d74fe4e712d14 (patch)
treeb314ff432f7d6a6657f0e8a578c6424487319204 /cmd2/ansi.py
parente35ab9c169eccc7af3e4ec604e2ddd2e668bdc2c (diff)
downloadcmd2-git-f57b08672af97f9d973148b6c30d74fe4e712d14.tar.gz
Added support for 8-bit/256-colors with the cmd2.EightBitFg and cmd2.EightBitBg classes.
Added support for 24-bit/RGB colors with the cmd2.RgbFg and cmd2.RgbBg classes. Removed dependency on colorama. Deprecated cmd2.fg. Use cmd2.Fg instead. Deprecated cmd2.bg. Use cmd2.Bg instead. Changed type of ansi.allow_style from a string to an ansi.AllowStyle Enum class. Fixed bug where using choices on a Settable didn't verify that a valid choice had been entered.
Diffstat (limited to 'cmd2/ansi.py')
-rw-r--r--cmd2/ansi.py1154
1 files changed, 950 insertions, 204 deletions
diff --git a/cmd2/ansi.py b/cmd2/ansi.py
index 32b50697..91188a9c 100644
--- a/cmd2/ansi.py
+++ b/cmd2/ansi.py
@@ -12,165 +12,56 @@ from typing import (
IO,
Any,
List,
- Union,
+ Optional,
cast,
)
-import colorama # type: ignore [import]
-from colorama import (
- Back,
- Fore,
- Style,
-)
-from wcwidth import ( # type: ignore [import]
+from wcwidth import ( # type: ignore[import]
wcswidth,
)
-# On Windows, filter ANSI escape codes out of text sent to stdout/stderr, and replace them with equivalent Win32 calls
-colorama.init(strip=False)
+#######################################################
+# Common ANSI escape sequence constants
+#######################################################
+CSI = '\033['
+OSC = '\033]'
+BEL = '\a'
-# Values for allow_style setting
-STYLE_NEVER = 'Never'
-"""
-Constant for ``cmd2.ansi.allow_style`` to indicate ANSI style sequences should
-be removed from all output.
-"""
-STYLE_TERMINAL = 'Terminal'
-"""
-Constant for ``cmd2.ansi.allow_style`` to indicate ANSI style sequences
-should be removed if the output is not going to the terminal.
-"""
-STYLE_ALWAYS = 'Always'
-"""
-Constant for ``cmd2.ansi.allow_style`` to indicate ANSI style sequences should
-always be output.
-"""
-# Controls when ANSI style sequences are allowed in output
-allow_style = STYLE_TERMINAL
-"""When using outside of a cmd2 app, set this variable to one of:
+class AllowStyle(Enum):
+ """Values for ``cmd2.ansi.allow_style``"""
-- ``STYLE_NEVER`` - remove ANSI style sequences from all output
-- ``STYLE_TERMINAL`` - remove ANSI style sequences if the output is not going to the terminal
-- ``STYLE_ALWAYS`` - always output ANSI style sequences
+ 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
-to control the output of ANSI style sequences by methods in this module.
-
-The default is ``STYLE_TERMINAL``.
-"""
-
-# Regular expression to match ANSI style sequences (including 8-bit and 24-bit colors)
-ANSI_STYLE_RE = re.compile(r'\x1b\[[^m]*m')
-
-
-class ColorBase(Enum):
- """
- Base class used for defining color enums. See fg and bg classes for examples.
-
- Child classes should define enums in the follow structure:
+ def __str__(self) -> str:
+ """Return value instead of enum name for printing in cmd2's set command"""
+ return str(self.value)
- key: color name (e.g. black)
+ def __repr__(self) -> str:
+ """Return quoted value instead of enum description for printing in cmd2's set command"""
+ return repr(self.value)
- value: anything that when cast to a string returns an ANSI sequence
- """
- def __str__(self) -> str:
- """
- Return ANSI color sequence instead of enum name
- This is helpful when using a ColorBase in an f-string or format() call
- e.g. my_str = f"{fg.blue}hello{fg.reset}"
- """
- return str(self.value)
+# Controls when ANSI style sequences are allowed in output
+allow_style = AllowStyle.TERMINAL
+"""When using outside of a cmd2 app, set this variable to one of:
- def __add__(self, other: Any) -> str:
- """
- Support building a color string when self is the left operand
- e.g. fg.blue + "hello"
- """
- return cast(str, str(self) + other)
+- ``AllowStyle.ALWAYS`` - always output ANSI style sequences
+- ``AllowStyle.NEVER`` - remove ANSI style sequences from all output
+- ``AllowStyle.TERMINAL`` - remove ANSI style sequences if the output is not going to the terminal
- def __radd__(self, other: Any) -> str:
- """
- Support building a color string when self is the right operand
- e.g. "hello" + fg.reset
- """
- return cast(str, other + str(self))
+to control how ANSI style sequences are handled by ``style_aware_write()``.
- @classmethod
- def colors(cls) -> List[str]:
- """Return a list of color names."""
- # Use __members__ to ensure we get all key names, including those which are aliased
- return [color for color in cls.__members__]
+``style_aware_write()`` is called by cmd2 methods like ``poutput()``, ``perror()``,
+``pwarning()``, etc.
+The default is ``AllowStyle.TERMINAL``.
+"""
-# Foreground colors
-# noinspection PyPep8Naming
-class fg(ColorBase):
- """Enum class for foreground colors"""
-
- black = Fore.BLACK
- red = Fore.RED
- green = Fore.GREEN
- yellow = Fore.YELLOW
- blue = Fore.BLUE
- magenta = Fore.MAGENTA
- cyan = Fore.CYAN
- white = Fore.WHITE
- bright_black = Fore.LIGHTBLACK_EX
- bright_red = Fore.LIGHTRED_EX
- bright_green = Fore.LIGHTGREEN_EX
- bright_yellow = Fore.LIGHTYELLOW_EX
- bright_blue = Fore.LIGHTBLUE_EX
- bright_magenta = Fore.LIGHTMAGENTA_EX
- bright_cyan = Fore.LIGHTCYAN_EX
- bright_white = Fore.LIGHTWHITE_EX
- reset = Fore.RESET
-
-
-# Background colors
-# noinspection PyPep8Naming
-class bg(ColorBase):
- """Enum class for background colors"""
-
- black = Back.BLACK
- red = Back.RED
- green = Back.GREEN
- yellow = Back.YELLOW
- blue = Back.BLUE
- magenta = Back.MAGENTA
- cyan = Back.CYAN
- white = Back.WHITE
- bright_black = Back.LIGHTBLACK_EX
- bright_red = Back.LIGHTRED_EX
- bright_green = Back.LIGHTGREEN_EX
- bright_yellow = Back.LIGHTYELLOW_EX
- bright_blue = Back.LIGHTBLUE_EX
- bright_magenta = Back.LIGHTMAGENTA_EX
- bright_cyan = Back.LIGHTCYAN_EX
- bright_white = Back.LIGHTWHITE_EX
- reset = Back.RESET
-
-
-FG_RESET = fg.reset.value
-"""ANSI sequence to reset the foreground attributes"""
-BG_RESET = bg.reset.value
-"""ANSI sequence to reset the terminal background attributes"""
-RESET_ALL = Style.RESET_ALL
-"""ANSI sequence to reset all terminal attributes"""
-
-# Text intensities
-INTENSITY_BRIGHT = Style.BRIGHT
-"""ANSI sequence to make the text bright"""
-INTENSITY_DIM = Style.DIM
-"""ANSI sequence to make the text dim"""
-INTENSITY_NORMAL = Style.NORMAL
-"""ANSI sequence to make the text normal"""
-
-# ANSI style sequences not provided by colorama
-UNDERLINE_ENABLE = colorama.ansi.code_to_chars(4)
-"""ANSI sequence to turn on underline"""
-UNDERLINE_DISABLE = colorama.ansi.code_to_chars(24)
-"""ANSI sequence to turn off underline"""
+# Regular expression to match ANSI style sequences (including 8-bit and 24-bit colors)
+ANSI_STYLE_RE = re.compile(r'\x1b\[[^m]*m')
def strip_style(text: str) -> str:
@@ -225,56 +116,843 @@ def style_aware_write(fileobj: IO[str], msg: str) -> None:
:param fileobj: the file object being written to
:param msg: the string being written
"""
- if allow_style.lower() == STYLE_NEVER.lower() or (allow_style.lower() == STYLE_TERMINAL.lower() and not fileobj.isatty()):
+ if allow_style == AllowStyle.NEVER or (allow_style == AllowStyle.TERMINAL and not fileobj.isatty()):
msg = strip_style(msg)
fileobj.write(msg)
-def fg_lookup(fg_name: Union[str, fg]) -> str:
+####################################################################################
+# Utility functions which create various ANSI sequences
+####################################################################################
+def set_title(title: str) -> str:
"""
- Look up ANSI escape codes based on foreground color name.
+ Generate a string that, when printed, sets a terminal's window title.
- :param fg_name: foreground color name or enum to look up ANSI escape code(s) for
- :return: ANSI escape code(s) associated with this color
- :raises: ValueError: if the color cannot be found
+ :param title: new title for the window
+ :return: the set title string
"""
- if isinstance(fg_name, fg):
- return str(fg_name.value)
+ return f"{OSC}2;{title}{BEL}"
- try:
- ansi_escape = fg[fg_name.lower()].value
- except KeyError:
- raise ValueError(f"Foreground color '{fg_name}' does not exist; must be one of: {fg.colors()}")
- return str(ansi_escape)
+
+def clear_screen(clear_type: int = 2) -> str:
+ """
+ Generate a string that, when printed, clears a terminal screen based on value of clear_type.
+
+ :param clear_type: integer which specifies how to clear the screen (Defaults to 2)
+ Possible values:
+ 0 - clear from cursor to end of screen
+ 1 - clear from cursor to beginning of the screen
+ 2 - clear entire screen
+ 3 - clear entire screen and delete all lines saved in the scrollback buffer
+ :return: the clear screen string
+ :raises: ValueError if clear_type is not a valid value
+ """
+ if 0 <= clear_type <= 3:
+ return f"{CSI}{clear_type}J"
+ raise ValueError("clear_type must in an integer from 0 to 3")
-def bg_lookup(bg_name: Union[str, bg]) -> str:
+def clear_line(clear_type: int = 2) -> str:
+ """
+ Generate a string that, when printed, clears a line based on value of clear_type.
+
+ :param clear_type: integer which specifies how to clear the line (Defaults to 2)
+ Possible values:
+ 0 - clear from cursor to the end of the line
+ 1 - clear from cursor to beginning of the line
+ 2 - clear entire line
+ :return: the clear line string
+ :raises: ValueError if clear_type is not a valid value
"""
- Look up ANSI escape codes based on background color name.
+ if 0 <= clear_type <= 2:
+ return f"{CSI}{clear_type}K"
+ raise ValueError("clear_type must in an integer from 0 to 2")
- :param bg_name: background color name or enum to look up ANSI escape code(s) for
- :return: ANSI escape code(s) associated with this color
- :raises: ValueError: if the color cannot be found
+
+####################################################################################
+# Base classes which are not intended to be used directly
+####################################################################################
+class AnsiSequence:
+ """Base class to create ANSI sequence strings"""
+
+ def __add__(self, other: Any) -> str:
+ """
+ Support building an ANSI sequence string when self is the left operand
+ e.g. Fg.LIGHT_MAGENTA + "hello"
+ """
+ return str(self) + str(other)
+
+ def __radd__(self, other: Any) -> str:
+ """
+ Support building an ANSI sequence string when self is the right operand
+ e.g. "hello" + Fg.RESET
+ """
+ return str(other) + str(self)
+
+
+class FgColor(AnsiSequence):
+ """Base class for ANSI Sequences which set foreground text color"""
+
+ pass
+
+
+class BgColor(AnsiSequence):
+ """Base class for ANSI Sequences which set background text color"""
+
+ pass
+
+
+####################################################################################
+# Implementations intended for direct use
+####################################################################################
+# noinspection PyPep8Naming
+class Cursor:
+ """Create ANSI sequences to alter the cursor position"""
+
+ @staticmethod
+ def UP(count: int = 1) -> str:
+ """Move the cursor up a specified amount of lines (Defaults to 1)"""
+ return f"{CSI}{count}A"
+
+ @staticmethod
+ def DOWN(count: int = 1) -> str:
+ """Move the cursor down a specified amount of lines (Defaults to 1)"""
+ return f"{CSI}{count}B"
+
+ @staticmethod
+ def FORWARD(count: int = 1) -> str:
+ """Move the cursor forward a specified amount of lines (Defaults to 1)"""
+ return f"{CSI}{count}C"
+
+ @staticmethod
+ def BACK(count: int = 1) -> str:
+ """Move the cursor back a specified amount of lines (Defaults to 1)"""
+ return f"{CSI}{count}D"
+
+ @staticmethod
+ def SET_POS(x: int, y: int) -> str:
+ """Set the cursor position to coordinates which are 1-based"""
+ return f"{CSI}{y};{x}H"
+
+
+class TextStyle(AnsiSequence, Enum):
+ """Create text style ANSI sequences"""
+
+ # Resets all styles and colors of text
+ RESET_ALL = 0
+
+ INTENSITY_BOLD = 1
+ INTENSITY_DIM = 2
+ INTENSITY_NORMAL = 22
+
+ ITALIC_ENABLE = 3
+ ITALIC_DISABLE = 23
+
+ OVERLINE_ENABLE = 53
+ OVERLINE_DISABLE = 55
+
+ STRIKETHROUGH_ENABLE = 9
+ STRIKETHROUGH_DISABLE = 29
+
+ UNDERLINE_ENABLE = 4
+ UNDERLINE_DISABLE = 24
+
+ def __str__(self) -> str:
+ """
+ Return ANSI text style sequence instead of enum name
+ This is helpful when using a TextStyle in an f-string or format() call
+ e.g. my_str = f"{TextStyle.UNDERLINE_ENABLE}hello{TextStyle.UNDERLINE_DISABLE}"
+ """
+ return f"{CSI}{self.value}m"
+
+
+class Fg(FgColor, Enum):
+ """
+ Create ANSI sequences for the 16 standard terminal foreground text colors.
+ A terminal's color settings affect how these colors appear.
+ To reset any foreground color, use Fg.RESET.
"""
- if isinstance(bg_name, bg):
- return str(bg_name.value)
- try:
- ansi_escape = bg[bg_name.lower()].value
- except KeyError:
- raise ValueError(f"Background color '{bg_name}' does not exist; must be one of: {bg.colors()}")
- return str(ansi_escape)
+ BLACK = 30
+ RED = 31
+ GREEN = 32
+ YELLOW = 33
+ BLUE = 34
+ MAGENTA = 35
+ CYAN = 36
+ LIGHT_GRAY = 37
+ DARK_GRAY = 90
+ LIGHT_RED = 91
+ LIGHT_GREEN = 92
+ LIGHT_YELLOW = 93
+ LIGHT_BLUE = 94
+ LIGHT_MAGENTA = 95
+ LIGHT_CYAN = 96
+ WHITE = 97
+
+ RESET = 39
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using an Fg in an f-string or format() call
+ e.g. my_str = f"{Fg.BLUE}hello{Fg.RESET}"
+ """
+ return f"{CSI}{self.value}m"
+
+
+class Bg(BgColor, Enum):
+ """
+ Create ANSI sequences for the 16 standard terminal background text colors.
+ A terminal's color settings affect how these colors appear.
+ To reset any background color, use Bg.RESET.
+ """
+
+ BLACK = 40
+ RED = 41
+ GREEN = 42
+ YELLOW = 44
+ BLUE = 44
+ MAGENTA = 45
+ CYAN = 46
+ LIGHT_GRAY = 47
+ DARK_GRAY = 100
+ LIGHT_RED = 101
+ LIGHT_GREEN = 102
+ LIGHT_YELLOW = 103
+ LIGHT_BLUE = 104
+ LIGHT_MAGENTA = 105
+ LIGHT_CYAN = 106
+ WHITE = 107
+
+ RESET = 49
+
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using a Bg in an f-string or format() call
+ e.g. my_str = f"{Bg.BLACK}hello{Bg.RESET}"
+ """
+ return f"{CSI}{self.value}m"
+
+
+class EightBitFg(FgColor, Enum):
+ """
+ Create ANSI sequences for 8-bit terminal foreground text colors. Most terminals support 8-bit/256-color mode.
+ The first 16 colors correspond to the 16 colors from Fg and behave the same way.
+ To reset any foreground color, including 8-bit, use Fg.RESET.
+ """
+
+ BLACK = 0
+ RED = 1
+ GREEN = 2
+ YELLOW = 3
+ BLUE = 4
+ MAGENTA = 5
+ CYAN = 6
+ LIGHT_GRAY = 7
+ DARK_GRAY = 8
+ LIGHT_RED = 9
+ LIGHT_GREEN = 10
+ LIGHT_YELLOW = 11
+ LIGHT_BLUE = 12
+ LIGHT_MAGENTA = 13
+ LIGHT_CYAN = 14
+ WHITE = 15
+ GRAY_0 = 16
+ NAVY_BLUE = 17
+ DARK_BLUE = 18
+ BLUE_3A = 19
+ BLUE_3B = 20
+ BLUE_1 = 21
+ DARK_GREEN = 22
+ DEEP_SKY_BLUE_4A = 23
+ DEEP_SKY_BLUE_4B = 24
+ DEEP_SKY_BLUE_4C = 25
+ DODGER_BLUE_3 = 26
+ DODGER_BLUE_2 = 27
+ GREEN_4 = 28
+ SPRING_GREEN_4 = 29
+ TURQUOISE_4 = 30
+ DEEP_SKY_BLUE_3A = 31
+ DEEP_SKY_BLUE_3B = 32
+ DODGER_BLUE_1 = 33
+ GREEN_3A = 34
+ SPRING_GREEN_3A = 35
+ DARK_CYAN = 36
+ LIGHT_SEA_GREEN = 37
+ DEEP_SKY_BLUE_2 = 38
+ DEEP_SKY_BLUE_1 = 39
+ GREEN_3B = 40
+ SPRING_GREEN_3B = 41
+ SPRING_GREEN_2A = 42
+ CYAN_3 = 43
+ DARK_TURQUOISE = 44
+ TURQUOISE_2 = 45
+ GREEN_1 = 46
+ SPRING_GREEN_2B = 47
+ SPRING_GREEN_1 = 48
+ MEDIUM_SPRING_GREEN = 49
+ CYAN_2 = 50
+ CYAN_1 = 51
+ DARK_RED_1 = 52
+ DEEP_PINK_4A = 53
+ PURPLE_4A = 54
+ PURPLE_4B = 55
+ PURPLE_3 = 56
+ BLUE_VIOLET = 57
+ ORANGE_4A = 58
+ GRAY_37 = 59
+ MEDIUM_PURPLE_4 = 60
+ SLATE_BLUE_3A = 61
+ SLATE_BLUE_3B = 62
+ ROYAL_BLUE_1 = 63
+ CHARTREUSE_4 = 64
+ DARK_SEA_GREEN_4A = 65
+ PALE_TURQUOISE_4 = 66
+ STEEL_BLUE = 67
+ STEEL_BLUE_3 = 68
+ CORNFLOWER_BLUE = 69
+ CHARTREUSE_3A = 70
+ DARK_SEA_GREEN_4B = 71
+ CADET_BLUE_2 = 72
+ CADET_BLUE_1 = 73
+ SKY_BLUE_3 = 74
+ STEEL_BLUE_1A = 75
+ CHARTREUSE_3B = 76
+ PALE_GREEN_3A = 77
+ SEA_GREEN_3 = 78
+ AQUAMARINE_3 = 79
+ MEDIUM_TURQUOISE = 80
+ STEEL_BLUE_1B = 81
+ CHARTREUSE_2A = 82
+ SEA_GREEN_2 = 83
+ SEA_GREEN_1A = 84
+ SEA_GREEN_1B = 85
+ AQUAMARINE_1A = 86
+ DARK_SLATE_GRAY_2 = 87
+ DARK_RED_2 = 88
+ DEEP_PINK_4B = 89
+ DARK_MAGENTA_1 = 90
+ DARK_MAGENTA_2 = 91
+ DARK_VIOLET_1A = 92
+ PURPLE_1A = 93
+ ORANGE_4B = 94
+ LIGHT_PINK_4 = 95
+ PLUM_4 = 96
+ MEDIUM_PURPLE_3A = 97
+ MEDIUM_PURPLE_3B = 98
+ SLATE_BLUE_1 = 99
+ YELLOW_4A = 100
+ WHEAT_4 = 101
+ GRAY_53 = 102
+ LIGHT_SLATE_GRAY = 103
+ MEDIUM_PURPLE = 104
+ LIGHT_SLATE_BLUE = 105
+ YELLOW_4B = 106
+ DARK_OLIVE_GREEN_3A = 107
+ DARK_GREEN_SEA = 108
+ LIGHT_SKY_BLUE_3A = 109
+ LIGHT_SKY_BLUE_3B = 110
+ SKY_BLUE_2 = 111
+ CHARTREUSE_2B = 112
+ DARK_OLIVE_GREEN_3B = 113
+ PALE_GREEN_3B = 114
+ DARK_SEA_GREEN_3A = 115
+ DARK_SLATE_GRAY_3 = 116
+ SKY_BLUE_1 = 117
+ CHARTREUSE_1 = 118
+ LIGHT_GREEN_2 = 119
+ LIGHT_GREEN_3 = 120
+ PALE_GREEN_1A = 121
+ AQUAMARINE_1B = 122
+ DARK_SLATE_GRAY_1 = 123
+ RED_3A = 124
+ DEEP_PINK_4C = 125
+ MEDIUM_VIOLET_RED = 126
+ MAGENTA_3A = 127
+ DARK_VIOLET_1B = 128
+ PURPLE_1B = 129
+ DARK_ORANGE_3A = 130
+ INDIAN_RED_1A = 131
+ HOT_PINK_3A = 132
+ MEDIUM_ORCHID_3 = 133
+ MEDIUM_ORCHID = 134
+ MEDIUM_PURPLE_2A = 135
+ DARK_GOLDENROD = 136
+ LIGHT_SALMON_3A = 137
+ ROSY_BROWN = 138
+ GRAY_63 = 139
+ MEDIUM_PURPLE_2B = 140
+ MEDIUM_PURPLE_1 = 141
+ GOLD_3A = 142
+ DARK_KHAKI = 143
+ NAVAJO_WHITE_3 = 144
+ GRAY_69 = 145
+ LIGHT_STEEL_BLUE_3 = 146
+ LIGHT_STEEL_BLUE = 147
+ YELLOW_3A = 148
+ DARK_OLIVE_GREEN_3 = 149
+ DARK_SEA_GREEN_3B = 150
+ DARK_SEA_GREEN_2 = 151
+ LIGHT_CYAN_3 = 152
+ LIGHT_SKY_BLUE_1 = 153
+ GREEN_YELLOW = 154
+ DARK_OLIVE_GREEN_2 = 155
+ PALE_GREEN_1B = 156
+ DARK_SEA_GREEN_5B = 157
+ DARK_SEA_GREEN_5A = 158
+ PALE_TURQUOISE_1 = 159
+ RED_3B = 160
+ DEEP_PINK_3A = 161
+ DEEP_PINK_3B = 162
+ MAGENTA_3B = 163
+ MAGENTA_3C = 164
+ MAGENTA_2A = 165
+ DARK_ORANGE_3B = 166
+ INDIAN_RED_1B = 167
+ HOT_PINK_3B = 168
+ HOT_PINK_2 = 169
+ ORCHID = 170
+ MEDIUM_ORCHID_1A = 171
+ ORANGE_3 = 172
+ LIGHT_SALMON_3B = 173
+ LIGHT_PINK_3 = 174
+ PINK_3 = 175
+ PLUM_3 = 176
+ VIOLET = 177
+ GOLD_3B = 178
+ LIGHT_GOLDENROD_3 = 179
+ TAN = 180
+ MISTY_ROSE_3 = 181
+ THISTLE_3 = 182
+ PLUM_2 = 183
+ YELLOW_3B = 184
+ KHAKI_3 = 185
+ LIGHT_GOLDENROD_2A = 186
+ LIGHT_YELLOW_3 = 187
+ GRAY_84 = 188
+ LIGHT_STEEL_BLUE_1 = 189
+ YELLOW_2 = 190
+ DARK_OLIVE_GREEN_1A = 191
+ DARK_OLIVE_GREEN_1B = 192
+ DARK_SEA_GREEN_1 = 193
+ HONEYDEW_2 = 194
+ LIGHT_CYAN_1 = 195
+ RED_1 = 196
+ DEEP_PINK_2 = 197
+ DEEP_PINK_1A = 198
+ DEEP_PINK_1B = 199
+ MAGENTA_2B = 200
+ MAGENTA_1 = 201
+ ORANGE_RED_1 = 202
+ INDIAN_RED_1C = 203
+ INDIAN_RED_1D = 204
+ HOT_PINK_1A = 205
+ HOT_PINK_1B = 206
+ MEDIUM_ORCHID_1B = 207
+ DARK_ORANGE = 208
+ SALMON_1 = 209
+ LIGHT_CORAL = 210
+ PALE_VIOLET_RED_1 = 211
+ ORCHID_2 = 212
+ ORCHID_1 = 213
+ ORANGE_1 = 214
+ SANDY_BROWN = 215
+ LIGHT_SALMON_1 = 216
+ LIGHT_PINK_1 = 217
+ PINK_1 = 218
+ PLUM_1 = 219
+ GOLD_1 = 220
+ LIGHT_GOLDENROD_2B = 221
+ LIGHT_GOLDENROD_2C = 222
+ NAVAJO_WHITE_1 = 223
+ MISTY_ROSE1 = 224
+ THISTLE_1 = 225
+ YELLOW_1 = 226
+ LIGHT_GOLDENROD_1 = 227
+ KHAKI_1 = 228
+ WHEAT_1 = 229
+ CORNSILK_1 = 230
+ GRAY_100 = 231
+ GRAY_3 = 232
+ GRAY_7 = 233
+ GRAY_11 = 234
+ GRAY_15 = 235
+ GRAY_19 = 236
+ GRAY_23 = 237
+ GRAY_27 = 238
+ GRAY_30 = 239
+ GRAY_35 = 240
+ GRAY_39 = 241
+ GRAY_42 = 242
+ GRAY_46 = 243
+ GRAY_50 = 244
+ GRAY_54 = 245
+ GRAY_58 = 246
+ GRAY_62 = 247
+ GRAY_66 = 248
+ GRAY_70 = 249
+ GRAY_74 = 250
+ GRAY_78 = 251
+ GRAY_82 = 252
+ GRAY_85 = 253
+ GRAY_89 = 254
+ GRAY_93 = 255
+
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using an EightBitFg in an f-string or format() call
+ e.g. my_str = f"{EightBitFg.SLATE_BLUE_1}hello{Fg.RESET}"
+ """
+ return f"{CSI}{38};5;{self.value}m"
+
+
+class EightBitBg(BgColor, Enum):
+ """
+ Create ANSI sequences for 8-bit terminal background text colors. Most terminals support 8-bit/256-color mode.
+ The first 16 colors correspond to the 16 colors from Bg and behave the same way.
+ To reset any background color, including 8-bit, use Bg.RESET.
+ """
+
+ BLACK = 0
+ RED = 1
+ GREEN = 2
+ YELLOW = 3
+ BLUE = 4
+ MAGENTA = 5
+ CYAN = 6
+ LIGHT_GRAY = 7
+ DARK_GRAY = 8
+ LIGHT_RED = 9
+ LIGHT_GREEN = 10
+ LIGHT_YELLOW = 11
+ LIGHT_BLUE = 12
+ LIGHT_MAGENTA = 13
+ LIGHT_CYAN = 14
+ WHITE = 15
+ GRAY_0 = 16
+ NAVY_BLUE = 17
+ DARK_BLUE = 18
+ BLUE_3A = 19
+ BLUE_3B = 20
+ BLUE_1 = 21
+ DARK_GREEN = 22
+ DEEP_SKY_BLUE_4A = 23
+ DEEP_SKY_BLUE_4B = 24
+ DEEP_SKY_BLUE_4C = 25
+ DODGER_BLUE_3 = 26
+ DODGER_BLUE_2 = 27
+ GREEN_4 = 28
+ SPRING_GREEN_4 = 29
+ TURQUOISE_4 = 30
+ DEEP_SKY_BLUE_3A = 31
+ DEEP_SKY_BLUE_3B = 32
+ DODGER_BLUE_1 = 33
+ GREEN_3A = 34
+ SPRING_GREEN_3A = 35
+ DARK_CYAN = 36
+ LIGHT_SEA_GREEN = 37
+ DEEP_SKY_BLUE_2 = 38
+ DEEP_SKY_BLUE_1 = 39
+ GREEN_3B = 40
+ SPRING_GREEN_3B = 41
+ SPRING_GREEN_2A = 42
+ CYAN_3 = 43
+ DARK_TURQUOISE = 44
+ TURQUOISE_2 = 45
+ GREEN_1 = 46
+ SPRING_GREEN_2B = 47
+ SPRING_GREEN_1 = 48
+ MEDIUM_SPRING_GREEN = 49
+ CYAN_2 = 50
+ CYAN_1 = 51
+ DARK_RED_1 = 52
+ DEEP_PINK_4A = 53
+ PURPLE_4A = 54
+ PURPLE_4B = 55
+ PURPLE_3 = 56
+ BLUE_VIOLET = 57
+ ORANGE_4A = 58
+ GRAY_37 = 59
+ MEDIUM_PURPLE_4 = 60
+ SLATE_BLUE_3A = 61
+ SLATE_BLUE_3B = 62
+ ROYAL_BLUE_1 = 63
+ CHARTREUSE_4 = 64
+ DARK_SEA_GREEN_4A = 65
+ PALE_TURQUOISE_4 = 66
+ STEEL_BLUE = 67
+ STEEL_BLUE_3 = 68
+ CORNFLOWER_BLUE = 69
+ CHARTREUSE_3A = 70
+ DARK_SEA_GREEN_4B = 71
+ CADET_BLUE_2 = 72
+ CADET_BLUE_1 = 73
+ SKY_BLUE_3 = 74
+ STEEL_BLUE_1A = 75
+ CHARTREUSE_3B = 76
+ PALE_GREEN_3A = 77
+ SEA_GREEN_3 = 78
+ AQUAMARINE_3 = 79
+ MEDIUM_TURQUOISE = 80
+ STEEL_BLUE_1B = 81
+ CHARTREUSE_2A = 82
+ SEA_GREEN_2 = 83
+ SEA_GREEN_1A = 84
+ SEA_GREEN_1B = 85
+ AQUAMARINE_1A = 86
+ DARK_SLATE_GRAY_2 = 87
+ DARK_RED_2 = 88
+ DEEP_PINK_4B = 89
+ DARK_MAGENTA_1 = 90
+ DARK_MAGENTA_2 = 91
+ DARK_VIOLET_1A = 92
+ PURPLE_1A = 93
+ ORANGE_4B = 94
+ LIGHT_PINK_4 = 95
+ PLUM_4 = 96
+ MEDIUM_PURPLE_3A = 97
+ MEDIUM_PURPLE_3B = 98
+ SLATE_BLUE_1 = 99
+ YELLOW_4A = 100
+ WHEAT_4 = 101
+ GRAY_53 = 102
+ LIGHT_SLATE_GRAY = 103
+ MEDIUM_PURPLE = 104
+ LIGHT_SLATE_BLUE = 105
+ YELLOW_4B = 106
+ DARK_OLIVE_GREEN_3A = 107
+ DARK_GREEN_SEA = 108
+ LIGHT_SKY_BLUE_3A = 109
+ LIGHT_SKY_BLUE_3B = 110
+ SKY_BLUE_2 = 111
+ CHARTREUSE_2B = 112
+ DARK_OLIVE_GREEN_3B = 113
+ PALE_GREEN_3B = 114
+ DARK_SEA_GREEN_3A = 115
+ DARK_SLATE_GRAY_3 = 116
+ SKY_BLUE_1 = 117
+ CHARTREUSE_1 = 118
+ LIGHT_GREEN_2 = 119
+ LIGHT_GREEN_3 = 120
+ PALE_GREEN_1A = 121
+ AQUAMARINE_1B = 122
+ DARK_SLATE_GRAY_1 = 123
+ RED_3A = 124
+ DEEP_PINK_4C = 125
+ MEDIUM_VIOLET_RED = 126
+ MAGENTA_3A = 127
+ DARK_VIOLET_1B = 128
+ PURPLE_1B = 129
+ DARK_ORANGE_3A = 130
+ INDIAN_RED_1A = 131
+ HOT_PINK_3A = 132
+ MEDIUM_ORCHID_3 = 133
+ MEDIUM_ORCHID = 134
+ MEDIUM_PURPLE_2A = 135
+ DARK_GOLDENROD = 136
+ LIGHT_SALMON_3A = 137
+ ROSY_BROWN = 138
+ GRAY_63 = 139
+ MEDIUM_PURPLE_2B = 140
+ MEDIUM_PURPLE_1 = 141
+ GOLD_3A = 142
+ DARK_KHAKI = 143
+ NAVAJO_WHITE_3 = 144
+ GRAY_69 = 145
+ LIGHT_STEEL_BLUE_3 = 146
+ LIGHT_STEEL_BLUE = 147
+ YELLOW_3A = 148
+ DARK_OLIVE_GREEN_3 = 149
+ DARK_SEA_GREEN_3B = 150
+ DARK_SEA_GREEN_2 = 151
+ LIGHT_CYAN_3 = 152
+ LIGHT_SKY_BLUE_1 = 153
+ GREEN_YELLOW = 154
+ DARK_OLIVE_GREEN_2 = 155
+ PALE_GREEN_1B = 156
+ DARK_SEA_GREEN_5B = 157
+ DARK_SEA_GREEN_5A = 158
+ PALE_TURQUOISE_1 = 159
+ RED_3B = 160
+ DEEP_PINK_3A = 161
+ DEEP_PINK_3B = 162
+ MAGENTA_3B = 163
+ MAGENTA_3C = 164
+ MAGENTA_2A = 165
+ DARK_ORANGE_3B = 166
+ INDIAN_RED_1B = 167
+ HOT_PINK_3B = 168
+ HOT_PINK_2 = 169
+ ORCHID = 170
+ MEDIUM_ORCHID_1A = 171
+ ORANGE_3 = 172
+ LIGHT_SALMON_3B = 173
+ LIGHT_PINK_3 = 174
+ PINK_3 = 175
+ PLUM_3 = 176
+ VIOLET = 177
+ GOLD_3B = 178
+ LIGHT_GOLDENROD_3 = 179
+ TAN = 180
+ MISTY_ROSE_3 = 181
+ THISTLE_3 = 182
+ PLUM_2 = 183
+ YELLOW_3B = 184
+ KHAKI_3 = 185
+ LIGHT_GOLDENROD_2A = 186
+ LIGHT_YELLOW_3 = 187
+ GRAY_84 = 188
+ LIGHT_STEEL_BLUE_1 = 189
+ YELLOW_2 = 190
+ DARK_OLIVE_GREEN_1A = 191
+ DARK_OLIVE_GREEN_1B = 192
+ DARK_SEA_GREEN_1 = 193
+ HONEYDEW_2 = 194
+ LIGHT_CYAN_1 = 195
+ RED_1 = 196
+ DEEP_PINK_2 = 197
+ DEEP_PINK_1A = 198
+ DEEP_PINK_1B = 199
+ MAGENTA_2B = 200
+ MAGENTA_1 = 201
+ ORANGE_RED_1 = 202
+ INDIAN_RED_1C = 203
+ INDIAN_RED_1D = 204
+ HOT_PINK_1A = 205
+ HOT_PINK_1B = 206
+ MEDIUM_ORCHID_1B = 207
+ DARK_ORANGE = 208
+ SALMON_1 = 209
+ LIGHT_CORAL = 210
+ PALE_VIOLET_RED_1 = 211
+ ORCHID_2 = 212
+ ORCHID_1 = 213
+ ORANGE_1 = 214
+ SANDY_BROWN = 215
+ LIGHT_SALMON_1 = 216
+ LIGHT_PINK_1 = 217
+ PINK_1 = 218
+ PLUM_1 = 219
+ GOLD_1 = 220
+ LIGHT_GOLDENROD_2B = 221
+ LIGHT_GOLDENROD_2C = 222
+ NAVAJO_WHITE_1 = 223
+ MISTY_ROSE1 = 224
+ THISTLE_1 = 225
+ YELLOW_1 = 226
+ LIGHT_GOLDENROD_1 = 227
+ KHAKI_1 = 228
+ WHEAT_1 = 229
+ CORNSILK_1 = 230
+ GRAY_100 = 231
+ GRAY_3 = 232
+ GRAY_7 = 233
+ GRAY_11 = 234
+ GRAY_15 = 235
+ GRAY_19 = 236
+ GRAY_23 = 237
+ GRAY_27 = 238
+ GRAY_30 = 239
+ GRAY_35 = 240
+ GRAY_39 = 241
+ GRAY_42 = 242
+ GRAY_46 = 243
+ GRAY_50 = 244
+ GRAY_54 = 245
+ GRAY_58 = 246
+ GRAY_62 = 247
+ GRAY_66 = 248
+ GRAY_70 = 249
+ GRAY_74 = 250
+ GRAY_78 = 251
+ GRAY_82 = 252
+ GRAY_85 = 253
+ GRAY_89 = 254
+ GRAY_93 = 255
+
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using an EightBitBg in an f-string or format() call
+ e.g. my_str = f"{EightBitBg.KHAKI_3}hello{Bg.RESET}"
+ """
+ return f"{CSI}{48};5;{self.value}m"
+
+
+class RgbFg(FgColor):
+ """
+ Create ANSI sequences for 24-bit (RGB) terminal foreground text colors. The terminal must support 24-bit/true-color mode.
+ To reset any foreground color, including 24-bit, use Fg.RESET.
+ """
+
+ def __init__(self, r: int, g: int, b: int) -> None:
+ """
+ RgbFg initializer
+
+ :param r: integer from 0-255 for the red component of the color
+ :param g: integer from 0-255 for the green component of the color
+ :param b: integer from 0-255 for the blue component of the color
+ :raises: ValueError if r, g, or b is not in the range 0-255
+ """
+ if any(c < 0 or c > 255 for c in [r, g, b]):
+ raise ValueError("RGB values must be integers in the range of 0 to 255")
+
+ self._sequence = f"{CSI}{38};2;{r};{g};{b}m"
+
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using an RgbFg in an f-string or format() call
+ e.g. my_str = f"{RgbFg(0, 55, 100)}hello{Fg.RESET}"
+ """
+ return self._sequence
+
+
+class RgbBg(BgColor):
+ """
+ Create ANSI sequences for 24-bit (RGB) terminal background text colors. The terminal must support 24-bit/true-color mode.
+ To reset any background color, including 24-bit, use Bg.RESET.
+ """
+
+ def __init__(self, r: int, g: int, b: int) -> None:
+ """
+ RgbBg initializer
+
+ :param r: integer from 0-255 for the red component of the color
+ :param g: integer from 0-255 for the green component of the color
+ :param b: integer from 0-255 for the blue component of the color
+ :raises: ValueError if r, g, or b is not in the range 0-255
+ """
+ if any(c < 0 or c > 255 for c in [r, g, b]):
+ raise ValueError("RGB values must be integers in the range of 0 to 255")
+
+ self._sequence = f"{CSI}{48};2;{r};{g};{b}m"
+
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using an RgbBg in an f-string or format() call
+ e.g. my_str = f"{RgbBg(100, 255, 27)}hello{Bg.RESET}"
+ """
+ return self._sequence
+
+# TODO: Remove this PyShadowingNames usage when deprecated fg and bg classes are removed.
# noinspection PyShadowingNames
def style(
text: Any,
*,
- fg: Union[str, fg] = '',
- bg: Union[str, bg] = '',
- bold: bool = False,
- dim: bool = False,
- underline: bool = False,
+ fg: Optional[FgColor] = None,
+ bg: Optional[BgColor] = None,
+ bold: Optional[bool] = None,
+ dim: Optional[bool] = None,
+ italic: Optional[bool] = None,
+ overline: Optional[bool] = None,
+ strikethrough: Optional[bool] = None,
+ underline: Optional[bool] = None,
) -> str:
"""
Apply ANSI colors and/or styles to a string and return it.
@@ -282,59 +960,74 @@ def style(
to undo whatever styling was done at the beginning.
:param text: text to format (anything convertible to a str)
- :param fg: foreground color. Relies on `fg_lookup()` to retrieve ANSI escape based on name or enum.
+ :param fg: foreground color provided as any subclass of FgColor (e.g. Fg, EightBitFg, RgbFg)
Defaults to no color.
- :param bg: background color. Relies on `bg_lookup()` to retrieve ANSI escape based on name or enum.
+ :param bg: foreground color provided as any subclass of BgColor (e.g. Bg, EightBitBg, RgbBg)
Defaults to no color.
- :param bold: apply the bold style if True. Can be combined with dim. Defaults to False.
- :param dim: apply the dim style if True. Can be combined with bold. Defaults to False.
+ :param bold: apply the bold style if True. Defaults to False.
+ :param dim: apply the dim style if True. Defaults to False.
+ :param italic: apply the italic style if True. Defaults to False.
+ :param overline: apply the overline style if True. Defaults to False.
+ :param strikethrough: apply the strikethrough style if True. Defaults to False.
:param underline: apply the underline style if True. Defaults to False.
:return: the stylized string
"""
# List of strings that add style
- additions: List[str] = []
+ additions: List[AnsiSequence] = []
# List of strings that remove style
- removals: List[str] = []
+ removals: List[AnsiSequence] = []
# Convert the text object into a string if it isn't already one
text_formatted = str(text)
# Process the style settings
- if fg:
- additions.append(fg_lookup(fg))
- removals.append(FG_RESET)
+ if fg is not None:
+ additions.append(fg)
+ removals.append(Fg.RESET)
- if bg:
- additions.append(bg_lookup(bg))
- removals.append(BG_RESET)
+ if bg is not None:
+ additions.append(bg)
+ removals.append(Bg.RESET)
if bold:
- additions.append(INTENSITY_BRIGHT)
- removals.append(INTENSITY_NORMAL)
+ additions.append(TextStyle.INTENSITY_BOLD)
+ removals.append(TextStyle.INTENSITY_NORMAL)
if dim:
- additions.append(INTENSITY_DIM)
- removals.append(INTENSITY_NORMAL)
+ additions.append(TextStyle.INTENSITY_DIM)
+ removals.append(TextStyle.INTENSITY_NORMAL)
+
+ if italic:
+ additions.append(TextStyle.ITALIC_ENABLE)
+ removals.append(TextStyle.ITALIC_DISABLE)
+
+ if overline:
+ additions.append(TextStyle.OVERLINE_ENABLE)
+ removals.append(TextStyle.OVERLINE_DISABLE)
+
+ if strikethrough:
+ additions.append(TextStyle.STRIKETHROUGH_ENABLE)
+ removals.append(TextStyle.STRIKETHROUGH_DISABLE)
if underline:
- additions.append(UNDERLINE_ENABLE)
- removals.append(UNDERLINE_DISABLE)
+ additions.append(TextStyle.UNDERLINE_ENABLE)
+ removals.append(TextStyle.UNDERLINE_DISABLE)
# Combine the ANSI style sequences with the text
- return "".join(additions) + text_formatted + "".join(removals)
+ return "".join(map(str, additions)) + text_formatted + "".join(map(str, removals))
# Default styles for printing strings of various types.
# These can be altered to suit an application's needs and only need to be a
# function with the following structure: func(str) -> str
-style_success = functools.partial(style, fg=fg.green)
+style_success = functools.partial(style, fg=Fg.GREEN)
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify success"""
-style_warning = functools.partial(style, fg=fg.bright_yellow)
+style_warning = functools.partial(style, fg=Fg.LIGHT_YELLOW)
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify a warning"""
-style_error = functools.partial(style, fg=fg.bright_red)
+style_error = functools.partial(style, fg=Fg.LIGHT_RED)
"""Partial function supplying arguments to :meth:`cmd2.ansi.style()` which colors text to signify an error"""
@@ -348,9 +1041,6 @@ def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_off
:param alert_msg: the message to display to the user
:return: the correct string so that the alert message appears to the user to be printed above the current line.
"""
- from colorama import (
- Cursor,
- )
# Split the prompt lines since it can contain newline characters.
prompt_lines = prompt.splitlines()
@@ -385,20 +1075,76 @@ def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_off
# Clear each line from the bottom up so that the cursor ends up on the first prompt line
total_lines = num_prompt_terminal_lines + num_input_terminal_lines
- terminal_str += (colorama.ansi.clear_line() + Cursor.UP(1)) * (total_lines - 1)
+ terminal_str += (clear_line() + Cursor.UP(1)) * (total_lines - 1)
# Clear the first prompt line
- terminal_str += colorama.ansi.clear_line()
+ terminal_str += clear_line()
# Move the cursor to the beginning of the first prompt line and print the alert
terminal_str += '\r' + alert_msg
return terminal_str
-def set_title_str(title: str) -> str:
- """Get the required string, including ANSI escape codes, for setting window title for the terminal.
+####################################################################################
+# The following classes are deprecated.
+####################################################################################
+# noinspection PyPep8Naming
+class fg(FgColor, Enum):
+ """Deprecated Enum class for foreground colors. Use Fg instead."""
+
+ black = 30
+ red = 31
+ green = 32
+ yellow = 33
+ blue = 34
+ magenta = 35
+ cyan = 36
+ white = 37
+ reset = 39
+ bright_black = 90
+ bright_red = 91
+ bright_green = 92
+ bright_yellow = 93
+ bright_blue = 94
+ bright_magenta = 95
+ bright_cyan = 96
+ bright_white = 97
- :param title: new title for the window
- :return: string to write to sys.stderr in order to set the window title to the desired test
- """
- return cast(str, colorama.ansi.set_title(title))
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using an fg in an f-string or format() call
+ e.g. my_str = f"{fg.blue}hello{fg.reset}"
+ """
+ return f"{CSI}{self.value}m"
+
+
+# noinspection PyPep8Naming
+class bg(BgColor, Enum):
+ """Deprecated Enum class for background colors. Use Bg instead."""
+
+ black = 40
+ red = 41
+ green = 42
+ yellow = 43
+ blue = 44
+ magenta = 45
+ cyan = 46
+ white = 47
+ reset = 49
+ bright_black = 100
+ bright_red = 101
+ bright_green = 102
+ bright_yellow = 103
+ bright_blue = 104
+ bright_magenta = 105
+ bright_cyan = 106
+ bright_white = 107
+
+ def __str__(self) -> str:
+ """
+ Return ANSI color sequence instead of enum name
+ This is helpful when using a bg in an f-string or format() call
+ e.g. my_str = f"{bg.black}hello{bg.reset}"
+ """
+ return f"{CSI}{self.value}m"