Repository: Textualize/textual

Issue:
[colours] Prefix ANSI colours with `ansi_`, and add management of named Web colours (which would then become the main source of named colours in CSS)

Discussion:

Patch:
diff --git a/src/textual/_color_constants.py b/src/textual/_color_constants.py
index fd0037ea8..cedab5673 100644
--- a/src/textual/_color_constants.py
+++ b/src/textual/_color_constants.py
@@ -1,239 +1,172 @@
 from __future__ import annotations
 
-COLOR_NAME_TO_RGB: dict[str, tuple[int, int, int]] = {
+COLOR_NAME_TO_RGB: dict[str, tuple[int, int, int] | tuple[int, int, int, float]] = {
+    # Let's start with a specific pseudo-color::
+    "transparent": (0, 0, 0, 0),
+    # Then, the 16 common ANSI colors:
+    "ansi_black": (0, 0, 0),
+    "ansi_red": (128, 0, 0),
+    "ansi_green": (0, 128, 0),
+    "ansi_yellow": (128, 128, 0),
+    "ansi_blue": (0, 0, 128),
+    "ansi_magenta": (128, 0, 128),
+    "ansi_cyan": (0, 128, 128),
+    "ansi_white": (192, 192, 192),
+    "ansi_bright_black": (128, 128, 128),
+    "ansi_bright_red": (255, 0, 0),
+    "ansi_bright_green": (0, 255, 0),
+    "ansi_bright_yellow": (255, 255, 0),
+    "ansi_bright_blue": (0, 0, 255),
+    "ansi_bright_magenta": (255, 0, 255),
+    "ansi_bright_cyan": (0, 255, 255),
+    "ansi_bright_white": (255, 255, 255),
+    # And then, Web color keywords: (up to CSS Color Module Level 4)
     "black": (0, 0, 0),
-    "red": (128, 0, 0),
+    "silver": (192, 192, 192),
+    "gray": (128, 128, 128),
+    "white": (255, 255, 255),
+    "maroon": (128, 0, 0),
+    "red": (255, 0, 0),
+    "purple": (128, 0, 128),
+    "fuchsia": (255, 0, 255),
     "green": (0, 128, 0),
-    "yellow": (128, 128, 0),
-    "blue": (0, 0, 128),
-    "magenta": (128, 0, 128),
-    "cyan": (0, 128, 128),
-    "white": (192, 192, 192),
-    "bright_black": (128, 128, 128),
-    "bright_red": (255, 0, 0),
-    "bright_green": (0, 255, 0),
-    "bright_yellow": (255, 255, 0),
-    "bright_blue": (0, 0, 255),
-    "bright_magenta": (255, 0, 255),
-    "bright_cyan": (0, 255, 255),
-    "bright_white": (255, 255, 255),
-    "grey0": (0, 0, 0),
-    "gray0": (0, 0, 0),
-    "navy_blue": (0, 0, 95),
-    "dark_blue": (0, 0, 135),
-    "blue3": (0, 0, 215),
-    "blue1": (0, 0, 255),
-    "dark_green": (0, 95, 0),
-    "deep_sky_blue4": (0, 95, 175),
-    "dodger_blue3": (0, 95, 215),
-    "dodger_blue2": (0, 95, 255),
-    "green4": (0, 135, 0),
-    "spring_green4": (0, 135, 95),
-    "turquoise4": (0, 135, 135),
-    "deep_sky_blue3": (0, 135, 215),
-    "dodger_blue1": (0, 135, 255),
-    "green3": (0, 215, 0),
-    "spring_green3": (0, 215, 95),
-    "dark_cyan": (0, 175, 135),
-    "light_sea_green": (0, 175, 175),
-    "deep_sky_blue2": (0, 175, 215),
-    "deep_sky_blue1": (0, 175, 255),
-    "spring_green2": (0, 255, 95),
-    "cyan3": (0, 215, 175),
-    "dark_turquoise": (0, 215, 215),
-    "turquoise2": (0, 215, 255),
-    "green1": (0, 255, 0),
-    "spring_green1": (0, 255, 135),
-    "medium_spring_green": (0, 255, 175),
-    "cyan2": (0, 255, 215),
-    "cyan1": (0, 255, 255),
-    "dark_red": (135, 0, 0),
-    "deep_pink4": (175, 0, 95),
-    "purple4": (95, 0, 175),
-    "purple3": (95, 0, 215),
-    "blue_violet": (95, 0, 255),
-    "orange4": (135, 95, 0),
-    "grey37": (95, 95, 95),
-    "gray37": (95, 95, 95),
-    "medium_purple4": (95, 95, 135),
-    "slate_blue3": (95, 95, 215),
-    "royal_blue1": (95, 95, 255),
-    "chartreuse4": (95, 135, 0),
-    "dark_sea_green4": (95, 175, 95),
-    "pale_turquoise4": (95, 135, 135),
-    "steel_blue": (95, 135, 175),
-    "steel_blue3": (95, 135, 215),
-    "cornflower_blue": (95, 135, 255),
-    "chartreuse3": (95, 215, 0),
-    "cadet_blue": (95, 175, 175),
-    "sky_blue3": (95, 175, 215),
-    "steel_blue1": (95, 215, 255),
-    "pale_green3": (135, 215, 135),
-    "sea_green3": (95, 215, 135),
-    "aquamarine3": (95, 215, 175),
-    "medium_turquoise": (95, 215, 215),
-    "chartreuse2": (135, 215, 0),
-    "sea_green2": (95, 255, 95),
-    "sea_green1": (95, 255, 175),
-    "aquamarine1": (135, 255, 215),
-    "dark_slate_gray2": (95, 255, 255),
-    "dark_magenta": (135, 0, 175),
-    "dark_violet": (175, 0, 215),
-    "purple": (175, 0, 255),
-    "light_pink4": (135, 95, 95),
-    "plum4": (135, 95, 135),
-    "medium_purple3": (135, 95, 215),
-    "slate_blue1": (135, 95, 255),
-    "yellow4": (135, 175, 0),
-    "wheat4": (135, 135, 95),
-    "grey53": (135, 135, 135),
-    "gray53": (135, 135, 135),
-    "light_slate_grey": (135, 135, 175),
-    "light_slate_gray": (135, 135, 175),
-    "medium_purple": (135, 135, 215),
-    "light_slate_blue": (135, 135, 255),
-    "dark_olive_green3": (175, 215, 95),
-    "dark_sea_green": (135, 175, 135),
-    "light_sky_blue3": (135, 175, 215),
-    "sky_blue2": (135, 175, 255),
-    "dark_sea_green3": (175, 215, 135),
-    "dark_slate_gray3": (135, 215, 215),
-    "sky_blue1": (135, 215, 255),
-    "chartreuse1": (135, 255, 0),
-    "light_green": (135, 255, 135),
-    "pale_green1": (175, 255, 135),
-    "dark_slate_gray1": (135, 255, 255),
-    "red3": (215, 0, 0),
-    "medium_violet_red": (175, 0, 135),
-    "magenta3": (215, 0, 215),
-    "dark_orange3": (215, 95, 0),
-    "indian_red": (215, 95, 95),
-    "hot_pink3": (215, 95, 135),
-    "medium_orchid3": (175, 95, 175),
-    "medium_orchid": (175, 95, 215),
-    "medium_purple2": (175, 135, 215),
-    "dark_goldenrod": (175, 135, 0),
-    "light_salmon3": (215, 135, 95),
-    "rosy_brown": (175, 135, 135),
-    "grey63": (175, 135, 175),
-    "gray63": (175, 135, 175),
-    "medium_purple1": (175, 135, 255),
-    "gold3": (215, 175, 0),
-    "dark_khaki": (175, 175, 95),
-    "navajo_white3": (175, 175, 135),
-    "grey69": (175, 175, 175),
-    "gray69": (175, 175, 175),
-    "light_steel_blue3": (175, 175, 215),
-    "light_steel_blue": (175, 175, 255),
-    "yellow3": (215, 215, 0),
-    "dark_sea_green2": (175, 255, 175),
-    "light_cyan3": (175, 215, 215),
-    "light_sky_blue1": (175, 215, 255),
-    "green_yellow": (175, 255, 0),
-    "dark_olive_green2": (175, 255, 95),
-    "dark_sea_green1": (215, 255, 175),
-    "pale_turquoise1": (175, 255, 255),
-    "deep_pink3": (215, 0, 135),
-    "magenta2": (255, 0, 215),
-    "hot_pink2": (215, 95, 175),
-    "orchid": (215, 95, 215),
-    "medium_orchid1": (255, 95, 255),
-    "orange3": (215, 135, 0),
-    "light_pink3": (215, 135, 135),
-    "pink3": (215, 135, 175),
-    "plum3": (215, 135, 215),
-    "violet": (215, 135, 255),
-    "light_goldenrod3": (215, 175, 95),
-    "tan": (215, 175, 135),
-    "misty_rose3": (215, 175, 175),
-    "thistle3": (215, 175, 215),
-    "plum2": (215, 175, 255),
-    "khaki3": (215, 215, 95),
-    "light_goldenrod2": (255, 215, 135),
-    "light_yellow3": (215, 215, 175),
-    "grey84": (215, 215, 215),
-    "gray84": (215, 215, 215),
-    "light_steel_blue1": (215, 215, 255),
-    "yellow2": (215, 255, 0),
-    "dark_olive_green1": (215, 255, 135),
-    "honeydew2": (215, 255, 215),
-    "light_cyan1": (215, 255, 255),
-    "red1": (255, 0, 0),
-    "deep_pink2": (255, 0, 95),
-    "deep_pink1": (255, 0, 175),
-    "magenta1": (255, 0, 255),
-    "orange_red1": (255, 95, 0),
-    "indian_red1": (255, 95, 135),
-    "hot_pink": (255, 95, 215),
-    "dark_orange": (255, 135, 0),
-    "salmon1": (255, 135, 95),
-    "light_coral": (255, 135, 135),
-    "pale_violet_red1": (255, 135, 175),
-    "orchid2": (255, 135, 215),
-    "orchid1": (255, 135, 255),
-    "orange1": (255, 175, 0),
-    "sandy_brown": (255, 175, 95),
-    "light_salmon1": (255, 175, 135),
-    "light_pink1": (255, 175, 175),
-    "pink1": (255, 175, 215),
-    "plum1": (255, 175, 255),
-    "gold1": (255, 215, 0),
-    "navajo_white1": (255, 215, 175),
-    "misty_rose1": (255, 215, 215),
-    "thistle1": (255, 215, 255),
-    "yellow1": (255, 255, 0),
-    "light_goldenrod1": (255, 255, 95),
-    "khaki1": (255, 255, 135),
-    "wheat1": (255, 255, 175),
-    "cornsilk1": (255, 255, 215),
-    "grey100": (255, 255, 255),
-    "gray100": (255, 255, 255),
-    "grey3": (8, 8, 8),
-    "gray3": (8, 8, 8),
-    "grey7": (18, 18, 18),
-    "gray7": (18, 18, 18),
-    "grey11": (28, 28, 28),
-    "gray11": (28, 28, 28),
-    "grey15": (38, 38, 38),
-    "gray15": (38, 38, 38),
-    "grey19": (48, 48, 48),
-    "gray19": (48, 48, 48),
-    "grey23": (58, 58, 58),
-    "gray23": (58, 58, 58),
-    "grey27": (68, 68, 68),
-    "gray27": (68, 68, 68),
-    "grey30": (78, 78, 78),
-    "gray30": (78, 78, 78),
-    "grey35": (88, 88, 88),
-    "gray35": (88, 88, 88),
-    "grey39": (98, 98, 98),
-    "gray39": (98, 98, 98),
-    "grey42": (108, 108, 108),
-    "gray42": (108, 108, 108),
-    "grey46": (118, 118, 118),
-    "gray46": (118, 118, 118),
-    "grey50": (128, 128, 128),
-    "gray50": (128, 128, 128),
-    "grey54": (138, 138, 138),
-    "gray54": (138, 138, 138),
-    "grey58": (148, 148, 148),
-    "gray58": (148, 148, 148),
-    "grey62": (158, 158, 158),
-    "gray62": (158, 158, 158),
-    "grey66": (168, 168, 168),
-    "gray66": (168, 168, 168),
-    "grey70": (178, 178, 178),
-    "gray70": (178, 178, 178),
-    "grey74": (188, 188, 188),
-    "gray74": (188, 188, 188),
-    "grey78": (198, 198, 198),
-    "gray78": (198, 198, 198),
-    "grey82": (208, 208, 208),
-    "gray82": (208, 208, 208),
-    "grey85": (218, 218, 218),
-    "gray85": (218, 218, 218),
-    "grey89": (228, 228, 228),
-    "gray89": (228, 228, 228),
-    "grey93": (238, 238, 238),
-    "gray93": (238, 238, 238),
+    "lime": (0, 255, 0),
+    "olive": (128, 128, 0),
+    "yellow": (255, 255, 0),
+    "navy": (0, 0, 128),
+    "blue": (0, 0, 255),
+    "teal": (0, 128, 128),
+    "aqua": (0, 255, 255),
+    "orange": (255, 165, 0),
+    "aliceblue": (240, 248, 255),
+    "antiquewhite": (250, 235, 215),
+    "aquamarine": (127, 255, 212),
+    "azure": (240, 255, 255),
+    "beige": (245, 245, 220),
+    "bisque": (255, 228, 196),
+    "blanchedalmond": (255, 235, 205),
+    "blueviolet": (138, 43, 226),
+    "brown": (165, 42, 42),
+    "burlywood": (222, 184, 135),
+    "cadetblue": (95, 158, 160),
+    "chartreuse": (127, 255, 0),
+    "chocolate": (210, 105, 30),
+    "coral": (255, 127, 80),
+    "cornflowerblue": (100, 149, 237),
+    "cornsilk": (255, 248, 220),
+    "crimson": (220, 20, 60),
+    "cyan": (0, 255, 255),
+    "darkblue": (0, 0, 139),
+    "darkcyan": (0, 139, 139),
+    "darkgoldenrod": (184, 134, 11),
+    "darkgray": (169, 169, 169),
+    "darkgreen": (0, 100, 0),
+    "darkgrey": (169, 169, 169),
+    "darkkhaki": (189, 183, 107),
+    "darkmagenta": (139, 0, 139),
+    "darkolivegreen": (85, 107, 47),
+    "darkorange": (255, 140, 0),
+    "darkorchid": (153, 50, 204),
+    "darkred": (139, 0, 0),
+    "darksalmon": (233, 150, 122),
+    "darkseagreen": (143, 188, 143),
+    "darkslateblue": (72, 61, 139),
+    "darkslategray": (47, 79, 79),
+    "darkslategrey": (47, 79, 79),
+    "darkturquoise": (0, 206, 209),
+    "darkviolet": (148, 0, 211),
+    "deeppink": (255, 20, 147),
+    "deepskyblue": (0, 191, 255),
+    "dimgray": (105, 105, 105),
+    "dimgrey": (105, 105, 105),
+    "dodgerblue": (30, 144, 255),
+    "firebrick": (178, 34, 34),
+    "floralwhite": (255, 250, 240),
+    "forestgreen": (34, 139, 34),
+    "gainsboro": (220, 220, 220),
+    "ghostwhite": (248, 248, 255),
+    "gold": (255, 215, 0),
+    "goldenrod": (218, 165, 32),
+    "greenyellow": (173, 255, 47),
+    "grey": (128, 128, 128),
+    "honeydew": (240, 255, 240),
+    "hotpink": (255, 105, 180),
+    "indianred": (205, 92, 92),
+    "indigo": (75, 0, 130),
+    "ivory": (255, 255, 240),
+    "khaki": (240, 230, 140),
+    "lavender": (230, 230, 250),
+    "lavenderblush": (255, 240, 245),
+    "lawngreen": (124, 252, 0),
+    "lemonchiffon": (255, 250, 205),
+    "lightblue": (173, 216, 230),
+    "lightcoral": (240, 128, 128),
+    "lightcyan": (224, 255, 255),
+    "lightgoldenrodyellow": (250, 250, 210),
+    "lightgray": (211, 211, 211),
+    "lightgreen": (144, 238, 144),
+    "lightgrey": (211, 211, 211),
+    "lightpink": (255, 182, 193),
+    "lightsalmon": (255, 160, 122),
+    "lightseagreen": (32, 178, 170),
+    "lightskyblue": (135, 206, 250),
+    "lightslategray": (119, 136, 153),
+    "lightslategrey": (119, 136, 153),
+    "lightsteelblue": (176, 196, 222),
+    "lightyellow": (255, 255, 224),
+    "limegreen": (50, 205, 50),
+    "linen": (250, 240, 230),
+    "magenta": (255, 0, 255),
+    "mediumaquamarine": (102, 205, 170),
+    "mediumblue": (0, 0, 205),
+    "mediumorchid": (186, 85, 211),
+    "mediumpurple": (147, 112, 219),
+    "mediumseagreen": (60, 179, 113),
+    "mediumslateblue": (123, 104, 238),
+    "mediumspringgreen": (0, 250, 154),
+    "mediumturquoise": (72, 209, 204),
+    "mediumvioletred": (199, 21, 133),
+    "midnightblue": (25, 25, 112),
+    "mintcream": (245, 255, 250),
+    "mistyrose": (255, 228, 225),
+    "moccasin": (255, 228, 181),
+    "navajowhite": (255, 222, 173),
+    "oldlace": (253, 245, 230),
+    "olivedrab": (107, 142, 35),
+    "orangered": (255, 69, 0),
+    "orchid": (218, 112, 214),
+    "palegoldenrod": (238, 232, 170),
+    "palegreen": (152, 251, 152),
+    "paleturquoise": (175, 238, 238),
+    "palevioletred": (219, 112, 147),
+    "papayawhip": (255, 239, 213),
+    "peachpuff": (255, 218, 185),
+    "peru": (205, 133, 63),
+    "pink": (255, 192, 203),
+    "plum": (221, 160, 221),
+    "powderblue": (176, 224, 230),
+    "rosybrown": (188, 143, 143),
+    "royalblue": (65, 105, 225),
+    "saddlebrown": (139, 69, 19),
+    "salmon": (250, 128, 114),
+    "sandybrown": (244, 164, 96),
+    "seagreen": (46, 139, 87),
+    "seashell": (255, 245, 238),
+    "sienna": (160, 82, 45),
+    "skyblue": (135, 206, 235),
+    "slateblue": (106, 90, 205),
+    "slategray": (112, 128, 144),
+    "slategrey": (112, 128, 144),
+    "snow": (255, 250, 250),
+    "springgreen": (0, 255, 127),
+    "steelblue": (70, 130, 180),
+    "tan": (210, 180, 140),
+    "thistle": (216, 191, 216),
+    "tomato": (255, 99, 71),
+    "turquoise": (64, 224, 208),
+    "violet": (238, 130, 238),
+    "wheat": (245, 222, 179),
+    "whitesmoke": (245, 245, 245),
+    "yellowgreen": (154, 205, 50),
+    "rebeccapurple": (102, 51, 153),
 }
diff --git a/src/textual/color.py b/src/textual/color.py
index 56fa3bc46..f6e5be1c3 100644
--- a/src/textual/color.py
+++ b/src/textual/color.py
@@ -258,11 +258,9 @@ class Color(NamedTuple):
         """
         if isinstance(color_text, Color):
             return color_text
-        if color_text == "transparent":
-            return TRANSPARENT
-        ansi_color = COLOR_NAME_TO_RGB.get(color_text)
-        if ansi_color is not None:
-            return cls(*ansi_color)
+        color_from_name = COLOR_NAME_TO_RGB.get(color_text)
+        if color_from_name is not None:
+            return cls(*color_from_name)
         color_match = RE_COLOR.match(color_text)
         if color_match is None:
             raise ColorParseError(f"failed to parse {color_text!r} as a color")
diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py
index 430ea79c9..8c4959648 100644
--- a/src/textual/css/styles.py
+++ b/src/textual/css/styles.py
@@ -214,9 +214,9 @@ class StylesBase(ABC):
 
     rich_style = StyleProperty()
 
-    scrollbar_color = ColorProperty("bright_magenta")
-    scrollbar_color_hover = ColorProperty("yellow")
-    scrollbar_color_active = ColorProperty("bright_yellow")
+    scrollbar_color = ColorProperty("ansi_bright_magenta")
+    scrollbar_color_hover = ColorProperty("ansi_yellow")
+    scrollbar_color_active = ColorProperty("ansi_bright_yellow")
 
     scrollbar_background = ColorProperty("#555555")
     scrollbar_background_hover = ColorProperty("#444444")
diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py
index 5633f0d0e..62f05185c 100644
--- a/src/textual/scrollbar.py
+++ b/src/textual/scrollbar.py
@@ -69,7 +69,7 @@ class ScrollBarRender:
         position: float = 0,
         thickness: int = 1,
         vertical: bool = True,
-        style: StyleType = "bright_magenta on #555555",
+        style: StyleType = "ansi_bright_magenta on #555555",
     ) -> None:
         self.virtual_size = virtual_size
         self.window_size = window_size
@@ -89,7 +89,7 @@ class ScrollBarRender:
         thickness: int = 1,
         vertical: bool = True,
         back_color: Color = Color.parse("#555555"),
-        bar_color: Color = Color.parse("bright_magenta"),
+        bar_color: Color = Color.parse("ansi_bright_magenta"),
     ) -> Segments:
 
         if vertical:
@@ -181,7 +181,7 @@ class ScrollBarRender:
             vertical=self.vertical,
             thickness=thickness,
             back_color=_style.bgcolor or Color.parse("#555555"),
-            bar_color=_style.color or Color.parse("bright_magenta"),
+            bar_color=_style.color or Color.parse("ansi_bright_magenta"),
         )
         yield bar
 


Test patch:
diff --git a/tests/css/test_stylesheet.py b/tests/css/test_stylesheet.py
index 718446a43..4544d1e1d 100644
--- a/tests/css/test_stylesheet.py
+++ b/tests/css/test_stylesheet.py
@@ -10,18 +10,23 @@ from textual.css.tokenizer import TokenizeError
     "css_value,expectation,expected_color",
     [
         # Valid values:
-        ["red", does_not_raise(), Color(128, 0, 0)],
-        ["dark_cyan", does_not_raise(), Color(0, 175, 135)],
-        ["medium_turquoise", does_not_raise(), Color(95, 215, 215)],
-        ["turquoise4", does_not_raise(), Color(0, 135, 135)],
+        ["transparent", does_not_raise(), Color(0, 0, 0, 0)],
+        ["ansi_red", does_not_raise(), Color(128, 0, 0)],
+        ["ansi_bright_magenta", does_not_raise(), Color(255, 0, 255)],
+        ["red", does_not_raise(), Color(255, 0, 0)],
+        ["lime", does_not_raise(), Color(0, 255, 0)],
+        ["coral", does_not_raise(), Color(255, 127, 80)],
+        ["aqua", does_not_raise(), Color(0, 255, 255)],
+        ["deepskyblue", does_not_raise(), Color(0, 191, 255)],
+        ["rebeccapurple", does_not_raise(), Color(102, 51, 153)],
         ["#ffcc00", does_not_raise(), Color(255, 204, 0)],
         ["#ffcc0033", does_not_raise(), Color(255, 204, 0, 0.2)],
         ["rgb(200,90,30)", does_not_raise(), Color(200, 90, 30)],
         ["rgba(200,90,30,0.3)", does_not_raise(), Color(200, 90, 30, 0.3)],
         # Some invalid ones:
         ["coffee", pytest.raises(StylesheetParseError), None],  # invalid color name
-        ["turquoise10", pytest.raises(StylesheetParseError), None],
-        ["turquoise 4", pytest.raises(StylesheetParseError), None],  # space in it
+        ["ansi_dark_cyan", pytest.raises(StylesheetParseError), None],
+        ["red 4", pytest.raises(StylesheetParseError), None],  # space in it
         ["1", pytest.raises(StylesheetParseError), None],  # invalid value
         ["()", pytest.raises(TokenizeError), None],  # invalid tokens
         # TODO: implement hex colors with 3 chars? @link https://devdocs.io/css/color_value