[v7] Add an option with a color type.

Message ID 20240914190452.423367-1-gdb@mail.api.win
State New
Headers
Series [v7] Add an option with a color type. |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 fail Test failed
linaro-tcwg-bot/tcwg_gdb_check--master-arm fail Patch failed to apply

Commit Message

Andrei Pikas Sept. 14, 2024, 7:04 p.m. UTC
  Colors can be specified as "none" for terminal's default color, as a name of
one of the eight standard colors of ISO/IEC 6429 "black", "red", "green", etc.,
as an RGB hexadecimal tripplet #RRGGBB for 24-bit TrueColor, or as an
integer from 0 to 255.  Integers 0 to 7 are the synonyms for the standard
colors.  Integers 8-15 are used for the so-called bright colors from the
aixterm extended 16-color palette.  Integers 16-255 are the indexes into xterm
extended 256-color palette (usually 6x6x6 cube plus gray ramp).  In
general, 256-color palette is terminal dependent and sometimes can be
changed with OSC 4 sequences, e.g. "\033]4;1;rgb:00/FF/00\033\\".

It is the responsibility of the user to verify that the terminal supports
the specified colors.

PATCH v5 changes: documentation fixed.
PATCH v6 changes: documentation fixed.
PATCH v7 changes: rebase onto master and fixes after review.
---
 gdb/Makefile.in                           |   2 +
 gdb/NEWS                                  |  29 ++
 gdb/cli/cli-cmds.c                        |   7 +
 gdb/cli/cli-decode.c                      | 174 +++++++++
 gdb/cli/cli-decode.h                      |  21 +
 gdb/cli/cli-option.c                      |  44 +++
 gdb/cli/cli-option.h                      |  21 +
 gdb/cli/cli-setshow.c                     |  21 +
 gdb/cli/cli-style.c                       |  49 +--
 gdb/cli/cli-style.h                       |   4 +-
 gdb/command.h                             |  26 +-
 gdb/doc/gdb.texinfo                       |  38 +-
 gdb/doc/guile.texi                        | 104 +++++
 gdb/doc/python.texi                       |  97 +++++
 gdb/guile/guile-internal.h                |   9 +
 gdb/guile/guile.c                         |   1 +
 gdb/guile/scm-color.c                     | 443 ++++++++++++++++++++++
 gdb/guile/scm-param.c                     |  33 +-
 gdb/python/py-color.c                     | 347 +++++++++++++++++
 gdb/python/py-color.h                     |  35 ++
 gdb/python/py-param.c                     |  38 +-
 gdb/python/python.c                       |   7 +
 gdb/testsuite/gdb.base/style.exp          | 197 ++++++++++
 gdb/testsuite/gdb.guile/scm-color.exp     | 110 ++++++
 gdb/testsuite/gdb.guile/scm-parameter.exp |  47 +++
 gdb/testsuite/gdb.python/py-color.exp     | 100 +++++
 gdb/testsuite/gdb.python/py-parameter.exp |  53 +++
 gdb/testsuite/lib/gdb-utils.exp           |  22 +-
 gdb/testsuite/lib/gdb.exp                 |   3 +-
 gdb/top.c                                 |  14 +
 gdb/ui-style.c                            | 331 ++++++++++++----
 gdb/ui-style.h                            | 142 ++++++-
 gdb/unittests/style-selftests.c           |   6 +-
 33 files changed, 2414 insertions(+), 161 deletions(-)
 create mode 100644 gdb/guile/scm-color.c
 create mode 100644 gdb/python/py-color.c
 create mode 100644 gdb/python/py-color.h
 create mode 100644 gdb/testsuite/gdb.guile/scm-color.exp
 create mode 100644 gdb/testsuite/gdb.python/py-color.exp
  

Comments

Eli Zaretskii Sept. 15, 2024, 5:37 a.m. UTC | #1
> From: Andrei Pikas <gdb@mail.api.win>
> Cc: gdb-patches@sourceware.org,
> 	Andrei Pikas <gdb@mail.api.win>
> Date: Sat, 14 Sep 2024 22:04:52 +0300
> 
> Colors can be specified as "none" for terminal's default color, as a name of
> one of the eight standard colors of ISO/IEC 6429 "black", "red", "green", etc.,
> as an RGB hexadecimal tripplet #RRGGBB for 24-bit TrueColor, or as an
> integer from 0 to 255.  Integers 0 to 7 are the synonyms for the standard
> colors.  Integers 8-15 are used for the so-called bright colors from the
> aixterm extended 16-color palette.  Integers 16-255 are the indexes into xterm
> extended 256-color palette (usually 6x6x6 cube plus gray ramp).  In
> general, 256-color palette is terminal dependent and sometimes can be
> changed with OSC 4 sequences, e.g. "\033]4;1;rgb:00/FF/00\033\\".
> 
> It is the responsibility of the user to verify that the terminal supports
> the specified colors.
> 
> PATCH v5 changes: documentation fixed.
> PATCH v6 changes: documentation fixed.
> PATCH v7 changes: rebase onto master and fixes after review.
> ---
>  gdb/Makefile.in                           |   2 +
>  gdb/NEWS                                  |  29 ++
>  gdb/cli/cli-cmds.c                        |   7 +
>  gdb/cli/cli-decode.c                      | 174 +++++++++
>  gdb/cli/cli-decode.h                      |  21 +
>  gdb/cli/cli-option.c                      |  44 +++
>  gdb/cli/cli-option.h                      |  21 +
>  gdb/cli/cli-setshow.c                     |  21 +
>  gdb/cli/cli-style.c                       |  49 +--
>  gdb/cli/cli-style.h                       |   4 +-
>  gdb/command.h                             |  26 +-
>  gdb/doc/gdb.texinfo                       |  38 +-
>  gdb/doc/guile.texi                        | 104 +++++
>  gdb/doc/python.texi                       |  97 +++++
>  gdb/guile/guile-internal.h                |   9 +
>  gdb/guile/guile.c                         |   1 +
>  gdb/guile/scm-color.c                     | 443 ++++++++++++++++++++++
>  gdb/guile/scm-param.c                     |  33 +-
>  gdb/python/py-color.c                     | 347 +++++++++++++++++
>  gdb/python/py-color.h                     |  35 ++
>  gdb/python/py-param.c                     |  38 +-
>  gdb/python/python.c                       |   7 +
>  gdb/testsuite/gdb.base/style.exp          | 197 ++++++++++
>  gdb/testsuite/gdb.guile/scm-color.exp     | 110 ++++++
>  gdb/testsuite/gdb.guile/scm-parameter.exp |  47 +++
>  gdb/testsuite/gdb.python/py-color.exp     | 100 +++++
>  gdb/testsuite/gdb.python/py-parameter.exp |  53 +++
>  gdb/testsuite/lib/gdb-utils.exp           |  22 +-
>  gdb/testsuite/lib/gdb.exp                 |   3 +-
>  gdb/top.c                                 |  14 +
>  gdb/ui-style.c                            | 331 ++++++++++++----
>  gdb/ui-style.h                            | 142 ++++++-
>  gdb/unittests/style-selftests.c           |   6 +-
>  33 files changed, 2414 insertions(+), 161 deletions(-)
>  create mode 100644 gdb/guile/scm-color.c
>  create mode 100644 gdb/python/py-color.c
>  create mode 100644 gdb/python/py-color.h
>  create mode 100644 gdb/testsuite/gdb.guile/scm-color.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-color.exp

Thanks.

> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -37,11 +37,40 @@
>    history has been reached.  It also specifies that the forward execution can
>    continue, and the recording will also continue.
>  
> +* "set style" commands now supports numeric format for basic colors
> +  from 0 to 255 and #RRGGBB format for TrueColor.
> +
> +* New built-in convenience variable $_colorsupport provides comma-separated
> +  list of color space names supported by terminal.  It is handy for
> +  conditionally using styling colors based on terminal features.  For example:
> +
> +  (gdb) if $_regex ($_colorsupport, ".*(^|,)rgb_24bit($|,).*")
> +   >set style filename background #FACADE
> +   >else
> +   >if $_regex ($_colorsupport, ".*(^|,)xterm_256color($|,).*")
> +    >set style filename background 224
> +    >else
> +    >set style filename background red
> +    >end
> +   >end
> +

I'd prefer to have a plain-English explanation of "color space" here,
instead of trying to explain it via script which uses the variable.

> --- a/gdb/cli/cli-style.c
> +++ b/gdb/cli/cli-style.c
> @@ -42,20 +42,6 @@ bool source_styling = true;
>  
>  bool disassembler_styling = true;
>  
> -/* Name of colors; must correspond to ui_file_style::basic_color.  */
> -static const char * const cli_colors[] = {
> -  "none",
> -  "black",
> -  "red",
> -  "green",
> -  "yellow",
> -  "blue",
> -  "magenta",
> -  "cyan",
> -  "white",
> -  nullptr
> -};

I don't understand the effect of this and related changes, and the log
message says nothing about this.  Could you please explain why you
remove these names and their uses, and what will be used in their
stead?  And why?

Regardless, were these changes tested in the MinGW port of GDB?  It
emulates Posix terminal handling of colors via SGR escape sequences,
and I wonder whether these changes might somehow break styling support
in the MinGW port.

> +@item $_colorsupport
> +@vindex $_colorsupport@r{, convenience variable}
> +Comma-separated list of color space names supported by terminal.  Names could
> +be any of @samp{monochrome}, @samp{ansi_8color}, @samp{aixterm_16color},
> +@samp{xterm_256color}, @samp{rgb_24bit}.  E.g., for plain linux terminal the
> +value could be @samp{monochrome,ansi_8color} and for terminal with truecolor
> +support it could be
> +@samp{monochrome,ansi_8color,aixterm_16color,xterm_256color,rgb_24bit}.

What does this return for the MS-Windows terminal? aixterm_16color?
IOW, will any 16-color terminal return aixterm_16color?  I think this
should be documented, and perhaps we should remove the "aix" part from
the name (since it is not necessarily specific to AIX).

Also, please add a "@cindex color space" entry before the above text,
as I believe this is where we describe this term.  For the same
reason, I think the first use of "color spaces" should have the @dfn
markup.

> -Set the background to @var{color}.  Valid colors are @samp{none}
> -(meaning the terminal's default color), @samp{black}, @samp{red},
> -@samp{green}, @samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan},
> -and@samp{white}.
> +Set the background to @var{color}.  @var{color} can be @samp{none}
> +(meaning the terminal's default color), a name of one of the eight standard
> +colors of ISO/IEC 6429, index from 0 to 255 into terminal's color
> +palette or a hexadecimal RGB triplet in @samp{#RRGGBB} format for
> +24-bit TrueColor.

Should the RGB specs always use 2 hex characters per component,
or can one use RRRGGGBBB etc. as well?  This should be documented.
  
Tom Tromey Oct. 4, 2024, 5:55 p.m. UTC | #2
>>>>> "Andrei" == Andrei Pikas <gdb@mail.api.win> writes:

Andrei> Colors can be specified as "none" for terminal's default color, as a name of
Andrei> one of the eight standard colors of ISO/IEC 6429 "black", "red", "green", etc.,
Andrei> as an RGB hexadecimal tripplet #RRGGBB for 24-bit TrueColor, or as an
Andrei> integer from 0 to 255.  Integers 0 to 7 are the synonyms for the standard
Andrei> colors.  Integers 8-15 are used for the so-called bright colors from the
Andrei> aixterm extended 16-color palette.  Integers 16-255 are the indexes into xterm
Andrei> extended 256-color palette (usually 6x6x6 cube plus gray ramp).  In
Andrei> general, 256-color palette is terminal dependent and sometimes can be
Andrei> changed with OSC 4 sequences, e.g. "\033]4;1;rgb:00/FF/00\033\\".

Thank you for the patch.  I'm sorry about the long delays on this; gdb
has an ongoing review bandwidth problem.  Anyway, I like this a lot and
I would like to see it get in.

I have a fair number of notes here but nothing really major.

Andrei> +    case var_color:
Andrei> +      {
Andrei> +	std::string s = var.get<ui_file_style::color> ().to_string ();
Andrei> +	return value_cstring (s.c_str (), s.size (),
Andrei> +			      builtin_type (gdbarch)->builtin_char);

Other code in this function uses:

	return current_language->value_string (gdbarch, value, len);

Andrei> +  if (*text == '\0')
Andrei> +    {
Andrei> +      /* Convenience to let the user know what the option
Andrei> +	 can accept.  Note there's no common prefix between
Andrei> +	 the strings on purpose, so that complete_on_enum doesn't do
Andrei> +	 a partial match.  */
Andrei> +      tracker.add_completion (make_unique_xstrdup ("NUMBER"));
Andrei> +      tracker.add_completion (make_unique_xstrdup ("#RRGGBB"));
Andrei> +    }

This is a clever idea and I somewhat wish readline had a more robust
completion API to show this kind of info "inline".

Andrei> +ui_file_style::color
Andrei> +parse_cli_var_color (const char **args)
Andrei> +{
Andrei> +  /* Do a "set" command.  ARG is nullptr if no argument, or the
Andrei> +     text of the argument.  */
Andrei> +
Andrei> +  if (args == nullptr || *args == nullptr || **args == '\0')
Andrei> +    {
Andrei> +      std::string msg;
Andrei> +
Andrei> +      for (size_t i = 0; ui_file_style::basic_color_enums[i]; ++i)
Andrei> +	{
Andrei> +	  msg.append ("\"");
Andrei> +	  msg.append (ui_file_style::basic_color_enums[i]);
Andrei> +	  msg.append ("\", ");

I think this ends up appending an extra comma.  Perhaps the
comma-addition should be done at the star of the loop, if "i > 0".

Andrei> diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c
Andrei> index 05539285c80..3a165ef6d2c 100644
Andrei> --- a/gdb/cli/cli-option.c
Andrei> +++ b/gdb/cli/cli-option.c
Andrei> @@ -45,6 +45,9 @@ union option_value
 
Andrei>    /* For var_string options.  This is malloc-allocated.  */
Andrei>    std::string *string;
Andrei> +
Andrei> +  /* For var_color options.  */
Andrei> +  ui_file_style::color color = ui_file_style::NONE;

I didn't even know inline initializers were possible in a union.
Anyway I think it's probably better to leave this off.

Andrei> +@node Colors In Python
Andrei> +@subsection Python representation of colors

This new node doesn't seem to be referenced from a menu anywhere.  I
think it has to be added to the appropriate spot in the menu in the
"Python API" node.

Andrei> diff --git a/gdb/python/py-color.c b/gdb/python/py-color.c
Andrei> new file mode 100644
Andrei> index 00000000000..1d72cc3e7d1
Andrei> --- /dev/null
Andrei> +++ b/gdb/python/py-color.c
Andrei> @@ -0,0 +1,347 @@
Andrei> +/* Python interface to ui_file_style::color objects.
Andrei> +
Andrei> +   Copyright (C) 2008-2022 Free Software Foundation, Inc.

I suspect this should just be 2024.

Andrei> +/* See py-color.h.  */
Andrei> +bool
Andrei> +gdbpy_is_color (PyObject *obj)
Andrei> +{
Andrei> +  return PyObject_IsInstance (obj, (PyObject *)&colorpy_object_type);

gdb style is a space after the closing paren of a cast.

Andrei> +static PyObject *
Andrei> +colorpy_escape_sequence (PyObject *self, PyObject *is_fg_obj)
Andrei> +{
...
Andrei> +  bool is_fg = PyObject_IsTrue (is_fg_obj);

In theory, this can fail, returning -1.

In practice, the code already calls PyBool_Check, so I think just a
direct comparison against Py_True would be fine.

Andrei> +static int
Andrei> +colorpy_init (PyObject *self, PyObject *args, PyObject *kwds)
...
Andrei> +      if (colorspace_obj)
Andrei> +	{
Andrei> +	  if (PyLong_Check (colorspace_obj))
Andrei> +	    {
Andrei> +	      long colorspace_id = -1;
Andrei> +	      if (! gdb_py_int_as_long (colorspace_obj, &colorspace_id))
Andrei> +		return -1;
Andrei> +	      if (colorspace_id < INT_MIN || colorspace_id > INT_MAX)
Andrei> +		error (_("colorspace %ld is out of range."), colorspace_id);

It seems like the range check here should use the colorspace enum values.

Andrei> +	  Py_ssize_t tuple_size = PyTuple_Size (value_obj);
Andrei> +	  if (tuple_size != 3)
Andrei> +	    error (_("Tuple value with RGB must be of size 3."));

If PyTuple_Size returns -1, this should early-return.

Andrei> +	  if (!str)
Andrei> +	    return -1;

gdb uses comparisons like 'str == nullptr' here.

Andrei> +	  if (colorspace_obj != nullptr
Andrei> +	      && colorspace != obj->color.colorspace ())
Andrei> +	    error (_("colorspace doesn't match to value."));

I would s/to/the in this error message.

Andrei> +  catch (const gdb_exception &except)
Andrei> +    {
Andrei> +      gdbpy_convert_exception (except);
Andrei> +      return -1;
Andrei> +    }

A newish change landed and now this is written

    return gdbpy_handle_exception (-1, except);

Andrei> +/* Deallocate function for a gdb.Color.  */
Andrei> +
Andrei> +static void
Andrei> +colorpy_dealloc (PyObject *)
Andrei> +{
Andrei> +}

I wonder if this is necessary.
If so maybe a comment explaining why would be good.

Andrei> +/* Initialize the 'color' module.  */
Andrei> +static int
Andrei> +gdbpy_initialize_color (void)
Andrei> +{
Andrei> +  colorpy_object_type.tp_new = PyType_GenericNew;
Andrei> +  if (PyType_Ready (&colorpy_object_type) < 0)
Andrei> +    return -1;

A recent change means that this should now call gdbpy_type_ready
instead.  This also removes the need to call gdb_pymodule_addobject, so
this call can be lowered to the end of this function.

Andrei> diff --git a/gdb/python/py-color.h b/gdb/python/py-color.h
...
Andrei> +/* Extracts value from OBJ object of gdb.Color type.  */
Andrei> +extern const ui_file_style::color & gdbpy_get_color (PyObject *obj);

No space after the "&".

Andrei> -  memset (&obj->value, 0, sizeof (obj->value));
Andrei> +  obj->value = {}; /* zeros initialization */

Was this needed?

Andrei> diff --git a/gdb/testsuite/gdb.guile/scm-color.exp b/gdb/testsuite/gdb.guile/scm-color.exp
Andrei> new file mode 100644
Andrei> index 00000000000..e5773650f2f
Andrei> --- /dev/null
Andrei> +++ b/gdb/testsuite/gdb.guile/scm-color.exp
Andrei> @@ -0,0 +1,110 @@
Andrei> +# Copyright (C) 2010-2022 Free Software Foundation, Inc.

Another date to change.  I'd suggest looking at all of them.

Andrei> diff --git a/gdb/testsuite/gdb.python/py-parameter.exp b/gdb/testsuite/gdb.python/py-parameter.exp
Andrei> index de524f49ad6..6e6a6007198 100644
Andrei> --- a/gdb/testsuite/gdb.python/py-parameter.exp
Andrei> +++ b/gdb/testsuite/gdb.python/py-parameter.exp
Andrei> @@ -185,6 +185,58 @@ proc_with_prefix test_enum_parameter { } {
Andrei>  	"Undefined item: \"three\".*" "set invalid enum parameter"
Andrei>  }
 
Andrei> +# Test an color parameter.
Andrei> +proc_with_prefix test_color_parameter { } {
Andrei> +    global env
Andrei> +    save_vars { env(TERM) env(COLORTERM) } {

This should probably use with_ansi_styling_terminal to avoid having
NO_COLOR set?

Andrei> +static void
Andrei> +init_colorsupport_var (void)

You should use "()" instead of "(void)".  The latter is a leftover
C-ism.

Andrei> +{
Andrei> +  const std::vector<color_space>& cs = colorsupport ();

Also move the "&" after the space.

Andrei> +/* See ui-style.h.  */
Andrei> +/* Must correspond to ui_file_style::basic_color.  */
Andrei> +const std::vector<const char *> ui_file_style::basic_color_enums = {
Andrei> +  "none",
Andrei> +  "black",
Andrei> +  "red",
Andrei> +  "green",
Andrei> +  "yellow",
Andrei> +  "blue",
Andrei> +  "magenta",
Andrei> +  "cyan",
Andrei> +  "white",
Andrei> +  nullptr
Andrei> +};

I guess the nullptr is needed since although it is a vector now, it's
still passed to some null-expecting API?  Like maybe completion?

I wonder if a vector can be constexpr.

Andrei>  bool
Andrei>  ui_file_style::color::append_ansi (bool is_fg, std::string *str) const
Andrei>  {
...
Andrei> +  else
Andrei> +    return false;

I wonder when this can happen and if it could somehow be made
impossible.

Andrei> +const std::vector<color_space> &
Andrei> +colorsupport ()
Andrei> +{
Andrei> +  static const std::vector<color_space> value = []

I didn't realize you could omit the '()' from a lambda.

Andrei> +    {
Andrei> +      std::vector<color_space> result = {color_space::MONOCHROME};
Andrei> +
Andrei> +      int colors = tgetnum ((char *)"Co");

Is the cast really needed?

Andrei> +      const char *colorterm = getenv ("COLORTERM");
Andrei> +      if (colorterm && (!strcmp (colorterm, "truecolor")

'colorterm != nullptr'

Andrei> +const char *
Andrei> +color_space_name (color_space c)
Andrei> +{
Andrei> +  switch (c)
Andrei> +    {
Andrei> +    case color_space::MONOCHROME: return "monochrome";
Andrei> +    case color_space::ANSI_8COLOR: return "ansi_8color";
Andrei> +    case color_space::AIXTERM_16COLOR: return "aixterm_16color";
Andrei> +    case color_space::XTERM_256COLOR: return "xterm_256color";
Andrei> +    case color_space::RGB_24BIT: return "rgb_24bit";
Andrei> +    }
Andrei> +  error (_("Color space %d has no name."), static_cast<int> (c));

Probably an assert-not-reached is more appropriate here?

Andrei> +}
Andrei> diff --git a/gdb/ui-style.h b/gdb/ui-style.h
Andrei> index 1b7b5fafb9d..a13618dfce5 100644
Andrei> --- a/gdb/ui-style.h
Andrei> +++ b/gdb/ui-style.h
Andrei> @@ -19,6 +19,26 @@
Andrei>  #ifndef UI_STYLE_H
Andrei>  #define UI_STYLE_H
 
Andrei> +/* One of the color spaces that usually supported by terminals.  */
Andrei> +enum class color_space
Andrei> +{
Andrei> +  MONOCHROME, // one default terminal color
Andrei> +  ANSI_8COLOR, // foreground colors \e[30m ... \e[37m,
Andrei> +	       // background colors \e[40m ... \e[47m
Andrei> +  AIXTERM_16COLOR, // foreground colors \e[30m ... \e[37m, \e[90m ... \e[97m
Andrei> +		   // background colors \e[40m ... \e[47m, \e[100m ... \e107m
Andrei> +  XTERM_256COLOR, // foreground colors \e[38;5;0m ... \e[38;5;255m
Andrei> +		  // background colors \e[48;5;0m ... \e[48;5;255m
Andrei> +  RGB_24BIT // foreground colors \e[38;2;0;0;0m ... \e[38;2;255;255;255m
Andrei> +	    // background colors \e[48;2;0;0;0m ... \e[48;2;255;255;255m

gdb doesn't use "//" comments.  Also normally gdb doesn't use trailing
comments on the line; instead I'd put them before each constant they
describe.

Tom
  
Andrei Pikas Oct. 5, 2024, 5:55 p.m. UTC | #3
Hello.


> Andrei> +  if (*text == '\0')
> Andrei> +    {
> Andrei> +      /* Convenience to let the user know what the option
> Andrei> +	 can accept.  Note there's no common prefix between
> Andrei> +	 the strings on purpose, so that complete_on_enum doesn't do
> Andrei> +	 a partial match.  */
> Andrei> +      tracker.add_completion (make_unique_xstrdup ("NUMBER"));
> Andrei> +      tracker.add_completion (make_unique_xstrdup ("#RRGGBB"));
> Andrei> +    }
>
> This is a clever idea and I somewhat wish readline had a more robust
> completion API to show this kind of info "inline".
I didn't understand what I have to do about this.


> Andrei> +ui_file_style::color
> Andrei> +parse_cli_var_color (const char **args)
> Andrei> +{
> Andrei> +  /* Do a "set" command.  ARG is nullptr if no argument, or the
> Andrei> +     text of the argument.  */
> Andrei> +
> Andrei> +  if (args == nullptr || *args == nullptr || **args == '\0')
> Andrei> +    {
> Andrei> +      std::string msg;
> Andrei> +
> Andrei> +      for (size_t i = 0; ui_file_style::basic_color_enums[i]; ++i)
> Andrei> +	{
> Andrei> +	  msg.append ("\"");
> Andrei> +	  msg.append (ui_file_style::basic_color_enums[i]);
> Andrei> +	  msg.append ("\", ");
>
> I think this ends up appending an extra comma.  Perhaps the
> comma-addition should be done at the star of the loop, if "i > 0".
Yes, comma will be appended after the last color name.
But this is expected comma before the word integer.
The whole string will be
Requires an argument. Valid arguments are "none", "black", "red", 
"green", "yellow", "blue", "magenta", "cyan", "white", integer from -1 
to 255 or an RGB hex triplet in a format #RRGGBB
I will remove the extra space before integer in the next version of the 
patch.


> Andrei> diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c
> Andrei> index 05539285c80..3a165ef6d2c 100644
> Andrei> --- a/gdb/cli/cli-option.c
> Andrei> +++ b/gdb/cli/cli-option.c
> Andrei> @@ -45,6 +45,9 @@ union option_value
>   
> Andrei>    /* For var_string options.  This is malloc-allocated.  */
> Andrei>    std::string *string;
> Andrei> +
> Andrei> +  /* For var_color options.  */
> Andrei> +  ui_file_style::color color = ui_file_style::NONE;
>
> I didn't even know inline initializers were possible in a union.
> Anyway I think it's probably better to leave this off.
There is a compilation error without this.


> Andrei> -  memset (&obj->value, 0, sizeof (obj->value));
> Andrei> +  obj->value = {}; /* zeros initialization */
>
> Was this needed?
Yes. parmpy_variable is not std::is_trivial now because of the 
ui_file_style::color. So memset from gdbsupport/poison.h fails on this type.
I will add explicit call to ~color() into parmpy_dealloc in the next 
version of the patch. Though this destructor is trivial at the moment.


> Andrei> +/* See ui-style.h.  */
> Andrei> +/* Must correspond to ui_file_style::basic_color.  */
> Andrei> +const std::vector<const char *> ui_file_style::basic_color_enums = {
> Andrei> +  "none",
> Andrei> +  "black",
> Andrei> +  "red",
> Andrei> +  "green",
> Andrei> +  "yellow",
> Andrei> +  "blue",
> Andrei> +  "magenta",
> Andrei> +  "cyan",
> Andrei> +  "white",
> Andrei> +  nullptr
> Andrei> +};
>
> I guess the nullptr is needed since although it is a vector now, it's
> still passed to some null-expecting API?  Like maybe completion?
>
> I wonder if a vector can be constexpr.
Yes, it's passed to complete_on_enum. Vector can't be constexpr.


> Andrei>  bool
> Andrei>  ui_file_style::color::append_ansi (bool is_fg, std::string *str) const
> Andrei>  {
> ...
> Andrei> +  else
> Andrei> +    return false;
>
> I wonder when this can happen and if it could somehow be made
> impossible.
No, this should no happen after all the checks in the constructors.
I will add gdb_assert_not_reached here and change return type to void in 
the next version of the patch.


On 04/10/2024 20:55, Tom Tromey wrote:
>>>>>> "Andrei" == Andrei Pikas<gdb@mail.api.win>  writes:
> Andrei> Colors can be specified as "none" for terminal's default color, as a name of
> Andrei> one of the eight standard colors of ISO/IEC 6429 "black", "red", "green", etc.,
> Andrei> as an RGB hexadecimal tripplet #RRGGBB for 24-bit TrueColor, or as an
> Andrei> integer from 0 to 255.  Integers 0 to 7 are the synonyms for the standard
> Andrei> colors.  Integers 8-15 are used for the so-called bright colors from the
> Andrei> aixterm extended 16-color palette.  Integers 16-255 are the indexes into xterm
> Andrei> extended 256-color palette (usually 6x6x6 cube plus gray ramp).  In
> Andrei> general, 256-color palette is terminal dependent and sometimes can be
> Andrei> changed with OSC 4 sequences, e.g. "\033]4;1;rgb:00/FF/00\033\\".
>
> Thank you for the patch.  I'm sorry about the long delays on this; gdb
> has an ongoing review bandwidth problem.  Anyway, I like this a lot and
> I would like to see it get in.
>
> I have a fair number of notes here but nothing really major.
>
> Andrei> +    case var_color:
> Andrei> +      {
> Andrei> +	std::string s = var.get<ui_file_style::color> ().to_string ();
> Andrei> +	return value_cstring (s.c_str (), s.size (),
> Andrei> +			      builtin_type (gdbarch)->builtin_char);
>
> Other code in this function uses:
>
> 	return current_language->value_string (gdbarch, value, len);
>
> Andrei> +  if (*text == '\0')
> Andrei> +    {
> Andrei> +      /* Convenience to let the user know what the option
> Andrei> +	 can accept.  Note there's no common prefix between
> Andrei> +	 the strings on purpose, so that complete_on_enum doesn't do
> Andrei> +	 a partial match.  */
> Andrei> +      tracker.add_completion (make_unique_xstrdup ("NUMBER"));
> Andrei> +      tracker.add_completion (make_unique_xstrdup ("#RRGGBB"));
> Andrei> +    }
>
> This is a clever idea and I somewhat wish readline had a more robust
> completion API to show this kind of info "inline".
>
> Andrei> +ui_file_style::color
> Andrei> +parse_cli_var_color (const char **args)
> Andrei> +{
> Andrei> +  /* Do a "set" command.  ARG is nullptr if no argument, or the
> Andrei> +     text of the argument.  */
> Andrei> +
> Andrei> +  if (args == nullptr || *args == nullptr || **args == '\0')
> Andrei> +    {
> Andrei> +      std::string msg;
> Andrei> +
> Andrei> +      for (size_t i = 0; ui_file_style::basic_color_enums[i]; ++i)
> Andrei> +	{
> Andrei> +	  msg.append ("\"");
> Andrei> +	  msg.append (ui_file_style::basic_color_enums[i]);
> Andrei> +	  msg.append ("\", ");
>
> I think this ends up appending an extra comma.  Perhaps the
> comma-addition should be done at the star of the loop, if "i > 0".
>
> Andrei> diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c
> Andrei> index 05539285c80..3a165ef6d2c 100644
> Andrei> --- a/gdb/cli/cli-option.c
> Andrei> +++ b/gdb/cli/cli-option.c
> Andrei> @@ -45,6 +45,9 @@ union option_value
>   
> Andrei>    /* For var_string options.  This is malloc-allocated.  */
> Andrei>    std::string *string;
> Andrei> +
> Andrei> +  /* For var_color options.  */
> Andrei> +  ui_file_style::color color = ui_file_style::NONE;
>
> I didn't even know inline initializers were possible in a union.
> Anyway I think it's probably better to leave this off.
>
> Andrei> +@node Colors In Python
> Andrei> +@subsection Python representation of colors
>
> This new node doesn't seem to be referenced from a menu anywhere.  I
> think it has to be added to the appropriate spot in the menu in the
> "Python API" node.
>
> Andrei> diff --git a/gdb/python/py-color.c b/gdb/python/py-color.c
> Andrei> new file mode 100644
> Andrei> index 00000000000..1d72cc3e7d1
> Andrei> --- /dev/null
> Andrei> +++ b/gdb/python/py-color.c
> Andrei> @@ -0,0 +1,347 @@
> Andrei> +/* Python interface to ui_file_style::color objects.
> Andrei> +
> Andrei> +   Copyright (C) 2008-2022 Free Software Foundation, Inc.
>
> I suspect this should just be 2024.
>
> Andrei> +/* See py-color.h.  */
> Andrei> +bool
> Andrei> +gdbpy_is_color (PyObject *obj)
> Andrei> +{
> Andrei> +  return PyObject_IsInstance (obj, (PyObject *)&colorpy_object_type);
>
> gdb style is a space after the closing paren of a cast.
>
> Andrei> +static PyObject *
> Andrei> +colorpy_escape_sequence (PyObject *self, PyObject *is_fg_obj)
> Andrei> +{
> ...
> Andrei> +  bool is_fg = PyObject_IsTrue (is_fg_obj);
>
> In theory, this can fail, returning -1.
>
> In practice, the code already calls PyBool_Check, so I think just a
> direct comparison against Py_True would be fine.
>
> Andrei> +static int
> Andrei> +colorpy_init (PyObject *self, PyObject *args, PyObject *kwds)
> ...
> Andrei> +      if (colorspace_obj)
> Andrei> +	{
> Andrei> +	  if (PyLong_Check (colorspace_obj))
> Andrei> +	    {
> Andrei> +	      long colorspace_id = -1;
> Andrei> +	      if (! gdb_py_int_as_long (colorspace_obj, &colorspace_id))
> Andrei> +		return -1;
> Andrei> +	      if (colorspace_id < INT_MIN || colorspace_id > INT_MAX)
> Andrei> +		error (_("colorspace %ld is out of range."), colorspace_id);
>
> It seems like the range check here should use the colorspace enum values.
>
> Andrei> +	  Py_ssize_t tuple_size = PyTuple_Size (value_obj);
> Andrei> +	  if (tuple_size != 3)
> Andrei> +	    error (_("Tuple value with RGB must be of size 3."));
>
> If PyTuple_Size returns -1, this should early-return.
>
> Andrei> +	  if (!str)
> Andrei> +	    return -1;
>
> gdb uses comparisons like 'str == nullptr' here.
>
> Andrei> +	  if (colorspace_obj != nullptr
> Andrei> +	      && colorspace != obj->color.colorspace ())
> Andrei> +	    error (_("colorspace doesn't match to value."));
>
> I would s/to/the in this error message.
>
> Andrei> +  catch (const gdb_exception &except)
> Andrei> +    {
> Andrei> +      gdbpy_convert_exception (except);
> Andrei> +      return -1;
> Andrei> +    }
>
> A newish change landed and now this is written
>
>      return gdbpy_handle_exception (-1, except);
>
> Andrei> +/* Deallocate function for a gdb.Color.  */
> Andrei> +
> Andrei> +static void
> Andrei> +colorpy_dealloc (PyObject *)
> Andrei> +{
> Andrei> +}
>
> I wonder if this is necessary.
> If so maybe a comment explaining why would be good.
>
> Andrei> +/* Initialize the 'color' module.  */
> Andrei> +static int
> Andrei> +gdbpy_initialize_color (void)
> Andrei> +{
> Andrei> +  colorpy_object_type.tp_new = PyType_GenericNew;
> Andrei> +  if (PyType_Ready (&colorpy_object_type) < 0)
> Andrei> +    return -1;
>
> A recent change means that this should now call gdbpy_type_ready
> instead.  This also removes the need to call gdb_pymodule_addobject, so
> this call can be lowered to the end of this function.
>
> Andrei> diff --git a/gdb/python/py-color.h b/gdb/python/py-color.h
> ...
> Andrei> +/* Extracts value from OBJ object of gdb.Color type.  */
> Andrei> +extern const ui_file_style::color & gdbpy_get_color (PyObject *obj);
>
> No space after the "&".
>
> Andrei> -  memset (&obj->value, 0, sizeof (obj->value));
> Andrei> +  obj->value = {}; /* zeros initialization */
>
> Was this needed?
>
> Andrei> diff --git a/gdb/testsuite/gdb.guile/scm-color.exp b/gdb/testsuite/gdb.guile/scm-color.exp
> Andrei> new file mode 100644
> Andrei> index 00000000000..e5773650f2f
> Andrei> --- /dev/null
> Andrei> +++ b/gdb/testsuite/gdb.guile/scm-color.exp
> Andrei> @@ -0,0 +1,110 @@
> Andrei> +# Copyright (C) 2010-2022 Free Software Foundation, Inc.
>
> Another date to change.  I'd suggest looking at all of them.
>
> Andrei> diff --git a/gdb/testsuite/gdb.python/py-parameter.exp b/gdb/testsuite/gdb.python/py-parameter.exp
> Andrei> index de524f49ad6..6e6a6007198 100644
> Andrei> --- a/gdb/testsuite/gdb.python/py-parameter.exp
> Andrei> +++ b/gdb/testsuite/gdb.python/py-parameter.exp
> Andrei> @@ -185,6 +185,58 @@ proc_with_prefix test_enum_parameter { } {
> Andrei>  	"Undefined item: \"three\".*" "set invalid enum parameter"
> Andrei>  }
>   
> Andrei> +# Test an color parameter.
> Andrei> +proc_with_prefix test_color_parameter { } {
> Andrei> +    global env
> Andrei> +    save_vars { env(TERM) env(COLORTERM) } {
>
> This should probably use with_ansi_styling_terminal to avoid having
> NO_COLOR set?
>
> Andrei> +static void
> Andrei> +init_colorsupport_var (void)
>
> You should use "()" instead of "(void)".  The latter is a leftover
> C-ism.
>
> Andrei> +{
> Andrei> +  const std::vector<color_space>& cs = colorsupport ();
>
> Also move the "&" after the space.
>
> Andrei> +/* See ui-style.h.  */
> Andrei> +/* Must correspond to ui_file_style::basic_color.  */
> Andrei> +const std::vector<const char *> ui_file_style::basic_color_enums = {
> Andrei> +  "none",
> Andrei> +  "black",
> Andrei> +  "red",
> Andrei> +  "green",
> Andrei> +  "yellow",
> Andrei> +  "blue",
> Andrei> +  "magenta",
> Andrei> +  "cyan",
> Andrei> +  "white",
> Andrei> +  nullptr
> Andrei> +};
>
> I guess the nullptr is needed since although it is a vector now, it's
> still passed to some null-expecting API?  Like maybe completion?
>
> I wonder if a vector can be constexpr.
>
> Andrei>  bool
> Andrei>  ui_file_style::color::append_ansi (bool is_fg, std::string *str) const
> Andrei>  {
> ...
> Andrei> +  else
> Andrei> +    return false;
>
> I wonder when this can happen and if it could somehow be made
> impossible.
>
> Andrei> +const std::vector<color_space> &
> Andrei> +colorsupport ()
> Andrei> +{
> Andrei> +  static const std::vector<color_space> value = []
>
> I didn't realize you could omit the '()' from a lambda.
>
> Andrei> +    {
> Andrei> +      std::vector<color_space> result = {color_space::MONOCHROME};
> Andrei> +
> Andrei> +      int colors = tgetnum ((char *)"Co");
>
> Is the cast really needed?
>
> Andrei> +      const char *colorterm = getenv ("COLORTERM");
> Andrei> +      if (colorterm && (!strcmp (colorterm, "truecolor")
>
> 'colorterm != nullptr'
>
> Andrei> +const char *
> Andrei> +color_space_name (color_space c)
> Andrei> +{
> Andrei> +  switch (c)
> Andrei> +    {
> Andrei> +    case color_space::MONOCHROME: return "monochrome";
> Andrei> +    case color_space::ANSI_8COLOR: return "ansi_8color";
> Andrei> +    case color_space::AIXTERM_16COLOR: return "aixterm_16color";
> Andrei> +    case color_space::XTERM_256COLOR: return "xterm_256color";
> Andrei> +    case color_space::RGB_24BIT: return "rgb_24bit";
> Andrei> +    }
> Andrei> +  error (_("Color space %d has no name."), static_cast<int> (c));
>
> Probably an assert-not-reached is more appropriate here?
>
> Andrei> +}
> Andrei> diff --git a/gdb/ui-style.h b/gdb/ui-style.h
> Andrei> index 1b7b5fafb9d..a13618dfce5 100644
> Andrei> --- a/gdb/ui-style.h
> Andrei> +++ b/gdb/ui-style.h
> Andrei> @@ -19,6 +19,26 @@
> Andrei>  #ifndef UI_STYLE_H
> Andrei>  #define UI_STYLE_H
>   
> Andrei> +/* One of the color spaces that usually supported by terminals.  */
> Andrei> +enum class color_space
> Andrei> +{
> Andrei> +  MONOCHROME, // one default terminal color
> Andrei> +  ANSI_8COLOR, // foreground colors \e[30m ... \e[37m,
> Andrei> +	       // background colors \e[40m ... \e[47m
> Andrei> +  AIXTERM_16COLOR, // foreground colors \e[30m ... \e[37m, \e[90m ... \e[97m
> Andrei> +		   // background colors \e[40m ... \e[47m, \e[100m ... \e107m
> Andrei> +  XTERM_256COLOR, // foreground colors \e[38;5;0m ... \e[38;5;255m
> Andrei> +		  // background colors \e[48;5;0m ... \e[48;5;255m
> Andrei> +  RGB_24BIT // foreground colors \e[38;2;0;0;0m ... \e[38;2;255;255;255m
> Andrei> +	    // background colors \e[48;2;0;0;0m ... \e[48;2;255;255;255m
>
> gdb doesn't use "//" comments.  Also normally gdb doesn't use trailing
> comments on the line; instead I'd put them before each constant they
> describe.
>
> Tom
  
Andrei Pikas Oct. 5, 2024, 7:11 p.m. UTC | #4
Hello.

> --- a/gdb/cli/cli-style.c
> +++ b/gdb/cli/cli-style.c
> @@ -42,20 +42,6 @@ bool source_styling = true;
>   
>   bool disassembler_styling = true;
>   
> -/* Name of colors; must correspond to ui_file_style::basic_color.  */
> -static const char * const cli_colors[] = {
> -  "none",
> -  "black",
> -  "red",
> -  "green",
> -  "yellow",
> -  "blue",
> -  "magenta",
> -  "cyan",
> -  "white",
> -  nullptr
> -};
> I don't understand the effect of this and related changes, and the log
> message says nothing about this.  Could you please explain why you
> remove these names and their uses, and what will be used in their
> stead?  And why?

They not removed. They moved to ui-style.c under the name
ui_file_style::basic_color_enums and became public to other files.


> Regardless, were these changes tested in the MinGW port of GDB?  It
> emulates Posix terminal handling of colors via SGR escape sequences,
> and I wonder whether these changes might somehow break styling support
> in the MinGW port.
No, I can't build gdb with MinGW.
The same escape sequences \033[30m ... \033[37m will be used
for the basic eight colors as before. This patch only adds the possibility
for the user to setup another colors in addition to the basic. But they
are not used by default, so the styling will not be broken.


> +@item $_colorsupport
> +@vindex $_colorsupport@r{, convenience variable}
> +Comma-separated list of color space names supported by terminal.  Names could
> +be any of @samp{monochrome}, @samp{ansi_8color}, @samp{aixterm_16color},
> +@samp{xterm_256color}, @samp{rgb_24bit}.  E.g., for plain linux terminal the
> +value could be @samp{monochrome,ansi_8color} and for terminal with truecolor
> +support it could be
> +@samp{monochrome,ansi_8color,aixterm_16color,xterm_256color,rgb_24bit}.
> What does this return for the MS-Windows terminal? aixterm_16color?
> IOW, will any 16-color terminal return aixterm_16color?  I think this
> should be documented, and perhaps we should remove the "aix" part from
> the name (since it is not necessarily specific to AIX).
tgetnum("Co") returns 8 in msys and 256 in cmd.exe because
TERM=xterm in msys and TERM is unset in cmd.exe.
When I set TERM=xterm or set TERM=ansi it returns 8.
monochrome color space is enabled always.
ansi_8color is enabled when tgetnum("Co") returns >= 8.
aixterm_16color is enabled when tgetnum("Co") returns >= 16.
xterm_256color is enabled when tgetnum("Co") returns >= 256.
rgb_24bit is enabled when COLORTERM=truecolor or 24bit.
I will document that it depends on "Co" termcap, TERM and COLORTERM
in the next patch version.

I don't think that "aix" part should be removed. Because there is no 
standard
for the escape sequences \033[90m ... \033[97m. They where first used in the
aixterm and it is the only way to refer them unambiguously. Any other 
terminal
in theory may invent it's own 16 colors and escape sequences for them.


On 15/09/2024 08:37, Eli Zaretskii wrote:
>> From: Andrei Pikas <gdb@mail.api.win>
>> Cc: gdb-patches@sourceware.org,
>> 	Andrei Pikas <gdb@mail.api.win>
>> Date: Sat, 14 Sep 2024 22:04:52 +0300
>>
>> Colors can be specified as "none" for terminal's default color, as a name of
>> one of the eight standard colors of ISO/IEC 6429 "black", "red", "green", etc.,
>> as an RGB hexadecimal tripplet #RRGGBB for 24-bit TrueColor, or as an
>> integer from 0 to 255.  Integers 0 to 7 are the synonyms for the standard
>> colors.  Integers 8-15 are used for the so-called bright colors from the
>> aixterm extended 16-color palette.  Integers 16-255 are the indexes into xterm
>> extended 256-color palette (usually 6x6x6 cube plus gray ramp).  In
>> general, 256-color palette is terminal dependent and sometimes can be
>> changed with OSC 4 sequences, e.g. "\033]4;1;rgb:00/FF/00\033\\".
>>
>> It is the responsibility of the user to verify that the terminal supports
>> the specified colors.
>>
>> PATCH v5 changes: documentation fixed.
>> PATCH v6 changes: documentation fixed.
>> PATCH v7 changes: rebase onto master and fixes after review.
>> ---
>>   gdb/Makefile.in                           |   2 +
>>   gdb/NEWS                                  |  29 ++
>>   gdb/cli/cli-cmds.c                        |   7 +
>>   gdb/cli/cli-decode.c                      | 174 +++++++++
>>   gdb/cli/cli-decode.h                      |  21 +
>>   gdb/cli/cli-option.c                      |  44 +++
>>   gdb/cli/cli-option.h                      |  21 +
>>   gdb/cli/cli-setshow.c                     |  21 +
>>   gdb/cli/cli-style.c                       |  49 +--
>>   gdb/cli/cli-style.h                       |   4 +-
>>   gdb/command.h                             |  26 +-
>>   gdb/doc/gdb.texinfo                       |  38 +-
>>   gdb/doc/guile.texi                        | 104 +++++
>>   gdb/doc/python.texi                       |  97 +++++
>>   gdb/guile/guile-internal.h                |   9 +
>>   gdb/guile/guile.c                         |   1 +
>>   gdb/guile/scm-color.c                     | 443 ++++++++++++++++++++++
>>   gdb/guile/scm-param.c                     |  33 +-
>>   gdb/python/py-color.c                     | 347 +++++++++++++++++
>>   gdb/python/py-color.h                     |  35 ++
>>   gdb/python/py-param.c                     |  38 +-
>>   gdb/python/python.c                       |   7 +
>>   gdb/testsuite/gdb.base/style.exp          | 197 ++++++++++
>>   gdb/testsuite/gdb.guile/scm-color.exp     | 110 ++++++
>>   gdb/testsuite/gdb.guile/scm-parameter.exp |  47 +++
>>   gdb/testsuite/gdb.python/py-color.exp     | 100 +++++
>>   gdb/testsuite/gdb.python/py-parameter.exp |  53 +++
>>   gdb/testsuite/lib/gdb-utils.exp           |  22 +-
>>   gdb/testsuite/lib/gdb.exp                 |   3 +-
>>   gdb/top.c                                 |  14 +
>>   gdb/ui-style.c                            | 331 ++++++++++++----
>>   gdb/ui-style.h                            | 142 ++++++-
>>   gdb/unittests/style-selftests.c           |   6 +-
>>   33 files changed, 2414 insertions(+), 161 deletions(-)
>>   create mode 100644 gdb/guile/scm-color.c
>>   create mode 100644 gdb/python/py-color.c
>>   create mode 100644 gdb/python/py-color.h
>>   create mode 100644 gdb/testsuite/gdb.guile/scm-color.exp
>>   create mode 100644 gdb/testsuite/gdb.python/py-color.exp
> Thanks.
>
>> --- a/gdb/NEWS
>> +++ b/gdb/NEWS
>> @@ -37,11 +37,40 @@
>>     history has been reached.  It also specifies that the forward execution can
>>     continue, and the recording will also continue.
>>   
>> +* "set style" commands now supports numeric format for basic colors
>> +  from 0 to 255 and #RRGGBB format for TrueColor.
>> +
>> +* New built-in convenience variable $_colorsupport provides comma-separated
>> +  list of color space names supported by terminal.  It is handy for
>> +  conditionally using styling colors based on terminal features.  For example:
>> +
>> +  (gdb) if $_regex ($_colorsupport, ".*(^|,)rgb_24bit($|,).*")
>> +   >set style filename background #FACADE
>> +   >else
>> +   >if $_regex ($_colorsupport, ".*(^|,)xterm_256color($|,).*")
>> +    >set style filename background 224
>> +    >else
>> +    >set style filename background red
>> +    >end
>> +   >end
>> +
> I'd prefer to have a plain-English explanation of "color space" here,
> instead of trying to explain it via script which uses the variable.
>
>> --- a/gdb/cli/cli-style.c
>> +++ b/gdb/cli/cli-style.c
>> @@ -42,20 +42,6 @@ bool source_styling = true;
>>   
>>   bool disassembler_styling = true;
>>   
>> -/* Name of colors; must correspond to ui_file_style::basic_color.  */
>> -static const char * const cli_colors[] = {
>> -  "none",
>> -  "black",
>> -  "red",
>> -  "green",
>> -  "yellow",
>> -  "blue",
>> -  "magenta",
>> -  "cyan",
>> -  "white",
>> -  nullptr
>> -};
> I don't understand the effect of this and related changes, and the log
> message says nothing about this.  Could you please explain why you
> remove these names and their uses, and what will be used in their
> stead?  And why?
>
> Regardless, were these changes tested in the MinGW port of GDB?  It
> emulates Posix terminal handling of colors via SGR escape sequences,
> and I wonder whether these changes might somehow break styling support
> in the MinGW port.
>
>> +@item $_colorsupport
>> +@vindex $_colorsupport@r{, convenience variable}
>> +Comma-separated list of color space names supported by terminal.  Names could
>> +be any of @samp{monochrome}, @samp{ansi_8color}, @samp{aixterm_16color},
>> +@samp{xterm_256color}, @samp{rgb_24bit}.  E.g., for plain linux terminal the
>> +value could be @samp{monochrome,ansi_8color} and for terminal with truecolor
>> +support it could be
>> +@samp{monochrome,ansi_8color,aixterm_16color,xterm_256color,rgb_24bit}.
> What does this return for the MS-Windows terminal? aixterm_16color?
> IOW, will any 16-color terminal return aixterm_16color?  I think this
> should be documented, and perhaps we should remove the "aix" part from
> the name (since it is not necessarily specific to AIX).
>
> Also, please add a "@cindex color space" entry before the above text,
> as I believe this is where we describe this term.  For the same
> reason, I think the first use of "color spaces" should have the @dfn
> markup.
>
>> -Set the background to @var{color}.  Valid colors are @samp{none}
>> -(meaning the terminal's default color), @samp{black}, @samp{red},
>> -@samp{green}, @samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan},
>> -and@samp{white}.
>> +Set the background to @var{color}.  @var{color} can be @samp{none}
>> +(meaning the terminal's default color), a name of one of the eight standard
>> +colors of ISO/IEC 6429, index from 0 to 255 into terminal's color
>> +palette or a hexadecimal RGB triplet in @samp{#RRGGBB} format for
>> +24-bit TrueColor.
> Should the RGB specs always use 2 hex characters per component,
> or can one use RRRGGGBBB etc. as well?  This should be documented.
  
Eli Zaretskii Oct. 5, 2024, 7:40 p.m. UTC | #5
> Date: Sat, 5 Oct 2024 22:11:12 +0300
> Cc: tom@tromey.com, gdb-patches@sourceware.org
> From: Andrei Pikas <gdb@mail.api.win>
> 
> > Regardless, were these changes tested in the MinGW port of GDB?  It
> > emulates Posix terminal handling of colors via SGR escape sequences,
> > and I wonder whether these changes might somehow break styling support
> > in the MinGW port.
> No, I can't build gdb with MinGW.
> The same escape sequences \033[30m ... \033[37m will be used
> for the basic eight colors as before. This patch only adds the possibility
> for the user to setup another colors in addition to the basic. But they
> are not used by default, so the styling will not be broken.

And if the user wants to add a color, what do they need to do after
these changes?

> > +@item $_colorsupport
> > +@vindex $_colorsupport@r{, convenience variable}
> > +Comma-separated list of color space names supported by terminal.  Names could
> > +be any of @samp{monochrome}, @samp{ansi_8color}, @samp{aixterm_16color},
> > +@samp{xterm_256color}, @samp{rgb_24bit}.  E.g., for plain linux terminal the
> > +value could be @samp{monochrome,ansi_8color} and for terminal with truecolor
> > +support it could be
> > +@samp{monochrome,ansi_8color,aixterm_16color,xterm_256color,rgb_24bit}.
> > What does this return for the MS-Windows terminal? aixterm_16color?
> > IOW, will any 16-color terminal return aixterm_16color?  I think this
> > should be documented, and perhaps we should remove the "aix" part from
> > the name (since it is not necessarily specific to AIX).
> tgetnum("Co") returns 8 in msys and 256 in cmd.exe because
> TERM=xterm in msys and TERM is unset in cmd.exe.
> When I set TERM=xterm or set TERM=ansi it returns 8.

But the MS-Windows console supports 16 colors, not 8.

> monochrome color space is enabled always.
> ansi_8color is enabled when tgetnum("Co") returns >= 8.
> aixterm_16color is enabled when tgetnum("Co") returns >= 16.
> xterm_256color is enabled when tgetnum("Co") returns >= 256.
> rgb_24bit is enabled when COLORTERM=truecolor or 24bit.
> I will document that it depends on "Co" termcap, TERM and COLORTERM
> in the next patch version.

The "Co" capability on Windows is emulated, as Windows doesn't really
support this stuff.  So relying on what you get from tgetnum in
various cases, like with MSYS, will likely produce results you cannot
rely upon.

> I don't think that "aix" part should be removed. Because there is no 
> standard
> for the escape sequences \033[90m ... \033[97m. They where first used in the
> aixterm and it is the only way to refer them unambiguously. Any other 
> terminal
> in theory may invent it's own 16 colors and escape sequences for them.

Then I guess we will need a new name for the Windows terminal?
  

Patch

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index bcf1ee45a70..502b4b6f9e5 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -359,6 +359,7 @@  SUBDIR_GUILE_SRCS = \
 	guile/scm-block.c \
 	guile/scm-breakpoint.c \
 	guile/scm-cmd.c \
+	guile/scm-color.c \
 	guile/scm-disasm.c \
 	guile/scm-exception.c \
 	guile/scm-frame.c \
@@ -395,6 +396,7 @@  SUBDIR_PYTHON_SRCS = \
 	python/py-bpevent.c \
 	python/py-breakpoint.c \
 	python/py-cmd.c \
+	python/py-color.c \
 	python/py-connection.c \
 	python/py-continueevent.c \
 	python/py-dap.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index cfc9cb05f77..4accb394cc9 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -37,11 +37,40 @@ 
   history has been reached.  It also specifies that the forward execution can
   continue, and the recording will also continue.
 
+* "set style" commands now supports numeric format for basic colors
+  from 0 to 255 and #RRGGBB format for TrueColor.
+
+* New built-in convenience variable $_colorsupport provides comma-separated
+  list of color space names supported by terminal.  It is handy for
+  conditionally using styling colors based on terminal features.  For example:
+
+  (gdb) if $_regex ($_colorsupport, ".*(^|,)rgb_24bit($|,).*")
+   >set style filename background #FACADE
+   >else
+   >if $_regex ($_colorsupport, ".*(^|,)xterm_256color($|,).*")
+    >set style filename background 224
+    >else
+    >set style filename background red
+    >end
+   >end
+
 * Python API
 
   ** Added gdb.record.clear.  Clears the trace data of the current recording.
      This forces re-decoding of the trace for successive commands.
 
+  ** New class gdb.Color for dealing with colors.
+
+  ** New constant gdb.PARAM_COLOR represents color type of a
+     gdb.Parameter.value.  Parameter's value is gdb.Color instance.
+
+* Guile API
+
+  ** New type <gdb:color> for dealing with colors.
+
+  ** New constant PARAM_COLOR represents color type of a value
+     of a <gdb:parameter> object.  Parameter's value is <gdb::color> instance.
+
 * Debugger Adapter Protocol changes
 
   ** The "scopes" request will now return a scope holding global
diff --git a/gdb/cli/cli-cmds.c b/gdb/cli/cli-cmds.c
index fd8e27735f0..deec2a77cf3 100644
--- a/gdb/cli/cli-cmds.c
+++ b/gdb/cli/cli-cmds.c
@@ -2387,6 +2387,12 @@  value_from_setting (const setting &var, struct gdbarch *gdbarch)
 
 	return current_language->value_string (gdbarch, value, len);
       }
+    case var_color:
+      {
+	std::string s = var.get<ui_file_style::color> ().to_string ();
+	return value_cstring (s.c_str (), s.size (),
+			      builtin_type (gdbarch)->builtin_char);
+      }
     default:
       gdb_assert_not_reached ("bad var_type");
     }
@@ -2434,6 +2440,7 @@  str_value_from_setting (const setting &var, struct gdbarch *gdbarch)
     case var_pinteger:
     case var_boolean:
     case var_auto_boolean:
+    case var_color:
       {
 	std::string cmd_val = get_setshow_command_value_string (var);
 
diff --git a/gdb/cli/cli-decode.c b/gdb/cli/cli-decode.c
index 163012a6bec..f86152480a0 100644
--- a/gdb/cli/cli-decode.c
+++ b/gdb/cli/cli-decode.c
@@ -23,6 +23,7 @@ 
 #include "cli/cli-cmds.h"
 #include "cli/cli-decode.h"
 #include "cli/cli-style.h"
+#include "cli/cli-utils.h"
 #include <optional>
 
 /* Prototypes for local functions.  */
@@ -739,6 +740,87 @@  add_setshow_enum_cmd (const char *name, command_class theclass,
   return cmds;
 }
 
+/* See cli-decode.h.  */
+
+void
+complete_on_color (completion_tracker &tracker,
+		   const char *text, const char *word)
+{
+  complete_on_enum (tracker, ui_file_style::basic_color_enums.data (),
+		    text, word);
+  if (*text == '\0')
+    {
+      /* Convenience to let the user know what the option
+	 can accept.  Note there's no common prefix between
+	 the strings on purpose, so that complete_on_enum doesn't do
+	 a partial match.  */
+      tracker.add_completion (make_unique_xstrdup ("NUMBER"));
+      tracker.add_completion (make_unique_xstrdup ("#RRGGBB"));
+    }
+}
+
+/* Completer used in color commands.  */
+
+static void
+color_completer (struct cmd_list_element *ignore,
+		 completion_tracker &tracker,
+		 const char *text, const char *word)
+{
+  complete_on_color (tracker, text, word);
+}
+
+
+/* Add element named NAME to command list LIST (the list for set or
+   some sublist thereof).  CLASS is as in add_cmd.  VAR is address
+   of the variable which will contain the color.  */
+
+set_show_commands
+add_setshow_color_cmd (const char *name,
+		       enum command_class theclass,
+		       ui_file_style::color *var,
+		       const char *set_doc,
+		       const char *show_doc,
+		       const char *help_doc,
+		       cmd_func_ftype *set_func,
+		       show_value_ftype *show_func,
+		       struct cmd_list_element **set_list,
+		       struct cmd_list_element **show_list)
+{
+  set_show_commands commands = add_setshow_cmd_full<ui_file_style::color>
+    (name, theclass, var_color, var,
+     set_doc, show_doc, help_doc,
+     nullptr, nullptr, set_func, show_func,
+     set_list, show_list);
+
+  set_cmd_completer (commands.set, color_completer);
+
+  return commands;
+}
+
+/* Same as above but using a getter and a setter function instead of a pointer
+   to a global storage buffer.  */
+
+set_show_commands
+add_setshow_color_cmd (const char *name, command_class theclass,
+		       const char *set_doc, const char *show_doc,
+		       const char *help_doc,
+		       setting_func_types<ui_file_style::color>::set set_func,
+		       setting_func_types<ui_file_style::color>::get get_func,
+		       show_value_ftype *show_func,
+		       cmd_list_element **set_list,
+		       cmd_list_element **show_list)
+{
+  auto cmds = add_setshow_cmd_full<ui_file_style::color>
+    (name, theclass, var_color, nullptr,
+     set_doc, show_doc, help_doc,
+     set_func, get_func, nullptr, show_func,
+     set_list, show_list);
+
+  set_cmd_completer (cmds.set, color_completer);
+
+  return cmds;
+}
+
 /* See cli-decode.h.  */
 const char * const auto_boolean_enums[] = { "on", "off", "auto", NULL };
 
@@ -2749,3 +2831,95 @@  cli_user_command_p (struct cmd_list_element *cmd)
 {
   return cmd->theclass == class_user && cmd->func == do_simple_func;
 }
+
+/* See cli-decode.h.  */
+
+ui_file_style::color
+parse_cli_var_color (const char **args)
+{
+  /* Do a "set" command.  ARG is nullptr if no argument, or the
+     text of the argument.  */
+
+  if (args == nullptr || *args == nullptr || **args == '\0')
+    {
+      std::string msg;
+
+      for (size_t i = 0; ui_file_style::basic_color_enums[i]; ++i)
+	{
+	  msg.append ("\"");
+	  msg.append (ui_file_style::basic_color_enums[i]);
+	  msg.append ("\", ");
+	}
+
+      error (_("Requires an argument. Valid arguments are %s integer from -1 "
+	     "to 255 or an RGB hex triplet in a format #RRGGBB"),
+	     msg.c_str ());
+    }
+
+  const char *p = skip_to_space (*args);
+  size_t len = p - *args;
+
+  int nmatches = 0;
+  ui_file_style::basic_color match = ui_file_style::NONE;
+  for (int i = 0; ui_file_style::basic_color_enums[i]; ++i)
+    if (strncmp (*args, ui_file_style::basic_color_enums[i], len) == 0)
+      {
+	match = static_cast<ui_file_style::basic_color> (i - 1);
+	if (ui_file_style::basic_color_enums[i][len] == '\0')
+	  {
+	    nmatches = 1;
+	    break; /* Exact match.  */
+	  }
+	else
+	  nmatches++;
+      }
+
+  if (nmatches == 1)
+    {
+      *args += len;
+      return ui_file_style::color (match);
+    }
+
+  if (nmatches > 1)
+    error (_("Ambiguous item \"%.*s\"."), (int) len, *args);
+
+  if (**args != '#')
+    {
+      ULONGEST num = get_ulongest (args);
+      if (num > 255)
+	error (_("integer %s out of range"), pulongest (num));
+      return ui_file_style::color (color_space::XTERM_256COLOR,
+				   static_cast<int> (num));
+    }
+
+  /* Try to parse #RRGGBB string.  */
+  if (len != 7)
+    error_no_arg (_("invalid RGB hex triplet format"));
+
+  uint8_t r, g, b;
+  int scanned_chars = 0;
+  int parsed_args = sscanf (*args, "#%2" SCNx8 "%2" SCNx8 "%2" SCNx8 "%n",
+			    &r, &g, &b, &scanned_chars);
+
+  if (parsed_args != 3 || scanned_chars != 7)
+    error_no_arg (_("invalid RGB hex triplet format"));
+
+  *args += len;
+  return ui_file_style::color (r, g, b);
+}
+
+/* See cli-decode.h.  */
+
+ui_file_style::color
+parse_var_color (const char *arg)
+{
+  const char *end_arg = arg;
+  ui_file_style::color color = parse_cli_var_color (&end_arg);
+
+  int len = end_arg - arg;
+  const char *after = skip_spaces (end_arg);
+  if (*after != '\0')
+    error (_("Junk after item \"%.*s\": %s"), len, arg, after);
+
+  return color;
+}
diff --git a/gdb/cli/cli-decode.h b/gdb/cli/cli-decode.h
index 7365c3f0157..5918d3b9dbe 100644
--- a/gdb/cli/cli-decode.h
+++ b/gdb/cli/cli-decode.h
@@ -306,6 +306,27 @@  extern const char * const boolean_enums[];
 /* The enums of auto-boolean commands.  */
 extern const char * const auto_boolean_enums[];
 
+/* Add the different possible completions of TEXT with color.
+
+   WORD points in the same buffer as TEXT, and completions should be
+   returned relative to this position.  For example, suppose TEXT is "foo"
+   and we want to complete to "foobar".  If WORD is "oo", return
+   "oobar"; if WORD is "baz/foo", return "baz/foobar".  */
+
+extern void complete_on_color (completion_tracker &tracker,
+			       const char *text, const char *word);
+
+/* Parse ARGS, an option to a var_color variable.
+ *
+   Either returns the parsed value on success or throws an error.  ARGS may be
+   one of strings {none, black, red, green, yellow, blue, magenta,
+   cyan, white}, or color number from 0 to 255, or RGB hex triplet #RRGGBB.
+   */
+extern ui_file_style::color parse_cli_var_color (const char **args);
+
+/* Same as above but additionally check that there is no junk in the end.  */
+extern ui_file_style::color parse_var_color (const char *arg);
+
 /* Verify whether a given cmd_list_element is a user-defined command.
    Return 1 if it is user-defined.  Return 0 otherwise.  */
 
diff --git a/gdb/cli/cli-option.c b/gdb/cli/cli-option.c
index 05539285c80..3a165ef6d2c 100644
--- a/gdb/cli/cli-option.c
+++ b/gdb/cli/cli-option.c
@@ -45,6 +45,9 @@  union option_value
 
   /* For var_string options.  This is malloc-allocated.  */
   std::string *string;
+
+  /* For var_color options.  */
+  ui_file_style::color color = ui_file_style::NONE;
 };
 
 /* Holds an options definition and its value.  */
@@ -433,6 +436,35 @@  parse_option (gdb::array_view<const option_def_group> options_group,
 	val.enumeration = parse_cli_var_enum (args, match->enums);
 	return option_def_and_value {*match, match_ctx, val};
       }
+    case var_color:
+      {
+	if (completion != nullptr)
+	  {
+	    const char *after_arg = skip_to_space (*args);
+	    if (*after_arg == '\0')
+	      {
+		complete_on_color (completion->tracker, *args, *args);
+
+		if (completion->tracker.have_completions ())
+		  return {};
+	      }
+	  }
+
+	if (check_for_argument (args, "--"))
+	  {
+	    /* Treat e.g., "backtrace -entry-values --" as if there
+	       was no argument after "-entry-values".  This makes
+	       parse_cli_var_color throw an error with a suggestion of
+	       what are the valid options.  */
+	    args = nullptr;
+	  }
+
+	option_value val;
+	ui_file_style::color color = parse_cli_var_color (args);
+	ui_file_style::color approx_color = color.approximate (colorsupport ());
+	val.color = approx_color;
+	return option_def_and_value {*match, match_ctx, val};
+      }
     case var_string:
       {
 	if (check_for_argument (args, "--"))
@@ -611,6 +643,10 @@  save_option_value_in_ctx (std::optional<option_def_and_value> &ov)
       *ov->option.var_address.enumeration (ov->option, ov->ctx)
 	= ov->value->enumeration;
       break;
+    case var_color:
+      *ov->option.var_address.color (ov->option, ov->ctx)
+	= ov->value->color;
+      break;
     case var_string:
       *ov->option.var_address.string (ov->option, ov->ctx)
 	= std::move (*ov->value->string);
@@ -699,6 +735,14 @@  get_val_type_str (const option_def &opt, std::string &buffer)
 	  }
 	return buffer.c_str ();
       }
+    case var_color:
+      {
+	buffer = "";
+	for (size_t i = 0; ui_file_style::basic_color_enums[i]; ++i)
+	  buffer.append (ui_file_style::basic_color_enums[i]).append ("|");
+	buffer += "NUMBER|#RRGGBB";
+	return buffer.c_str ();
+      }
     case var_string:
       return "STRING";
     default:
diff --git a/gdb/cli/cli-option.h b/gdb/cli/cli-option.h
index bbe281d9721..2fc354ef1a2 100644
--- a/gdb/cli/cli-option.h
+++ b/gdb/cli/cli-option.h
@@ -91,6 +91,7 @@  struct option_def
       int *(*integer) (const option_def &, void *ctx);
       const char **(*enumeration) (const option_def &, void *ctx);
       std::string *(*string) (const option_def &, void *ctx);
+      ui_file_style::color *(*color) (const option_def &, void *ctx);
     }
   var_address;
 
@@ -308,6 +309,26 @@  struct string_option_def : option_def
   }
 };
 
+/* A var_color command line option.  */
+
+template<typename Context>
+struct color_option_def : option_def
+{
+  color_option_def (const char *long_option_,
+		    ui_file_style::color *(*get_var_address_cb_) (Context *),
+		    show_value_ftype *show_cmd_cb_,
+		    const char *set_doc_,
+		    const char *show_doc_ = nullptr,
+		    const char *help_doc_ = nullptr)
+    : option_def (long_option_, var_color,
+		  (erased_get_var_address_ftype *) get_var_address_cb_,
+		  show_cmd_cb_,
+		  set_doc_, show_doc_, help_doc_)
+  {
+    var_address.color = detail::get_var_address<ui_file_style::color, Context>;
+  }
+};
+
 /* A group of options that all share the same context pointer to pass
    to the options' get-current-value callbacks.  */
 struct option_def_group
diff --git a/gdb/cli/cli-setshow.c b/gdb/cli/cli-setshow.c
index bcc793b3148..a8f22c9bde6 100644
--- a/gdb/cli/cli-setshow.c
+++ b/gdb/cli/cli-setshow.c
@@ -443,6 +443,13 @@  do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
 	option_changed = c->var->set<const char *> (match);
       }
       break;
+    case var_color:
+      {
+	ui_file_style::color color = parse_var_color (arg);
+	ui_file_style::color approx_color = color.approximate (colorsupport ());
+	option_changed = c->var->set<ui_file_style::color> (approx_color);
+      }
+      break;
     default:
       error (_("gdb internal error: bad var_type in do_setshow_command"));
     }
@@ -520,6 +527,14 @@  do_set_command (const char *arg, int from_tty, struct cmd_list_element *c)
 	  interps_notify_param_changed
 	    (name, c->var->get<const char *> ());
 	  break;
+	case var_color:
+	  {
+	    const ui_file_style::color &color
+	      = c->var->get<ui_file_style::color> ();
+	    interps_notify_param_changed
+	      (name, color.to_string ().c_str ());
+	  }
+	  break;
 	case var_boolean:
 	  {
 	    const char *opt = c->var->get<bool> () ? "on" : "off";
@@ -585,6 +600,12 @@  get_setshow_command_value_string (const setting &var)
 	  stb.puts (value);
       }
       break;
+    case var_color:
+      {
+	const ui_file_style::color &value = var.get<ui_file_style::color> ();
+	stb.puts (value.to_string ().c_str ());
+      }
+      break;
     case var_boolean:
       stb.puts (var.get<bool> () ? "on" : "off");
       break;
diff --git a/gdb/cli/cli-style.c b/gdb/cli/cli-style.c
index 5928998e9ca..36c1ead58f0 100644
--- a/gdb/cli/cli-style.c
+++ b/gdb/cli/cli-style.c
@@ -42,20 +42,6 @@  bool source_styling = true;
 
 bool disassembler_styling = true;
 
-/* Name of colors; must correspond to ui_file_style::basic_color.  */
-static const char * const cli_colors[] = {
-  "none",
-  "black",
-  "red",
-  "green",
-  "yellow",
-  "blue",
-  "magenta",
-  "cyan",
-  "white",
-  nullptr
-};
-
 /* Names of intensities; must correspond to
    ui_file_style::intensity.  */
 static const char * const cli_intensities[] = {
@@ -131,8 +117,8 @@  cli_style_option::cli_style_option (const char *name,
 				    ui_file_style::intensity intensity)
   : changed (name),
     m_name (name),
-    m_foreground (cli_colors[fg - ui_file_style::NONE]),
-    m_background (cli_colors[0]),
+    m_foreground (fg),
+    m_background (ui_file_style::NONE),
     m_intensity (cli_intensities[intensity])
 {
 }
@@ -143,32 +129,17 @@  cli_style_option::cli_style_option (const char *name,
 				    ui_file_style::intensity i)
   : changed (name),
     m_name (name),
-    m_foreground (cli_colors[0]),
-    m_background (cli_colors[0]),
+    m_foreground (ui_file_style::NONE),
+    m_background (ui_file_style::NONE),
     m_intensity (cli_intensities[i])
 {
 }
 
-/* Return the color number corresponding to COLOR.  */
-
-static int
-color_number (const char *color)
-{
-  for (int i = 0; i < ARRAY_SIZE (cli_colors); ++i)
-    {
-      if (color == cli_colors[i])
-	return i - 1;
-    }
-  gdb_assert_not_reached ("color not found");
-}
-
 /* See cli-style.h.  */
 
 ui_file_style
 cli_style_option::style () const
 {
-  int fg = color_number (m_foreground);
-  int bg = color_number (m_background);
   ui_file_style::intensity intensity = ui_file_style::NORMAL;
 
   for (int i = 0; i < ARRAY_SIZE (cli_intensities); ++i)
@@ -180,7 +151,7 @@  cli_style_option::style () const
 	}
     }
 
-  return ui_file_style (fg, bg, intensity);
+  return ui_file_style (m_foreground, m_background, intensity);
 }
 
 /* See cli-style.h.  */
@@ -253,9 +224,8 @@  cli_style_option::add_setshow_commands (enum command_class theclass,
 
   set_show_commands commands;
 
-  commands = add_setshow_enum_cmd
-    ("foreground", theclass, cli_colors,
-     &m_foreground,
+  commands = add_setshow_color_cmd
+    ("foreground", theclass, &m_foreground,
      _("Set the foreground color for this property."),
      _("Show the foreground color for this property."),
      nullptr,
@@ -265,9 +235,8 @@  cli_style_option::add_setshow_commands (enum command_class theclass,
   commands.set->set_context (this);
   commands.show->set_context (this);
 
-  commands = add_setshow_enum_cmd
-    ("background", theclass, cli_colors,
-     &m_background,
+  commands = add_setshow_color_cmd
+    ("background", theclass, &m_background,
      _("Set the background color for this property."),
      _("Show the background color for this property."),
      nullptr,
diff --git a/gdb/cli/cli-style.h b/gdb/cli/cli-style.h
index 1663b4ee53c..66ce35cf2e2 100644
--- a/gdb/cli/cli-style.h
+++ b/gdb/cli/cli-style.h
@@ -67,9 +67,9 @@  class cli_style_option
   const char *m_name;
 
   /* The foreground.  */
-  const char *m_foreground;
+  ui_file_style::color m_foreground;
   /* The background.  */
-  const char *m_background;
+  ui_file_style::color m_background;
   /* The intensity.  */
   const char *m_intensity;
 
diff --git a/gdb/command.h b/gdb/command.h
index 0ceaf3e2a6d..715fcd8096f 100644
--- a/gdb/command.h
+++ b/gdb/command.h
@@ -111,7 +111,9 @@  enum var_types
     /* Enumerated type.  Can only have one of the specified values.
        *VAR is a char pointer to the name of the element that we
        find.  */
-    var_enum
+    var_enum,
+    /* Color type.  *VAR is a ui_file_style::color structure.  */
+    var_color
   };
 
 /* A structure describing an extra literal accepted and shown in place
@@ -185,6 +187,14 @@  inline bool var_type_uses<const char *> (var_types t)
   return t == var_enum;
 }
 
+/* Return true if a setting of type T is backed by an ui_file_style::color
+   variable.  */
+template<>
+inline bool var_type_uses<ui_file_style::color> (var_types t)
+{
+  return t == var_color;
+}
+
 template<bool is_scalar, typename T> struct setting_func_types_1;
 
 template<typename T>
@@ -681,6 +691,20 @@  extern set_show_commands add_setshow_enum_cmd
    setting_func_types<const char *>::get get_func, show_value_ftype *show_func,
    cmd_list_element **set_list, cmd_list_element **show_list);
 
+extern set_show_commands add_setshow_color_cmd
+  (const char *name, command_class theclass, ui_file_style::color *var,
+   const char *set_doc, const char *show_doc, const char *help_doc,
+   cmd_func_ftype *set_func, show_value_ftype *show_func,
+   cmd_list_element **set_list, cmd_list_element **show_list);
+
+extern set_show_commands add_setshow_color_cmd
+  (const char *name, command_class theclass,
+   const char *set_doc, const char *show_doc, const char *help_doc,
+   setting_func_types<ui_file_style::color>::set set_func,
+   setting_func_types<ui_file_style::color>::get get_func,
+   show_value_ftype *show_func, cmd_list_element **set_list,
+   cmd_list_element **show_list);
+
 extern set_show_commands add_setshow_auto_boolean_cmd
   (const char *name, command_class theclass, auto_boolean *var,
    const char *set_doc, const char *show_doc, const char *help_doc,
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 77a4021b36a..7de0cce344d 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -12990,6 +12990,15 @@  and @code{$_shell_exitsignal} according to the exit status of the last
 launched command.  These variables are set and used similarly to
 the variables @code{$_exitcode} and @code{$_exitsignal}.
 
+@item $_colorsupport
+@vindex $_colorsupport@r{, convenience variable}
+Comma-separated list of color space names supported by terminal.  Names could
+be any of @samp{monochrome}, @samp{ansi_8color}, @samp{aixterm_16color},
+@samp{xterm_256color}, @samp{rgb_24bit}.  E.g., for plain linux terminal the
+value could be @samp{monochrome,ansi_8color} and for terminal with truecolor
+support it could be
+@samp{monochrome,ansi_8color,aixterm_16color,xterm_256color,rgb_24bit}.
+
 @end table
 
 @node Convenience Funs
@@ -27917,16 +27926,29 @@  For example, the style of file names can be controlled using the
 
 @table @code
 @item set style filename background @var{color}
-Set the background to @var{color}.  Valid colors are @samp{none}
-(meaning the terminal's default color), @samp{black}, @samp{red},
-@samp{green}, @samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan},
-and@samp{white}.
+Set the background to @var{color}.  @var{color} can be @samp{none}
+(meaning the terminal's default color), a name of one of the eight standard
+colors of ISO/IEC 6429, index from 0 to 255 into terminal's color
+palette or a hexadecimal RGB triplet in @samp{#RRGGBB} format for
+24-bit TrueColor.
+
+Valid color names are @samp{black}, @samp{red}, @samp{green},
+@samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan}, and
+@samp{white}.
+
+Integers 0 to 7 are the synonyms for the standard colors.  Integers 8-15 are
+used for the so-called bright colors from the aixterm extended 16-color
+palette.  Integers 16-255 are the indexes into xterm extended 256-color palette
+(usually 6x6x6 cube plus gray ramp).  In general, 256-color palette is terminal
+dependent and sometimes can be changed with OSC 4 sequences, e.g.
+"\033]4;1;rgb:00/FF/00\033\\".
+
+It is the responsibility of the user to verify that the terminal supports
+the specified colors.
 
 @item set style filename foreground @var{color}
-Set the foreground to @var{color}.  Valid colors are @samp{none}
-(meaning the terminal's default color), @samp{black}, @samp{red},
-@samp{green}, @samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan},
-and@samp{white}.
+Set the foreground to @var{color}.  @var{color} can be given in the same ways
+as for the background.
 
 @item set style filename intensity @var{value}
 Set the intensity to @var{value}.  Valid intensities are @samp{normal}
diff --git a/gdb/doc/guile.texi b/gdb/doc/guile.texi
index a66e8bfe16b..0cb01663d6e 100644
--- a/gdb/doc/guile.texi
+++ b/gdb/doc/guile.texi
@@ -155,6 +155,7 @@  from the Guile interactive prompt.
 * I/O Ports in Guile::       GDB I/O ports
 * Memory Ports in Guile::    Accessing memory through ports and bytevectors
 * Iterators In Guile::       Basic iterator support
+* Colors In Guile::          Colorize output with Guile
 @end menu
 
 @node Basic Guile
@@ -408,6 +409,9 @@  as a symbol.
 
 @item <gdb:value>
 @xref{Values From Inferior In Guile}.
+
+@item <gdb:color>
+@xref{Colors In Guile}.
 @end table
 
 The following @value{GDBN} objects are managed internally so that the
@@ -2184,6 +2188,14 @@  The value is a filename.  This is just like
 @item PARAM_ENUM
 The value is a string, which must be one of a collection of string
 constants provided when the parameter is created.
+
+@item PARAM_COLOR
+The value is either a string or an unsigned integer.  Integer from 0 to 255
+means index into terminal's color palette.  String can be a hex RGB triplet in
+@samp{#RRGGBB} format or one of the following color names:
+@samp{none} (meaning the terminal's default color), @samp{black}, @samp{red},
+@samp{green}, @samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan},
+or @samp{white}.
 @end vtable
 
 @node Progspaces In Guile
@@ -3820,6 +3832,98 @@  Run @var{iterator} until the result of @code{(pred element)} is true
 and return that as the result.  Otherwise return @code{#f}.
 @end deffn
 
+@node Colors In Guile
+@subsubsection Colors In Guile
+
+@cindex guile colors
+@tindex <gdb:color>
+You can assign instance of @code{<gdb:color>} to the value of
+a @code{<gdb:parameter>} object created with @code{PARAM_COLOR}.
+
+@code{<gdb:color>} may refer to an index from color palette or contain
+components of a color from some colorspace.
+
+@deffn {Scheme Procedure} make-color @w{@r{[}value} @
+    @w{@r{[}#:color-space color-space@r{]}@r{]}}
+
+@var{value} is an integer index of a color in palette, tuple with color
+components or a string.  String can be a hex RGB triplet in @samp{#RRGGBB}
+format or one of the following color names:
+@samp{none} (meaning the terminal's default color), @samp{black}, @samp{red},
+@samp{green}, @samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan},
+or @samp{white}.
+
+@var{color-space} should be one of the @samp{COLORSPACE_} constants.  This
+argument tells @value{GDBN} which color space @var{value} belongs.
+@end deffn
+
+@deffn {Scheme Procedure} color? object
+Return @code{#t} if @var{object} is a @code{<gdb:color>} object.
+Otherwise return @code{#f}.
+@end deffn
+
+@deffn {Scheme Procedure} color-none? color
+Return @code{#t} if @var{color} is terminal's default.
+Otherwise return @code{#f}.
+@end deffn
+
+@deffn {Scheme Procedure} color-indexed? color
+Return @code{#t} if @var{color} is indexed, i.e. belongs to some palette.
+Otherwise return @code{#f}.
+@end deffn
+
+@deffn {Scheme Procedure} color-direct? color
+Return @code{#t} if @var{color} is direct in the sense of ISO/IEC 8613-6.
+Otherwise return @code{#f}.
+@end deffn
+
+@deffn {Scheme Procedure} color-string color
+Return the textual representation of a @code{<gdb:color>} object.
+@end deffn
+
+@deffn {Scheme Procedure} color-colorspace color
+Return the color space of a @code{<gdb:color>} object.
+@end deffn
+
+@deffn {Scheme Procedure} color-index color
+Return index of the color of a @code{<gdb:color>} object in a palette.
+@end deffn
+
+@deffn {Scheme Procedure} color-components color
+Return components of the direct @code{<gdb:color>} object.
+@end deffn
+
+@deffn {Scheme Procedure} color-escape-sequence color is_foreground
+Return string to change terminal's color to this.
+
+If @var{is_foreground} is @code{#t}, then the returned sequence will change
+foreground color.  Otherwise, the returned sequence will change background
+color.
+@end deffn
+
+When color is initialized, its color space must be specified.  The
+available color spaces are represented by constants defined in the @code{gdb}
+module:
+
+@vtable @code
+@item COLORSPACE_MONOCHROME
+Palette with only terminal's default color.
+
+@item COLORSPACE_ANSI_8COLOR
+Palette with eight standard colors of ISO/IEC 6429 "black", "red", "green", etc.
+
+@item COLORSPACE_AIXTERM_16COLOR
+Palette with 16 colors.  First eight are standard colors of ISO/IEC 6429
+"black", "red", "green", etc.  Next eight are their bright version.
+
+@item COLORSPACE_XTERM_256COLOR
+Palette with 256 colors.  First 16 are from COLORSPACE_AIXTERM_16COLOR.  Next
+216 colors are 6x6x6 RGB cube.  And last 24 colors form grayscale ramp.
+
+@item COLORSPACE_RGB_24BIT
+Direct 24-bit RGB colors.
+@end vtable
+
 @node Guile Auto-loading
 @subsection Guile Auto-loading
 @cindex guile auto-loading
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 22f0e6c6d0a..61c179a088d 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5230,6 +5230,11 @@  except the special value -1 is returned for the setting of ``unlimited''.
 @item gdb.PARAM_ENUM
 The value is a string, which must be one of a collection string
 constants provided when the parameter is created.
+
+@findex PARAM_COLOR
+@findex gdb.PARAM_COLOR
+@item gdb.PARAM_COLOR
+The value is @code{gdb.Color} instance.
 @end table
 
 @node Functions In Python
@@ -6999,6 +7004,98 @@  resolve this to the lazy string's character type, use the type's
 writable.
 @end defvar
 
+@node Colors In Python
+@subsection Python representation of colors
+
+@cindex colors in python
+@tindex gdb.Color
+You can assign instance of @code{Color} to the @code{value} of
+a @code{Parameter} instance created with @code{PARAM_COLOR}.
+
+@code{Color} may refer to an index from color palette or contain components
+of a color from some colorspace.
+
+@defun Color.__init__ (@r{[}@var{value} @r{[}, @var{color-space}@r{]}@r{]})
+
+@var{value} is @code{None} (meaning the terminal's default color),
+an integer index of a color in palette, tuple with color components
+or a string.  String can be a hex RGB triplet in @samp{#RRGGBB} format
+or one of the following color names:
+@samp{none} (meaning the terminal's default color), @samp{black}, @samp{red},
+@samp{green}, @samp{yellow}, @samp{blue}, @samp{magenta}, @samp{cyan},
+or @samp{white}.
+
+@var{color-space} should be one of the @samp{COLORSPACE_} constants.  This
+argument tells @value{GDBN} which color space @var{value} belongs.
+@end defun
+
+@defvar Color.is_none
+This atribute is boolean.  If its value is @code{True} then color is terminal's
+default.
+@end defvar
+
+@defvar Color.is_indexed
+This atribute is boolean.  If its value is @code{True} then color is indexed,
+i.e. belongs to some palette.
+@end defvar
+
+@defvar Color.is_direct
+This atribute is boolean.  If its value is @code{True} then this object
+describes direct colour in the sense of ISO/IEC 8613-6.
+@end defvar
+
+@defvar Color.index
+This attribute exist if @code{is_indexed} is @code{True}.  Its integer value is
+index of a color in a palette.
+@end defvar
+
+@defvar Color.components
+This attribute exist if @code{is_direct} is @code{True}.  Its value is tuple
+with integer components of a color.
+@end defvar
+
+@defun Color.escape_sequence (@var{self}, @var{is_foreground})
+Returns string to change terminal's color to this.
+
+If @var{is_foreground} is @code{True}, then the returned sequence will change
+foreground color.  Otherwise, the returned sequence will change background
+color.
+@end defun
+
+When color is initialized, its color space must be specified.  The
+available color spaces are represented by constants defined in the @code{gdb}
+module:
+
+@table @code
+@findex COLORSPACE_MONOCHROME
+@findex gdb.COLORSPACE_MONOCHROME
+@item gdb.COLORSPACE_MONOCHROME
+Palette with only terminal's default color.
+
+@findex COLORSPACE_ANSI_8COLOR
+@findex gdb.COLORSPACE_ANSI_8COLOR
+@item gdb.COLORSPACE_ANSI_8COLOR
+Palette with eight standard colors of ISO/IEC 6429 "black", "red", "green", etc.
+
+@findex COLORSPACE_AIXTERM_16COLOR
+@findex gdb.COLORSPACE_AIXTERM_16COLOR
+@item gdb.COLORSPACE_AIXTERM_16COLOR
+Palette with 16 colors.  First eight are standard colors of ISO/IEC 6429
+"black", "red", "green", etc.  Next eight are their bright version.
+
+@findex COLORSPACE_XTERM_256COLOR
+@findex gdb.COLORSPACE_XTERM_256COLOR
+@item gdb.COLORSPACE_XTERM_256COLOR
+Palette with 256 colors.  First 16 are from COLORSPACE_AIXTERM_16COLOR.  Next
+216 colors are 6x6x6 RGB cube.  And last 24 colors form grayscale ramp.
+
+@findex COLORSPACE_RGB_24BIT
+@findex gdb.COLORSPACE_RGB_24BIT
+@item gdb.COLORSPACE_RGB_24BIT
+Direct 24-bit RGB colors.
+
+@end table
+
 @node Architectures In Python
 @subsubsection Python representation of architectures
 @cindex Python architectures
diff --git a/gdb/guile/guile-internal.h b/gdb/guile/guile-internal.h
index dea78845088..b137713d133 100644
--- a/gdb/guile/guile-internal.h
+++ b/gdb/guile/guile-internal.h
@@ -439,6 +439,14 @@  extern int gdbscm_valid_command_class_p (int command_class);
 extern char *gdbscm_canonicalize_command_name (const char *name,
 					       int want_trailing_space);
 
+/* scm-color.c */
+
+extern SCM coscm_scm_from_color (const ui_file_style::color &color);
+
+extern int coscm_is_color (SCM scm);
+
+extern const ui_file_style::color & coscm_get_color (SCM color_scm);
+
 /* scm-frame.c */
 
 struct frame_smob;
@@ -617,6 +625,7 @@  extern void gdbscm_initialize_arches (void);
 extern void gdbscm_initialize_auto_load (void);
 extern void gdbscm_initialize_blocks (void);
 extern void gdbscm_initialize_breakpoints (void);
+extern void gdbscm_initialize_colors (void);
 extern void gdbscm_initialize_commands (void);
 extern void gdbscm_initialize_disasm (void);
 extern void gdbscm_initialize_exceptions (void);
diff --git a/gdb/guile/guile.c b/gdb/guile/guile.c
index 432093b6aea..5358b8330a3 100644
--- a/gdb/guile/guile.c
+++ b/gdb/guile/guile.c
@@ -594,6 +594,7 @@  initialize_gdb_module (void *data)
   gdbscm_initialize_auto_load ();
   gdbscm_initialize_blocks ();
   gdbscm_initialize_breakpoints ();
+  gdbscm_initialize_colors ();
   gdbscm_initialize_commands ();
   gdbscm_initialize_disasm ();
   gdbscm_initialize_frames ();
diff --git a/gdb/guile/scm-color.c b/gdb/guile/scm-color.c
new file mode 100644
index 00000000000..d0ba07342f8
--- /dev/null
+++ b/gdb/guile/scm-color.c
@@ -0,0 +1,443 @@ 
+/* GDB parameters implemented in Guile.
+
+   Copyright (C) 2008-2022 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "value.h"
+#include "charset.h"
+#include "cli/cli-decode.h"
+#include "completer.h"
+#include "language.h"
+#include "arch-utils.h"
+#include "guile-internal.h"
+
+/* A GDB color.  */
+
+struct color_smob
+{
+  /* This always appears first.  */
+  gdb_smob base;
+
+  /* Underlying value.  */
+  ui_file_style::color color;
+};
+
+static const char color_smob_name[] = "gdb:color";
+
+/* The tag Guile knows the color smob by.  */
+static scm_t_bits color_smob_tag;
+
+/* Keywords used by make-color.  */
+static SCM colorspace_keyword;
+
+static const char *coscm_colorspace_name (color_space colorspace);
+
+/* Administrivia for color smobs.  */
+
+static int
+coscm_print_color_smob (SCM self, SCM port, scm_print_state *pstate)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+
+  gdbscm_printf (port, "#<%s", color_smob_name);
+
+  gdbscm_printf (port, " %s", color.to_string ().c_str ());
+  gdbscm_printf (port, " %s", coscm_colorspace_name (color.colorspace ()));
+  scm_puts (">", port);
+
+  scm_remember_upto_here_1 (self);
+
+  /* Non-zero means success.  */
+  return 1;
+}
+
+/* Create an empty (uninitialized) color.  */
+
+static SCM
+coscm_make_color_smob (void)
+{
+  color_smob *c_smob = (color_smob *)
+    scm_gc_calloc (sizeof (color_smob), color_smob_name);
+  SCM c_scm;
+
+  c_smob->color = ui_file_style::color (ui_file_style::NONE);
+  c_scm = scm_new_smob (color_smob_tag, (scm_t_bits) c_smob);
+  gdbscm_init_gsmob (&c_smob->base);
+
+  return c_scm;
+}
+
+/* Return the <gdb:color> object that encapsulates COLOR.  */
+
+SCM
+coscm_scm_from_color (const ui_file_style::color &color)
+{
+  SCM c_scm = coscm_make_color_smob ();
+  color_smob *c_smob = (color_smob *) SCM_SMOB_DATA (c_scm);
+  c_smob->color = color;
+  return c_scm;
+}
+
+/* Return the color field of color_smob.  */
+
+const ui_file_style::color &
+coscm_get_color (SCM color_scm)
+{
+  SCM_ASSERT_TYPE (coscm_is_color (color_scm), color_scm, SCM_ARG1, FUNC_NAME,
+		   _("<gdb:color>"));
+
+  color_smob *c_smob = (color_smob *) SCM_SMOB_DATA (color_scm);
+  return c_smob->color;
+
+}
+
+/* Returns non-zero if SCM is a <gdb:color> object.  */
+
+int
+coscm_is_color (SCM scm)
+{
+  return SCM_SMOB_PREDICATE (color_smob_tag, scm);
+}
+
+/* (gdb:color? scm) -> boolean */
+
+static SCM
+gdbscm_color_p (SCM scm)
+{
+  return scm_from_bool (coscm_is_color (scm));
+}
+
+static const scheme_integer_constant colorspaces[] =
+{
+  { "COLORSPACE_MONOCHROME", (int) color_space::MONOCHROME },
+  { "COLORSPACE_ANSI_8COLOR", (int) color_space::ANSI_8COLOR },
+  { "COLORSPACE_AIXTERM_16COLOR", (int) color_space::AIXTERM_16COLOR },
+  { "COLORSPACE_XTERM_256COLOR", (int) color_space::XTERM_256COLOR },
+  { "COLORSPACE_RGB_24BIT", (int) color_space::RGB_24BIT },
+
+  END_INTEGER_CONSTANTS
+};
+
+/* Return non-zero if COLORSPACE is a valid color space.  */
+
+static int
+coscm_valid_colorspace_p (int colorspace)
+{
+  for (int i = 0; colorspaces[i].name != nullptr; ++i)
+    {
+      if (colorspaces[i].value == colorspace)
+	return 1;
+    }
+
+  return 0;
+}
+
+/* Return COLORSPACE as a string.  */
+
+static const char *
+coscm_colorspace_name (color_space colorspace)
+{
+  for (int i = 0; colorspaces[i].name != nullptr; ++i)
+    {
+      if (colorspaces[i].value == static_cast<int> (colorspace))
+	return colorspaces[i].name;
+    }
+
+  gdb_assert_not_reached ("bad color space");
+}
+
+/* Free function for a color_smob.  */
+static size_t
+coscm_free_color_smob (SCM self)
+{
+  (void) self;
+  return 0;
+}
+
+/* Color Scheme functions.  */
+
+/* (make-color [value
+     [#:color-space colorspace]]) -> <gdb:color>
+
+   VALUE is the value of the color.  It may be SCM_UNDEFINED, string, number
+   or list.
+
+   COLORSPACE is the color space of the VALUE.  It should be one of the
+   COLORSPACE_* constants defined in the gdb module.
+
+   The result is the <gdb:color> Scheme object.  */
+
+static SCM
+gdbscm_make_color (SCM value_scm, SCM rest)
+{
+  SCM colorspace_arg = SCM_UNDEFINED;
+  color_space colorspace = color_space::MONOCHROME;
+
+  scm_c_bind_keyword_arguments (FUNC_NAME, rest,
+				static_cast<scm_t_keyword_arguments_flags> (0),
+				colorspace_keyword, &colorspace_arg,
+				SCM_UNDEFINED);
+
+  if (!SCM_UNBNDP (colorspace_arg))
+    {
+      SCM_ASSERT_TYPE (scm_is_integer (colorspace_arg), colorspace_arg,
+		       SCM_ARG2, FUNC_NAME, _("int"));
+      int colorspace_int = scm_to_int (colorspace_arg);
+      if (coscm_valid_colorspace_p (colorspace_int))
+	colorspace = static_cast<color_space> (colorspace_int);
+      else
+	gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2,
+				   scm_from_int (colorspace_int),
+				   _("invalid colorspace argument"));
+    }
+
+  ui_file_style::color color = ui_file_style::NONE;
+  gdbscm_gdb_exception exc {};
+
+  try
+    {
+      if (SCM_UNBNDP (value_scm) || scm_is_integer (value_scm))
+	{
+	  int i = -1;
+	  if (scm_is_integer (value_scm))
+	    {
+	      i = scm_to_int (value_scm);
+	      if (i < 0)
+		gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG1, value_scm,
+					   _("negative color index"));
+	    }
+
+	  if (SCM_UNBNDP (colorspace_arg))
+	    color = ui_file_style::color (i);
+	  else
+	    color = ui_file_style::color (colorspace, i);
+	}
+      else if (gdbscm_is_true (scm_list_p (value_scm)))
+	{
+	  if (SCM_UNBNDP (colorspace_arg)
+	      || colorspace != color_space::RGB_24BIT)
+	    error (_("colorspace must be COLORSPACE_RGB_24BIT with "
+		   "value of list type."));
+
+	  if (scm_ilength (value_scm) != 3)
+	    error (_("List value with RGB must be of size 3."));
+
+	  uint8_t rgb[3] = {};
+	  int i = 0;
+	  for (; i < 3 && !scm_is_eq (value_scm, SCM_EOL); ++i)
+	    {
+	      SCM item = scm_car (value_scm);
+
+	      SCM_ASSERT_TYPE (scm_is_integer (item), item, SCM_ARG1, FUNC_NAME,
+			       _("int"));
+	      int component = scm_to_int (item);
+	      if (component < 0 || component > UINT8_MAX)
+		gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG1, item,
+					   _("invalid rgb component"));
+	      rgb[i] = static_cast<uint8_t> (component);
+
+	      value_scm = scm_cdr (value_scm);
+	    }
+
+	  gdb_assert (i == 3);
+
+	  color = ui_file_style::color (rgb[0], rgb[1], rgb[2]);
+	}
+      else if (scm_is_string (value_scm))
+	{
+	  SCM exception;
+
+	  gdb::unique_xmalloc_ptr<char> string
+	    = gdbscm_scm_to_host_string (value_scm, nullptr, &exception);
+	  if (string == nullptr)
+	    gdbscm_throw (exception);
+
+	  color = parse_var_color (string.get ());
+
+	  if (!SCM_UNBNDP (colorspace_arg) && colorspace != color.colorspace ())
+	    error (_("colorspace doesn't match to value."));
+
+	}
+      else
+	scm_wrong_type_arg_msg (FUNC_NAME, SCM_ARG1, value_scm,
+				"integer, string or list");
+    }
+  catch (const gdb_exception &except)
+    {
+      exc = unpack (except);
+    }
+
+  GDBSCM_HANDLE_GDB_EXCEPTION (exc);
+
+  return coscm_scm_from_color (color);
+}
+
+/* (color-string <gdb:color>) -> value */
+
+static SCM
+gdbscm_color_string (SCM self)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+  std::string s = color.to_string ();
+  return gdbscm_scm_from_host_string (s.c_str (), s.size ());
+}
+
+/* (color-colorspace <gdb:color>) -> value */
+
+static SCM
+gdbscm_color_colorspace (SCM self)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+  return scm_from_int (static_cast<int> (color.colorspace ()));
+}
+
+/* (color-none? scm) -> boolean */
+
+static SCM
+gdbscm_color_none_p (SCM self)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+  return scm_from_bool (color.is_none ());
+}
+
+/* (color-indexed? scm) -> boolean */
+
+static SCM
+gdbscm_color_indexed_p (SCM self)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+  return scm_from_bool (color.is_indexed ());
+}
+
+/* (color-direct? scm) -> boolean */
+
+static SCM
+gdbscm_color_direct_p (SCM self)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+  return scm_from_bool (color.is_direct ());
+}
+
+/* (color-index <gdb:color>) -> value */
+
+static SCM
+gdbscm_color_index (SCM self)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+
+  if (!color.is_indexed ())
+    gdbscm_misc_error (FUNC_NAME, SCM_ARG1, self, "color is not indexed");
+  return scm_from_int (color.get_value ());
+}
+
+/* (color-components <gdb:color>) -> value */
+
+static SCM
+gdbscm_color_components (SCM self)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+
+  if (!color.is_direct ())
+    gdbscm_misc_error (FUNC_NAME, SCM_ARG1, self, "color is not direct");
+
+  uint8_t rgb[3] = {};
+  color.get_rgb (rgb);
+  SCM red = scm_from_uint8 (rgb[0]);
+  SCM green = scm_from_uint8 (rgb[1]);
+  SCM blue = scm_from_uint8 (rgb[2]);
+  return scm_list_3 (red, green, blue);
+}
+
+/* (color-escape-sequence <gdb:color> is_fg) -> value */
+
+static SCM
+gdbscm_color_escape_sequence (SCM self, SCM is_fg_scm)
+{
+  const ui_file_style::color &color = coscm_get_color (self);
+  SCM_ASSERT_TYPE (gdbscm_is_bool (is_fg_scm), is_fg_scm, SCM_ARG2, FUNC_NAME,
+		   _("boolean"));
+  bool is_fg = gdbscm_is_true (is_fg_scm);
+  std::string s = color.to_ansi (is_fg);
+  return gdbscm_scm_from_host_string (s.c_str (), s.size ());
+}
+
+/* Initialize the Scheme color support.  */
+
+static const scheme_function color_functions[] =
+{
+  { "make-color", 0, 1, 1, as_a_scm_t_subr (gdbscm_make_color),
+    "\
+Make a GDB color object.\n\
+\n\
+  Arguments: [value\n\
+      [#:color-space <colorspace>]]\n\
+    value: The name of the color.  It may be string, number with color index\n\
+      or list with RGB components.\n\
+    colorspace: The color space of the color, one of COLORSPACE_*." },
+
+  { "color?", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_p),
+    "\
+Return #t if the object is a <gdb:color> object." },
+
+  { "color-none?", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_none_p),
+    "\
+Return #t if the <gdb:color> object has default color." },
+
+  { "color-indexed?", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_indexed_p),
+    "\
+Return #t if the <gdb:color> object is from indexed color space." },
+
+  { "color-direct?", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_direct_p),
+    "\
+Return #t if the <gdb:color> object has direct color (e.g. RGB, CMY, CMYK)." },
+
+  { "color-string", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_string),
+    "\
+Return the textual representation of a <gdb:color> object." },
+
+  { "color-colorspace", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_colorspace),
+    "\
+Return the color space of a <gdb:color> object." },
+
+  { "color-index", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_index),
+    "\
+Return index of the color of a <gdb:color> object in a palette." },
+
+  { "color-components", 1, 0, 0, as_a_scm_t_subr (gdbscm_color_components),
+    "\
+Return components of the direct <gdb:color> object." },
+
+  { "color-escape-sequence", 2, 0, 0,
+    as_a_scm_t_subr (gdbscm_color_escape_sequence),
+    "\
+Return string to change terminal's color to this." },
+
+  END_FUNCTIONS
+};
+
+void
+gdbscm_initialize_colors (void)
+{
+  color_smob_tag = gdbscm_make_smob_type (color_smob_name, sizeof (color_smob));
+  scm_set_smob_free (color_smob_tag, coscm_free_color_smob);
+  scm_set_smob_print (color_smob_tag, coscm_print_color_smob);
+
+  gdbscm_define_integer_constants (colorspaces, 1);
+  gdbscm_define_functions (color_functions, 1);
+
+  colorspace_keyword = scm_from_latin1_keyword ("color-space");
+}
diff --git a/gdb/guile/scm-param.c b/gdb/guile/scm-param.c
index 3a1e1583f54..749c5ea1f7d 100644
--- a/gdb/guile/scm-param.c
+++ b/gdb/guile/scm-param.c
@@ -47,6 +47,9 @@  union pascm_variable
 
   /* Hold a string, for enums.  */
   const char *cstringval;
+
+  /* Hold a color.  */
+  ui_file_style::color color;
 };
 
 /* A GDB parameter.
@@ -130,6 +133,7 @@  enum scm_param_types
   param_optional_filename,
   param_filename,
   param_enum,
+  param_color,
 };
 
 /* Translation from Guile parameters to GDB variable types.  Keep in the
@@ -155,7 +159,8 @@  param_to_var[] =
   { var_string_noescape },
   { var_optional_filename },
   { var_filename },
-  { var_enum }
+  { var_enum },
+  { var_color }
 };
 
 /* Wraps a setting around an existing param_smob.  This abstraction
@@ -179,6 +184,8 @@  make_setting (param_smob *s)
     return setting (type, s->value.stringval);
   else if (var_type_uses<const char *> (type))
     return setting (type, &s->value.cstringval);
+  else if (var_type_uses<ui_file_style::color> (s->type))
+    return setting (s->type, &s->value.color);
   else
     gdb_assert_not_reached ("unhandled var type");
 }
@@ -239,10 +246,9 @@  static SCM
 pascm_make_param_smob (void)
 {
   param_smob *p_smob = (param_smob *)
-    scm_gc_malloc (sizeof (param_smob), param_smob_name);
+    scm_gc_calloc (sizeof (param_smob), param_smob_name);
   SCM p_scm;
 
-  memset (p_smob, 0, sizeof (*p_smob));
   p_smob->cmd_class = no_class;
   p_smob->type = var_boolean; /* ARI: var_boolean */
   p_smob->set_func = SCM_BOOL_F;
@@ -511,6 +517,13 @@  add_setshow_generic (enum var_types param_type,
 				       set_list, show_list);
       break;
 
+    case var_color:
+      commands = add_setshow_color_cmd (cmd_name, cmd_class, &self->value.color,
+					set_doc, show_doc, help_doc,
+					set_func, show_func,
+					set_list, show_list);
+      break;
+
     default:
       gdb_assert_not_reached ("bad param_type value");
     }
@@ -588,6 +601,7 @@  static const scheme_integer_constant parameter_types[] =
   { "PARAM_OPTIONAL_FILENAME", param_optional_filename },
   { "PARAM_FILENAME", param_filename },
   { "PARAM_ENUM", param_enum },
+  { "PARAM_COLOR", param_color },
 
   END_INTEGER_CONSTANTS
 };
@@ -650,6 +664,11 @@  pascm_param_value (const setting &var, int arg_pos, const char *func_name)
 	return gdbscm_scm_from_host_string (str, strlen (str));
       }
 
+    case var_color:
+      {
+	return coscm_scm_from_color (var.get<ui_file_style::color> ());
+      }
+
     case var_boolean:
       {
 	if (var.get<bool> ())
@@ -764,6 +783,12 @@  pascm_set_param_value_x (param_smob *p_smob,
 	break;
       }
 
+    case var_color:
+      SCM_ASSERT_TYPE (coscm_is_color (value), value, arg_pos, func_name,
+		       _("<gdb:color>"));
+      var.set<ui_file_style::color> (coscm_get_color (value));
+      break;
+
     case var_boolean:
       SCM_ASSERT_TYPE (gdbscm_is_bool (value), value, arg_pos, func_name,
 		       _("boolean"));
@@ -1050,6 +1075,8 @@  gdbscm_make_parameter (SCM name_scm, SCM rest)
   scm_set_smob_free (parameter_smob_tag, pascm_free_parameter_smob);
   if (var_type_uses<std::string> (p_smob->type))
     p_smob->value.stringval = new std::string;
+  else if (var_type_uses<ui_file_style::color> (p_smob->type))
+    p_smob->value.color = ui_file_style::NONE;
 
   if (initial_value_arg_pos > 0)
     {
diff --git a/gdb/python/py-color.c b/gdb/python/py-color.c
new file mode 100644
index 00000000000..1d72cc3e7d1
--- /dev/null
+++ b/gdb/python/py-color.c
@@ -0,0 +1,347 @@ 
+/* Python interface to ui_file_style::color objects.
+
+   Copyright (C) 2008-2022 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+
+#include "python-internal.h"
+#include "py-color.h"
+#include "cli/cli-decode.h"
+
+/* Colorspace constants and their values.  */
+static struct {
+  const char *name;
+  color_space value;
+} colorspace_constants[] =
+{
+  { "COLORSPACE_MONOCHROME", color_space::MONOCHROME },
+  { "COLORSPACE_ANSI_8COLOR", color_space::ANSI_8COLOR },
+  { "COLORSPACE_AIXTERM_16COLOR", color_space::AIXTERM_16COLOR },
+  { "COLORSPACE_XTERM_256COLOR", color_space::XTERM_256COLOR },
+  { "COLORSPACE_RGB_24BIT", color_space::RGB_24BIT },
+};
+
+/* A color.  */
+struct colorpy_object
+{
+  PyObject_HEAD
+
+  /* Underlying value.  */
+  ui_file_style::color color;
+};
+
+extern PyTypeObject colorpy_object_type;
+
+/* See py-color.h.  */
+gdbpy_ref<>
+create_color_object (const ui_file_style::color &color)
+{
+  gdbpy_ref<colorpy_object> color_obj (PyObject_New (colorpy_object,
+						     &colorpy_object_type));
+
+  if (color_obj == nullptr)
+    return nullptr;
+
+  color_obj->color = color;
+  return gdbpy_ref<> ((PyObject *) color_obj.release ());
+}
+
+/* See py-color.h.  */
+bool
+gdbpy_is_color (PyObject *obj)
+{
+  return PyObject_IsInstance (obj, (PyObject *)&colorpy_object_type);
+}
+
+/* See py-color.h.  */
+const ui_file_style::color &
+gdbpy_get_color (PyObject *obj)
+{
+  gdb_assert (gdbpy_is_color (obj));
+  colorpy_object *self = (colorpy_object *) obj;
+  return self->color;
+}
+
+/* Get an attribute.  */
+static PyObject *
+get_attr (PyObject *obj, PyObject *attr_name)
+{
+  if (! PyUnicode_Check (attr_name))
+    return PyObject_GenericGetAttr (obj, attr_name);
+
+  colorpy_object *self = (colorpy_object *) obj;
+  const ui_file_style::color &color = self->color;
+
+  if (! PyUnicode_CompareWithASCIIString (attr_name, "colorspace"))
+    {
+      int value = static_cast<int> (color.colorspace ());
+      return gdb_py_object_from_longest (value).release ();
+    }
+
+  if (! PyUnicode_CompareWithASCIIString (attr_name, "is_none"))
+    return PyBool_FromLong (color.is_none ());
+
+  if (! PyUnicode_CompareWithASCIIString (attr_name, "is_indexed"))
+    return PyBool_FromLong (color.is_indexed ());
+
+  if (! PyUnicode_CompareWithASCIIString (attr_name, "is_direct"))
+    return PyBool_FromLong (color.is_direct ());
+
+  if (color.is_indexed ()
+      && ! PyUnicode_CompareWithASCIIString (attr_name, "index"))
+    return gdb_py_object_from_longest (color.get_value ()).release ();
+
+  if (color.is_direct ()
+      && ! PyUnicode_CompareWithASCIIString (attr_name, "components"))
+    {
+      uint8_t rgb[3];
+      color.get_rgb (rgb);
+
+      gdbpy_ref<> rgb_objects[3];
+      for (int i = 0; i < 3; ++i)
+	{
+	  rgb_objects[i] = gdb_py_object_from_ulongest (rgb[i]);
+	  if (rgb_objects[i] == nullptr)
+	    return nullptr;
+	}
+
+      PyObject *comp = PyTuple_New (3);
+      if (comp == nullptr)
+	return nullptr;
+
+      for (int i = 0; i < 3; ++i)
+	PyTuple_SET_ITEM (comp, i, rgb_objects[i].release ());
+
+      return comp;
+    }
+
+  return PyObject_GenericGetAttr (obj, attr_name);
+}
+
+/* Implementation of Color.escape_sequence (self, is_fg) -> str.  */
+
+static PyObject *
+colorpy_escape_sequence (PyObject *self, PyObject *is_fg_obj)
+{
+  if (!gdbpy_is_color (self))
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("Object is not gdb.Color."));
+      return nullptr;
+    }
+
+  if (! PyBool_Check (is_fg_obj))
+    {
+      PyErr_SetString (PyExc_RuntimeError,
+		       _("A boolean argument is required."));
+      return nullptr;
+    }
+
+  bool is_fg = PyObject_IsTrue (is_fg_obj);
+  std::string s = gdbpy_get_color (self).to_ansi (is_fg);
+
+  return host_string_to_python_string (s.c_str ()).release ();
+}
+
+/* Object initializer; fills color with value.
+
+   Use: __init__(VALUE = None, COLORSPACE = None)
+
+   VALUE is a string, integer, RGB-tuple or None.
+
+   COLORSPACE is the color space index.
+
+   Returns -1 on error, with a python exception set.  */
+
+static int
+colorpy_init (PyObject *self, PyObject *args, PyObject *kwds)
+{
+  colorpy_object *obj = (colorpy_object *) self;
+  PyObject *value_obj = nullptr;
+  PyObject *colorspace_obj = nullptr;
+  color_space colorspace = color_space::MONOCHROME;
+
+  if (! PyArg_ParseTuple (args, "|OO", &value_obj, &colorspace_obj))
+    return -1;
+
+  try
+    {
+      if (colorspace_obj)
+	{
+	  if (PyLong_Check (colorspace_obj))
+	    {
+	      long colorspace_id = -1;
+	      if (! gdb_py_int_as_long (colorspace_obj, &colorspace_id))
+		return -1;
+	      if (colorspace_id < INT_MIN || colorspace_id > INT_MAX)
+		error (_("colorspace %ld is out of range."), colorspace_id);
+	      colorspace = static_cast<color_space> (colorspace_id);
+	    }
+	  else if (colorspace_obj == Py_None)
+	    colorspace_obj = nullptr;
+	  else
+	    error (_("colorspace must be None or integer"));
+	}
+
+      if (value_obj == nullptr || value_obj == Py_None)
+	  obj->color = ui_file_style::color (colorspace, -1);
+      else if (PyLong_Check (value_obj))
+	{
+	  long value = -1;
+	  if (! gdb_py_int_as_long (value_obj, &value))
+	    return -1;
+	  if (value < 0 || value > INT_MAX)
+	    error (_("value %ld is out of range."), value);
+	  if (colorspace_obj)
+	    obj->color = ui_file_style::color (colorspace, value);
+	  else
+	    obj->color = ui_file_style::color (value);
+	}
+      else if (PyTuple_Check (value_obj))
+	{
+	  if (colorspace_obj == nullptr || colorspace != color_space::RGB_24BIT)
+	    error (_("colorspace must be gdb.COLORSPACE_RGB_24BIT with "
+		   "value of tuple type."));
+	  Py_ssize_t tuple_size = PyTuple_Size (value_obj);
+	  if (tuple_size != 3)
+	    error (_("Tuple value with RGB must be of size 3."));
+	  uint8_t rgb[3];
+	  for (int i = 0; i < 3; ++i)
+	    {
+	      PyObject *item = PyTuple_GetItem (value_obj, i);
+	      if (!PyLong_Check (item))
+		error (_("Item %d of an RGB tuple must be integer."), i);
+	      long item_value = -1;
+	      if (!gdb_py_int_as_long (item, &item_value))
+		return -1;
+	      if (item_value < 0 || item_value > UINT8_MAX)
+		error (_("RGB item %ld is out of byte range."), item_value);
+	      rgb[i] = static_cast<uint8_t> (item_value);
+	    }
+
+	  obj->color = ui_file_style::color (rgb[0], rgb[1], rgb[2]);
+	}
+      else if (PyUnicode_Check (value_obj))
+	{
+	  gdb::unique_xmalloc_ptr<char>
+	    str (python_string_to_host_string (value_obj));
+	  if (!str)
+	    return -1;
+	  obj->color = parse_var_color (str.get());
+
+	  if (colorspace_obj != nullptr
+	      && colorspace != obj->color.colorspace ())
+	    error (_("colorspace doesn't match to value."));
+	}
+      else
+	error (_("value must be one of None, integer, tuple or str."));
+    }
+  catch (const gdb_exception &except)
+    {
+      gdbpy_convert_exception (except);
+      return -1;
+    }
+
+  Py_INCREF (self);
+  return 0;
+}
+
+/* Deallocate function for a gdb.Color.  */
+
+static void
+colorpy_dealloc (PyObject *)
+{
+}
+
+static PyObject *
+colorpy_str (PyObject *self)
+{
+  colorpy_object *obj = reinterpret_cast<colorpy_object *> (self);
+
+  return PyUnicode_FromString (obj->color.to_string ().c_str ());
+}
+
+/* Initialize the 'color' module.  */
+static int
+gdbpy_initialize_color (void)
+{
+  colorpy_object_type.tp_new = PyType_GenericNew;
+  if (PyType_Ready (&colorpy_object_type) < 0)
+    return -1;
+
+  for (auto & pair : colorspace_constants)
+      if (PyModule_AddIntConstant (gdb_module, pair.name,
+				   static_cast<long> (pair.value)) < 0)
+	return -1;
+
+  return gdb_pymodule_addobject (gdb_module, "Color",
+				 (PyObject *) &colorpy_object_type);
+}
+
+/* Color methods.  */
+
+static PyMethodDef color_methods[] =
+{
+  { "escape_sequence", colorpy_escape_sequence, METH_O,
+    "escape_sequence (is_foreground) -> str.\n\
+Return the ANSI escape sequence for this color.\n\
+IS_FOREGROUND indicates whether this is a foreground or background color."},
+  {nullptr}
+};
+
+PyTypeObject colorpy_object_type =
+{
+  PyVarObject_HEAD_INIT (nullptr, 0)
+  "gdb.Color",			  /*tp_name*/
+  sizeof (colorpy_object),	  /*tp_basicsize*/
+  0,				  /*tp_itemsize*/
+  colorpy_dealloc,		  /*tp_dealloc*/
+  0,				  /*tp_print*/
+  0,				  /*tp_getattr*/
+  0,				  /*tp_setattr*/
+  0,				  /*tp_compare*/
+  0,				  /*tp_repr*/
+  0,				  /*tp_as_number*/
+  0,				  /*tp_as_sequence*/
+  0,				  /*tp_as_mapping*/
+  0,				  /*tp_hash */
+  0,				  /*tp_call*/
+  colorpy_str,			  /*tp_str*/
+  get_attr,			  /*tp_getattro*/
+  0,				  /*tp_setattro*/
+  0,				  /*tp_as_buffer*/
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+  "GDB color object",	  	  /* tp_doc */
+  0,				  /* tp_traverse */
+  0,				  /* tp_clear */
+  0,				  /* tp_richcompare */
+  0,				  /* tp_weaklistoffset */
+  0,				  /* tp_iter */
+  0,				  /* tp_iternext */
+  color_methods,		  /* tp_methods */
+  0,				  /* tp_members */
+  0,				  /* tp_getset */
+  0,				  /* tp_base */
+  0,				  /* tp_dict */
+  0,				  /* tp_descr_get */
+  0,				  /* tp_descr_set */
+  0,				  /* tp_dictoffset */
+  colorpy_init,			  /* tp_init */
+  0,				  /* tp_alloc */
+};
+
+GDBPY_INITIALIZE_FILE (gdbpy_initialize_color);
diff --git a/gdb/python/py-color.h b/gdb/python/py-color.h
new file mode 100644
index 00000000000..95726faa19a
--- /dev/null
+++ b/gdb/python/py-color.h
@@ -0,0 +1,35 @@ 
+/* Python interface to ui_file_style::color objects.
+
+   Copyright (C) 2009-2022 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef PYTHON_PY_COLOR_H
+#define PYTHON_PY_COLOR_H
+
+#include "python-internal.h"
+#include "ui-style.h"
+
+/* Create a new gdb.Color object from COLOR.  */
+extern gdbpy_ref<> create_color_object (const ui_file_style::color &color);
+
+/* Check if OBJ is instance of a gdb.Color type.  */
+extern bool gdbpy_is_color (PyObject *obj);
+
+/* Extracts value from OBJ object of gdb.Color type.  */
+extern const ui_file_style::color & gdbpy_get_color (PyObject *obj);
+
+#endif /* PYTHON_PY_COLOR_H */
diff --git a/gdb/python/py-param.c b/gdb/python/py-param.c
index e7032f7758c..7f1188b08e1 100644
--- a/gdb/python/py-param.c
+++ b/gdb/python/py-param.c
@@ -26,6 +26,7 @@ 
 #include "completer.h"
 #include "language.h"
 #include "arch-utils.h"
+#include "py-color.h"
 
 /* Python parameter types as in PARM_CONSTANTS below.  */
 
@@ -43,6 +44,7 @@  enum py_param_types
   param_zuinteger,
   param_zuinteger_unlimited,
   param_enum,
+  param_color,
 };
 
 /* Translation from Python parameters to GDB variable types.  Keep in the
@@ -69,7 +71,8 @@  param_to_var[] =
   { var_integer },
   { var_uinteger },
   { var_pinteger, pinteger_unlimited_literals },
-  { var_enum }
+  { var_enum },
+  { var_color }
 };
 
 /* Parameter constants and their values.  */
@@ -90,6 +93,7 @@  static struct {
   { "PARAM_ZUINTEGER", param_zuinteger },
   { "PARAM_ZUINTEGER_UNLIMITED", param_zuinteger_unlimited },
   { "PARAM_ENUM", param_enum },
+  { "PARAM_COLOR", param_color },
   { NULL, 0 }
 };
 
@@ -114,6 +118,9 @@  union parmpy_variable
 
   /* Hold a string, for enums.  */
   const char *cstringval;
+
+  /* Hold a color.  */
+  ui_file_style::color color;
 };
 
 /* A GDB parameter.  */
@@ -157,6 +164,8 @@  make_setting (parmpy_object *s)
     return setting (type, s->value.stringval);
   else if (var_type_uses<const char *> (type))
     return setting (type, &s->value.cstringval);
+  else if (var_type_uses<ui_file_style::color> (s->type))
+    return setting (s->type, &s->value.color);
   else
     gdb_assert_not_reached ("unhandled var type");
 }
@@ -248,6 +257,19 @@  set_parameter_value (parmpy_object *self, PyObject *value)
 	break;
       }
 
+    case var_color:
+      {
+	if (gdbpy_is_color (value))
+	  self->value.color = gdbpy_get_color (value);
+	else
+	  {
+	    PyErr_SetString (PyExc_RuntimeError,
+			     _("color argument must be a gdb.Color object."));
+	    return -1;
+	  }
+      }
+      break;
+
     case var_boolean:
       if (! PyBool_Check (value))
 	{
@@ -707,6 +729,15 @@  add_setshow_generic (enum var_types type, const literal_def *extra_literals,
 				       get_show_value, set_list, show_list);
       break;
 
+    case var_color:
+      /* Initialize the value, just in case.  */
+      self->value.color = ui_file_style::NONE;
+      commands = add_setshow_color_cmd (cmd_name.get (), cmdclass,
+					&self->value.color, set_doc,
+					show_doc, help_doc, get_set_value,
+					get_show_value, set_list, show_list);
+      break;
+
     default:
       gdb_assert_not_reached ("Unhandled parameter class.");
     }
@@ -830,7 +861,8 @@  parmpy_init (PyObject *self, PyObject *args, PyObject *kwds)
       && parmclass != param_string && parmclass != param_string_noescape
       && parmclass != param_optional_filename && parmclass != param_filename
       && parmclass != param_zinteger && parmclass != param_zuinteger
-      && parmclass != param_zuinteger_unlimited && parmclass != param_enum)
+      && parmclass != param_zuinteger_unlimited && parmclass != param_enum
+      && parmclass != param_color)
     {
       PyErr_SetString (PyExc_RuntimeError,
 		       _("Invalid parameter class argument."));
@@ -854,7 +886,7 @@  parmpy_init (PyObject *self, PyObject *args, PyObject *kwds)
   extra_literals = param_to_var[parmclass].extra_literals;
   obj->type = type;
   obj->extra_literals = extra_literals;
-  memset (&obj->value, 0, sizeof (obj->value));
+  obj->value = {}; /* zeros initialization */
 
   if (var_type_uses<std::string> (obj->type))
     obj->value.stringval = new std::string;
diff --git a/gdb/python/python.c b/gdb/python/python.c
index a2ce1f6545a..856bc867a4b 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -35,6 +35,7 @@ 
 #include "location.h"
 #include "run-on-main-thread.h"
 #include "observable.h"
+#include "py-color.h"
 
 #if GDB_SELF_TEST
 #include "gdbsupport/selftest.h"
@@ -506,6 +507,12 @@  gdbpy_parameter_value (const setting &var)
 	return host_string_to_python_string (str).release ();
       }
 
+    case var_color:
+      {
+	const ui_file_style::color &color = var.get<ui_file_style::color> ();
+	return create_color_object (color).release ();
+      }
+
     case var_boolean:
       {
 	if (var.get<bool> ())
diff --git a/gdb/testsuite/gdb.base/style.exp b/gdb/testsuite/gdb.base/style.exp
index aff654537ac..adcfd968b6b 100644
--- a/gdb/testsuite/gdb.base/style.exp
+++ b/gdb/testsuite/gdb.base/style.exp
@@ -70,6 +70,9 @@  proc run_style_tests { } {
     global currently_disabled_style decimal hex
 
     with_ansi_styling_terminal {
+	setenv TERM xterm-256color
+	setenv COLORTERM truecolor
+
 	# Restart GDB with the correct TERM variable setting, this
 	# means that GDB will enable styling.
 	clean_restart_and_disable "restart 1" ${binfile}
@@ -309,6 +312,21 @@  proc run_style_tests { } {
 	set url [limited_style "http:.*html" file]
 	gdb_test "show version" "${vers}.*<$url>.*" \
 	    "'show version' is styled"
+
+	if { $currently_disabled_style != "version" } {
+	    # Check that colors in styling can be set as integer and as RGB hex
+	    # triplet.  Check that the version string is styled in the output of
+	    # 'show version' according to the set colors.
+	    gdb_test_no_output "set style version intensity normal"
+	    gdb_test_no_output "set style version background 255"
+	    gdb_test_no_output "set style version foreground #FED210"
+	    gdb_test "show style version background" \
+		"The \033\\\[38;2;254;210;16;48;5;255;22;27m.*\".*version.*\".*style.*\033\\\[m background color is: 255" \
+		"Version's 256-color background style"
+	    gdb_test "show style version foreground" \
+		"The \033\\\[38;2;254;210;16;48;5;255;22;27m.*\".*version.*\".*style.*\033\\\[m foreground color is: #FED210" \
+		"Version's TrueColor foreground style"
+	}
     }
 }
 
@@ -485,6 +503,179 @@  proc test_startup_version_string { } {
     gdb_test "" "${vers}.*" "version is styled at startup"
 }
 
+# Color support is disabled when TERM=dumb and COLORTERM="".
+# All colors are approximated to none when set.
+proc test_colorsupport_monochrome { } {
+    with_test_prefix "colorsupport_monochrome" {
+	with_ansi_styling_terminal {
+	    setenv TERM dumb
+	    gdb_exit
+	    gdb_start
+	    gdb_test "print \$_colorsupport" \
+		"\\\$1 = \"monochrome\"" \
+		"color support is monochrome"
+	    gdb_test_no_output "set style enabled off"
+	    gdb_test_no_output "set style filename foreground none"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: none" \
+	      "none without approximation"
+	    gdb_test_no_output "set style filename foreground blue"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: none" \
+	      "blue approximated to none"
+	}
+    }
+}
+
+# Color support is limited by 8 colors when TERM=ansi and COLORTERM="".
+# All colors are approximated to basic colors when set.
+proc test_colorsupport_8color { } {
+    with_test_prefix "colorsupport_8color" {
+	with_ansi_styling_terminal {
+	    gdb_exit
+	    gdb_start
+	    gdb_test "print \$_colorsupport" \
+		"\033\\\[.*m\\\$1\033\\\[m = \"monochrome,ansi_8color\"" \
+		"color support is 8 color"
+	    gdb_test_no_output "set style enabled off"
+	    gdb_test_no_output "set style filename foreground none"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: none" \
+	      "none without approximation"
+	    gdb_test_no_output "set style filename foreground yellow"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: yellow" \
+	      "yellow without approximation"
+	    gdb_test_no_output "set style filename foreground 9"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: red" \
+	      "9 approximated to red"
+	    gdb_test_no_output "set style filename foreground 118"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: green" \
+	      "118 approximated to green"
+	    gdb_test_no_output "set style filename foreground #000ABC"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: blue" \
+	      "#000ABC approximated to blue"
+	}
+    }
+}
+
+# Color support is limited by 256 colors when TERM=xterm-256color and
+# COLORTERM="".  All colors are approximated by 256 color palette when set.
+proc test_colorsupport_256color { } {
+    with_test_prefix "colorsupport_256color" {
+	with_ansi_styling_terminal {
+	    setenv TERM xterm-256color
+	    gdb_exit
+	    gdb_start
+	    gdb_test "print \$_colorsupport" \
+		"\033\\\[.*m\\\$1\033\\\[m = \"monochrome,ansi_8color,aixterm_16color,xterm_256color\"" \
+		"color support is 256 color"
+	    gdb_test_no_output "set style enabled off"
+	    gdb_test_no_output "set style filename foreground none"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: none" \
+	      "none without approximation"
+	    gdb_test_no_output "set style filename foreground red"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: red" \
+	      "red without approximation"
+	    gdb_test_no_output "set style filename foreground 9"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: 9" \
+	      "9 without approximation"
+	    gdb_test_no_output "set style filename foreground 118"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: 118" \
+	      "118 without approximation"
+	    gdb_test_no_output "set style filename foreground #CD00CD"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: 5" \
+	      "#CD00CD approximated to 5"
+	    gdb_test_no_output "set style filename foreground #FFAF12"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: 214" \
+	      "#FFAF12 approximated to 214"
+	}
+    }
+}
+
+# Color support is limited by 16777216 colors when TERM=xterm-256color and
+# COLORTERM="truecolor".  No approximation needed.
+proc test_colorsupport_truecolor { } {
+    with_test_prefix "colorsupport_truecolor" {
+	with_ansi_styling_terminal {
+	    setenv TERM xterm-256color
+	    setenv COLORTERM truecolor
+	    gdb_exit
+	    gdb_start
+	    gdb_test "print \$_colorsupport" \
+		"\033\\\[.*m\\\$1\033\\\[m = \"monochrome,ansi_8color,aixterm_16color,xterm_256color,rgb_24bit\"" \
+		"color support is truecolor"
+	    gdb_test_no_output "set style enabled off"
+	    gdb_test_no_output "set style filename foreground none"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: none" \
+	      "none without approximation"
+	    gdb_test_no_output "set style filename foreground red"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: red" \
+	      "red without approximation"
+	    gdb_test_no_output "set style filename foreground 9"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: 9" \
+	      "9 without approximation"
+	    gdb_test_no_output "set style filename foreground 118"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: 118" \
+	      "118 without approximation"
+	    gdb_test_no_output "set style filename foreground #CD00CD"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: #CD00CD" \
+	      "#CD00CD without approximation"
+	}
+    }
+}
+
+# Color support is limited by 16777216 colors when TERM=dumb and
+# COLORTERM=24bit.  No approximation needed.  Basic colors replaced with RGB.
+proc test_colorsupport_truecolor_only { } {
+    with_test_prefix "colorsupport_truecolor_only" {
+	with_ansi_styling_terminal {
+	    setenv TERM dumb
+	    setenv COLORTERM truecolor
+	    gdb_exit
+	    gdb_start
+	    gdb_test "print \$_colorsupport" \
+		"\\\$1 = \"monochrome,rgb_24bit\"" \
+		"color support is truecolor only"
+	    gdb_test_no_output "set style enabled off"
+	    gdb_test_no_output "set style filename foreground none"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: none" \
+	      "none without approximation"
+	    gdb_test_no_output "set style filename foreground red"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: #DE382B" \
+	      "red replaced by #DE382B"
+	    gdb_test_no_output "set style filename foreground 9"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: #FF0000" \
+	      "9 replaced by #FF0000"
+	    gdb_test_no_output "set style filename foreground 118"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: #87FF00" \
+	      "118 replaced by #87FF00"
+	    gdb_test_no_output "set style filename foreground #CD00CD"
+	    gdb_test "show style filename foreground" \
+	      "The \"filename\" style foreground color is: #CD00CD" \
+	      "#CD00CD without approximation"
+	}
+    }
+}
+
 # Check to see if the Python styling of disassembler output is
 # expected or not, this styling requires Python support in GDB, and
 # the Python pygments module to be available.
@@ -519,3 +710,9 @@  test_disassembler_error_handling
 
 # Finally, check the styling of the version string during startup.
 test_startup_version_string
+
+test_colorsupport_monochrome
+test_colorsupport_8color
+test_colorsupport_256color
+test_colorsupport_truecolor
+test_colorsupport_truecolor_only
diff --git a/gdb/testsuite/gdb.guile/scm-color.exp b/gdb/testsuite/gdb.guile/scm-color.exp
new file mode 100644
index 00000000000..e5773650f2f
--- /dev/null
+++ b/gdb/testsuite/gdb.guile/scm-color.exp
@@ -0,0 +1,110 @@ 
+# Copyright (C) 2010-2022 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.
+# It tests GDB parameter support in Guile.
+
+load_lib gdb-guile.exp
+
+require allow_guile_tests
+
+clean_restart
+
+gdb_install_guile_utils
+gdb_install_guile_module
+
+gdb_test_no_output [concat "guile (define (print_color_attrs c) " \
+    "(display (color-string c)) (display \" \") " \
+    "(display (color-colorspace c)) (display \" \") " \
+    "(display (color-none? c)) (display \" \") " \
+    "(display (color-indexed? c)) (display \" \") " \
+    "(display (color-direct? c)) (newline))"] \
+    "print_color_attrs helper"
+
+gdb_test "guile (print_color_attrs (make-color))" \
+    "none 0 #t #f #f" \
+    "print attrs of a color without params"
+
+gdb_test_no_output "guile (define c (make-color \"green\"))" \
+    "create color from basic name string"
+gdb_test "guile (print_color_attrs c)" "green 1 #f #t #f" \
+    "print attrs of a basic color name"
+gdb_test "guile (print (color-index c))" "2" \
+    "print index of a basic color name"
+
+gdb_test_no_output "guile (define c (make-color 2))" \
+    "create color from basic index"
+gdb_test "guile (print_color_attrs c)" "green 1 #f #t #f" \
+    "print attrs of a basic color"
+gdb_test "guile (print (color-index c))" "2" \
+    "print index of a basic color"
+
+gdb_test_no_output "guile (define c (make-color 14))" \
+    "create color from integer 14"
+gdb_test "guile (print_color_attrs c)" "14 2 #f #t #f" \
+    "print attrs of an color 14"
+gdb_test "guile (print (color-index c))" "14" \
+    "print index of color 14"
+
+gdb_test_no_output "guile (define c (make-color 2 #:color-space COLORSPACE_ANSI_8COLOR))" \
+    "create color from basic index and ansi colorspace"
+gdb_test "guile (print_color_attrs c)" "green 1 #f #t #f" \
+    "print attrs of a basic color with ansi colorspace"
+gdb_test "guile (print (color-index c))" "2" \
+    "print index of a basic color with ansi colorspace"
+
+gdb_test_no_output "guile (define c (make-color 2 #:color-space COLORSPACE_XTERM_256COLOR))" \
+    "create color from basic index and xterm256 colorspace"
+gdb_test "guile (print_color_attrs c)" "2 3 #f #t #f" \
+    "print attrs of a basic color with xterm256 colorspace"
+gdb_test "guile (print (color-index c))" "2" \
+    "print index of a basic color with xterm256 colorspace"
+
+gdb_test_no_output "guile (define c (make-color '(171 205 239) #:color-space COLORSPACE_RGB_24BIT))" \
+    "create color from rgb components"
+gdb_test "guile (print_color_attrs c)" "#ABCDEF 4 #f #f #t" \
+    "print attrs of an RGB color"
+gdb_test "guile (print (color-components c))" "\\(171 205 239\\)" \
+    "print components of an RGB color"
+
+gdb_test_no_output "guile (define c (make-color \"none\"))" \
+    "create color from string none"
+gdb_test "guile (print_color_attrs c)" "none 0 #t #f #f" \
+    "print attrs of a color none"
+
+gdb_test_no_output "guile (define c (make-color \"254\"))" \
+    "create color from string 254"
+gdb_test "guile (print_color_attrs c)" "254 3 #f #t #f" \
+    "print attrs of an color 254"
+gdb_test "guile (print (color-index c))" "254" \
+    "print index of color 254"
+
+gdb_test_no_output "guile (define c_none (make-color \"none\"))" \
+    "save default color"
+gdb_test_no_output "guile (define c_red (make-color \"red\"))" \
+    "save blue color"
+gdb_test_no_output "guile (define c_green (make-color \"green\"))" \
+    "save yellow color"
+gdb_test [concat "guile " \
+    "(display (color-escape-sequence c_red #t)) " \
+    "(display (color-escape-sequence c_green #f)) " \
+    "(display \"red on green\") " \
+    "(display (color-escape-sequence c_none #f)) " \
+    "(display \" red on default\") " \
+    "(display (color-escape-sequence c_none #t)) " \
+    "(newline)"] \
+    "\033\\\[31m\033\\\[42mred on green\033\\\[49m red on default\033\\\[39m" \
+    "escape sequences"
+
diff --git a/gdb/testsuite/gdb.guile/scm-parameter.exp b/gdb/testsuite/gdb.guile/scm-parameter.exp
index d10e9d24065..eabd17980e7 100644
--- a/gdb/testsuite/gdb.guile/scm-parameter.exp
+++ b/gdb/testsuite/gdb.guile/scm-parameter.exp
@@ -389,3 +389,50 @@  with_test_prefix "previously-ambiguous" {
 }
 
 rename scm_param_test_maybe_no_output ""
+
+# Test a color parameter.
+
+with_ansi_styling_terminal {
+    # This enables 256 colors support and disables colors approximation.
+    setenv TERM xterm-256color
+    setenv COLORTERM truecolor
+
+    # Start with a fresh gdb.
+    gdb_exit
+    gdb_start
+    gdb_reinitialize_dir $srcdir/$subdir
+
+    gdb_install_guile_utils
+    gdb_install_guile_module
+
+    # We use "." here instead of ":" so that this works on win32 too.
+    set escaped_directory [string_to_regexp "$srcdir/$subdir"]
+
+    gdb_test_multiline "color gdb parameter" \
+	"guile" "" \
+	"(define test-color-param" "" \
+	"  (make-parameter \"print test-color-param\"" "" \
+	"   #:command-class COMMAND_DATA" "" \
+	"   #:parameter-type PARAM_COLOR" "" \
+	"   #:doc \"When set, test param does something useful. When disabled, does nothing.\"" "" \
+	"   #:show-doc \"Show the state of the test-color-param.\"" "" \
+	"   #:set-doc \"Set the state of the test-color-param.\"" "" \
+	"   #:show-func (lambda (self value)" "" \
+	"      (format #f \"The state of the test-color-param is ~a.\" value))" "" \
+	"   #:initial-value (make-color \"green\")))" "" \
+	"(register-parameter! test-color-param)" "" \
+	"end"
+
+    with_test_prefix "test-color-param" {
+	with_test_prefix "initial-value" {
+	    gdb_test "guile (print (parameter-value test-color-param))" "= #<gdb:color green COLORSPACE_ANSI_8COLOR>" "color parameter value (green)"
+	    gdb_test "show print test-color-param" "The state of the test-color-param is green." "show initial value"
+	    gdb_test_no_output "set print test-color-param 255"
+	}
+	with_test_prefix "new-value" {
+	    gdb_test "show print test-color-param" "The state of the test-color-param is 255." "show new value"
+	    gdb_test "guile (print (parameter-value test-color-param))" "= #<gdb:color 255 COLORSPACE_XTERM_256COLOR>" "color parameter value (255)"
+	    gdb_test "set print test-color-param 256" "integer 256 out of range.*" "set invalid color parameter"
+	}
+    }
+}
diff --git a/gdb/testsuite/gdb.python/py-color.exp b/gdb/testsuite/gdb.python/py-color.exp
new file mode 100644
index 00000000000..8387fbfe71a
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-color.exp
@@ -0,0 +1,100 @@ 
+# Copyright (C) 2010-2022 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This file is part of the GDB testsuite.
+# It tests gdb.parameter and gdb.Parameter.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Start with a fresh gdb.
+clean_restart
+
+gdb_test_no_output "python print_color_attrs = lambda c: print (c, c.colorspace, c.is_none, c.is_indexed, c.is_direct)" \
+    "print_color_attrs helper"
+
+gdb_test_no_output "python c = gdb.Color ()" \
+    "create color without params"
+gdb_test "python print_color_attrs (c)" "none 0 True False False" \
+    "print attrs of a color without params"
+
+gdb_test_no_output "python c = gdb.Color ('green')" \
+    "create color from basic name string"
+gdb_test "python print_color_attrs (c)" "green 1 False True False" \
+    "print attrs of a basic color name"
+gdb_test "python print (c.index)" "2" \
+    "print index of a basic color name"
+
+gdb_test_no_output "python c = gdb.Color (2)" \
+    "create color from basic index"
+gdb_test "python print_color_attrs (c)" "green 1 False True False" \
+    "print attrs of a basic color"
+gdb_test "python print (c.index)" "2" \
+    "print index of a basic color"
+
+gdb_test_no_output "python c = gdb.Color (14)" \
+    "create color from integer 14"
+gdb_test "python print_color_attrs (c)" "14 2 False True False" \
+    "print attrs of an color 14"
+gdb_test "python print (c.index)" "14" \
+    "print index of color 14"
+
+gdb_test_no_output "python c = gdb.Color (2, gdb.COLORSPACE_ANSI_8COLOR)" \
+    "create color from basic index and ansi colorspace"
+gdb_test "python print_color_attrs (c)" "green 1 False True False" \
+    "print attrs of a basic color with ansi colorspace"
+gdb_test "python print (c.index)" "2" \
+    "print index of a basic color with ansi colorspace"
+
+gdb_test_no_output "python c = gdb.Color (2, gdb.COLORSPACE_XTERM_256COLOR)" \
+    "create color from basic index and xterm256 colorspace"
+gdb_test "python print_color_attrs (c)" "2 3 False True False" \
+    "print attrs of a basic color with xterm256 colorspace"
+gdb_test "python print (c.index)" "2" \
+    "print index of a basic color with xterm256 colorspace"
+
+gdb_test_no_output "python c = gdb.Color ((171, 205, 239), gdb.COLORSPACE_RGB_24BIT)" \
+    "create color from rgb components"
+gdb_test "python print_color_attrs (c)" "#ABCDEF 4 False False True" \
+    "print attrs of an RGB color"
+gdb_test "python print (c.components)" "\\(171, 205, 239\\)" \
+    "print components of an RGB color"
+
+gdb_test_no_output "python c = gdb.Color ('none')" \
+    "create color from string none"
+gdb_test "python print_color_attrs (c)" "none 0 True False False" \
+    "print attrs of a color none"
+
+gdb_test_no_output "python c = gdb.Color ('254')" \
+    "create color from string 254"
+gdb_test "python print_color_attrs (c)" "254 3 False True False" \
+    "print attrs of an color 254"
+gdb_test "python print (c.index)" "254" \
+    "print index of color 254"
+
+gdb_test_no_output "python c_none = gdb.Color ('none')" \
+    "save default color"
+gdb_test_no_output "python c_red = gdb.Color ('red')" \
+    "save blue color"
+gdb_test_no_output "python c_green = gdb.Color ('green')" \
+    "save yellow color"
+gdb_test [concat "python print (c_red.escape_sequence (True) + " \
+    "c_green.escape_sequence (False) + 'red on green' + " \
+    "c_none.escape_sequence (False) + ' red on default' + " \
+    "c_none.escape_sequence (True))"] \
+    "\033\\\[31m\033\\\[42mred on green\033\\\[49m red on default\033\\\[39m" \
+    "escape sequences"
+
diff --git a/gdb/testsuite/gdb.python/py-parameter.exp b/gdb/testsuite/gdb.python/py-parameter.exp
index de524f49ad6..6e6a6007198 100644
--- a/gdb/testsuite/gdb.python/py-parameter.exp
+++ b/gdb/testsuite/gdb.python/py-parameter.exp
@@ -185,6 +185,58 @@  proc_with_prefix test_enum_parameter { } {
 	"Undefined item: \"three\".*" "set invalid enum parameter"
 }
 
+# Test an color parameter.
+proc_with_prefix test_color_parameter { } {
+    global env
+    save_vars { env(TERM) env(COLORTERM) } {
+	# This enables 256 colors support and disables colors approximation.
+	setenv TERM xterm-256color
+	setenv COLORTERM truecolor
+
+	clean_restart
+
+	gdb_test_multiline "color gdb parameter" \
+	    "python" "" \
+	    "class TestColorParam (gdb.Parameter):" "" \
+	    "   \"\"\"When set, test param does something useful. When disabled, does nothing.\"\"\"" "" \
+	    "   show_doc = \"Show the state of the color\"" ""\
+	    "   set_doc = \"Set the state of the color\"" "" \
+	    "   def get_show_string (self, pvalue):" ""\
+	    "      return \"The state of the color is \" + str(pvalue)" ""\
+	    "   def get_set_string (self):" ""\
+	    "      return \"The state of the color has been set to \" + str(self.value)" ""\
+	    "   def __init__ (self, name):" "" \
+	    "      super (TestColorParam, self).__init__ (name, gdb.COMMAND_DATA, gdb.PARAM_COLOR)" "" \
+	    "      self.value = gdb.Color(\"green\")" "" \
+	    "test_color_param = TestColorParam ('print test-color-param')" ""\
+	    "end"
+
+	gdb_test "python print (test_color_param.value)" "green" \
+	    "test color parameter value is green"
+	gdb_test "show print test-color-param" \
+	    "The state of the color is green.*" \
+	    "show parameter is initial value"
+	gdb_test "set print test-color-param 255" \
+	    "The state of the color has been set to 255" "set color to 255"
+	gdb_test "show print test-color-param" \
+	    "The state of the color is 255.*" "show parameter is new value"
+	gdb_test "python print (test_color_param.value)" "255" \
+	    "test color parameter value is 255"
+	gdb_test_no_output "python test_color_param.value = gdb.Color(254)" \
+	    "assign test_color_param.value to 254"
+	gdb_test "python print (test_color_param.value)" "254" \
+	    "test color parameter value is integer"
+	gdb_test_no_output "python test_color_param.value =  gdb.Color('#FED210')" \
+	    "assign test_color_param.value to #FED210"
+	gdb_test "python print (test_color_param.value.components)" "\\(254, 210, 16\\)" \
+	    "test color parameter components from RGB hex tripple value"
+	gdb_test "set print test-color-param 256" \
+	    "integer 256 out of range.*" "set invalid color parameter"
+	gdb_test "python test_color_param.value = gdb.Color(256)" \
+	    ".*Error occurred in Python: Palette color index 256 is out of range.*" "set invalid color value"
+    }
+}
+
 # Test a file parameter.
 proc_with_prefix test_file_parameter { } {
     clean_restart
@@ -623,6 +675,7 @@  test_directories
 test_data_directory
 test_boolean_parameter
 test_enum_parameter
+test_color_parameter
 test_file_parameter
 test_undocumented_parameter
 test_really_undocumented_parameter
diff --git a/gdb/testsuite/lib/gdb-utils.exp b/gdb/testsuite/lib/gdb-utils.exp
index 41989da3ed2..ea02bff27e4 100644
--- a/gdb/testsuite/lib/gdb-utils.exp
+++ b/gdb/testsuite/lib/gdb-utils.exp
@@ -67,18 +67,22 @@  proc string_list_to_regexp { args } {
 # "function", "variable", or "address".
 
 proc style {str style} {
+    set fg 39
+    set bg 49
+    set intensity 22
+    set reverse 27
     switch -exact -- $style {
-	title { set style 1 }
-	file { set style 32 }
-	function { set style 33 }
-	highlight { set style 31 }
-	variable { set style 36 }
-	address { set style 34 }
-	metadata { set style 2 }
-	version { set style "35;1" }
+	title { set intensity 1 }
+	file { set fg 32 }
+	function { set fg 33 }
+	highlight { set fg 31 }
+	variable { set fg 36 }
+	address { set fg 34 }
+	metadata { set intensity 2 }
+	version { set fg 35; set intensity 1 }
 	none { return $str }
     }
-    return "\033\\\[${style}m${str}\033\\\[m"
+    return "\033\\\[${fg};${bg};${intensity};${reverse}m${str}\033\\\[m"
 }
 
 # gdb_get_bp_addr num
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index c993f48fd34..1c579dc0d47 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -10061,10 +10061,11 @@  proc with_override { name override body } {
 # Run BODY after setting the TERM environment variable to 'ansi', and
 # unsetting the NO_COLOR environment variable.
 proc with_ansi_styling_terminal { body } {
-    save_vars { ::env(TERM) ::env(NO_COLOR) } {
+    save_vars { ::env(TERM) ::env(NO_COLOR) ::env(COLORTERM) } {
 	# Set environment variables to allow styling.
 	setenv TERM ansi
 	unset -nocomplain ::env(NO_COLOR)
+	unset -nocomplain ::env(COLORTERM)
 
 	set code [catch {uplevel 1 $body} result]
     }
diff --git a/gdb/top.c b/gdb/top.c
index d6bf1d448d5..1460c9cad6c 100644
--- a/gdb/top.c
+++ b/gdb/top.c
@@ -2122,6 +2122,17 @@  show_startup_quiet (struct ui_file *file, int from_tty,
 	      value);
 }
 
+static void
+init_colorsupport_var (void)
+{
+  const std::vector<color_space>& cs = colorsupport ();
+  std::string s;
+  for (color_space c : cs)
+    s.append (s.empty () ? "" : ",").append (color_space_name (c));
+  struct internalvar *colorsupport_var = create_internalvar ("_colorsupport");
+  set_internalvar_string (colorsupport_var, s.c_str ());
+}
+
 static void
 init_main (void)
 {
@@ -2326,6 +2337,9 @@  gdb_init ()
      during startup.  */
   set_language (language_c);
   expected_language = current_language;	/* Don't warn about the change.  */
+
+  /* Create $_colorsupport convenience variable.  */
+  init_colorsupport_var ();
 }
 
 void _initialize_top ();
diff --git a/gdb/ui-style.c b/gdb/ui-style.c
index 952102e30c3..ae54ca8634e 100644
--- a/gdb/ui-style.c
+++ b/gdb/ui-style.c
@@ -17,6 +17,7 @@ 
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "ui-style.h"
+#include "gdb_curses.h"
 #include "gdbsupport/gdb_regex.h"
 
 /* A regular expression that is used for matching ANSI terminal escape
@@ -46,91 +47,229 @@  static const char ansi_regex_text[] =
 
 static regex_t ansi_regex;
 
-/* This maps bright colors to RGB triples.  The index is the bright
-   color index, starting with bright black.  The values come from
-   xterm.  */
-
-static const uint8_t bright_colors[][3] = {
-  { 127, 127, 127 },		/* Black.  */
-  { 255, 0, 0 },		/* Red.  */
-  { 0, 255, 0 },		/* Green.  */
-  { 255, 255, 0 },		/* Yellow.  */
-  { 92, 92, 255 },		/* Blue.  */
-  { 255, 0, 255 },		/* Magenta.  */
-  { 0, 255, 255 },		/* Cyan.  */
-  { 255, 255, 255 }		/* White.  */
+/* This maps 8-color palette to RGB triples.  The values come from
+   plain linux terminal.  */
+
+static const uint8_t palette_8colors[][3] = {
+  { 1, 1, 1 },			/* Black.  */
+  { 222, 56, 43 },		/* Red.  */
+  { 57, 181, 74 },		/* Green.  */
+  { 255, 199, 6 },		/* Yellow.  */
+  { 0, 111, 184 },		/* Blue.  */
+  { 118, 38, 113 },		/* Magenta.  */
+  { 44, 181, 233 },		/* Cyan.  */
+  { 204, 204, 204 },		/* White.  */
 };
 
+/* This maps 16-color palette to RGB triples.  The values come from xterm.  */
+
+static const uint8_t palette_16colors[][3] = {
+  { 0, 0, 0 },			/* Black.  */
+  { 205, 0, 0 },		/* Red.  */
+  { 0, 205, 0 },		/* Green.  */
+  { 205, 205, 0 },		/* Yellow.  */
+  { 0, 0, 238 },		/* Blue.  */
+  { 205, 0, 205 },		/* Magenta.  */
+  { 0, 205, 205 },		/* Cyan.  */
+  { 229, 229, 229 },		/* White.  */
+  { 127, 127, 127 },		/* Bright Black.  */
+  { 255, 0, 0 },		/* Bright Red.  */
+  { 0, 255, 0 },		/* Bright Green.  */
+  { 255, 255, 0 },		/* Bright Yellow.  */
+  { 92, 92, 255 },		/* Bright Blue.  */
+  { 255, 0, 255 },		/* Bright Magenta.  */
+  { 0, 255, 255 },		/* Bright Cyan.  */
+  { 255, 255, 255 }		/* Bright White.  */
+};
+
+/* See ui-style.h.  */
+/* Must correspond to ui_file_style::basic_color.  */
+const std::vector<const char *> ui_file_style::basic_color_enums = {
+  "none",
+  "black",
+  "red",
+  "green",
+  "yellow",
+  "blue",
+  "magenta",
+  "cyan",
+  "white",
+  nullptr
+};
+
+/* Returns text representation of a basic COLOR.  */
+
+static const char *
+basic_color_name (int color)
+{
+  int pos = color - ui_file_style::NONE;
+  if (0 <= pos && pos < ui_file_style::basic_color_enums.size ())
+    if (const char *s = ui_file_style::basic_color_enums[pos])
+      return s;
+  error (_("Basic color %d has no name."), color);
+}
+
 /* See ui-style.h.  */
 
 bool
 ui_file_style::color::append_ansi (bool is_fg, std::string *str) const
 {
-  if (m_simple)
-    {
-      if (m_value >= BLACK && m_value <= WHITE)
-	str->append (std::to_string (m_value + (is_fg ? 30 : 40)));
-      else if (m_value > WHITE && m_value <= WHITE + 8)
-	str->append (std::to_string (m_value - WHITE + (is_fg ? 90 : 100)));
-      else if (m_value != -1)
-	{
-	  str->append (is_fg ? "38;5;" : "48;5;");
-	  str->append (std::to_string (m_value));
-	}
-      else
-	return false;
-    }
-  else
+  if (m_color_space == color_space::MONOCHROME)
+    str->append (is_fg ? "39" : "49");
+  else if (is_basic ())
+    str->append (std::to_string (m_value + (is_fg ? 30 : 40)));
+  else if (m_color_space == color_space::AIXTERM_16COLOR)
+    str->append (std::to_string (m_value - WHITE - 1 + (is_fg ? 90 : 100)));
+  else if (m_color_space == color_space::XTERM_256COLOR)
+    str->append (is_fg ? "38;5;" : "48;5;").append (std::to_string (m_value));
+  else if (m_color_space == color_space::RGB_24BIT)
     {
       str->append (is_fg ? "38;2;" : "48;2;");
       str->append (std::to_string (m_red)
 		   + ";" + std::to_string (m_green)
 		   + ";" + std::to_string (m_blue));
     }
+  else
+    return false;
+
   return true;
 }
 
 /* See ui-style.h.  */
+std::string
+ui_file_style::color::to_ansi (bool is_fg) const
+{
+  std::string s = "\033[";
+  if (!append_ansi (is_fg, &s))
+    return {};
+  s.push_back ('m');
+  return s;
+}
 
-void
-ui_file_style::color::get_rgb (uint8_t *rgb) const
+/* See ui-style.h.  */
+
+std::string
+ui_file_style::color::to_string () const
 {
-  if (m_simple)
+  if (m_color_space == color_space::RGB_24BIT)
     {
-      /* Can't call this for a basic color or NONE -- those will end
-	 up in the assert below.  */
-      if (m_value >= 8 && m_value <= 15)
-	memcpy (rgb, bright_colors[m_value - 8], 3 * sizeof (uint8_t));
-      else if (m_value >= 16 && m_value <= 231)
-	{
-	  int value = m_value;
-	  value -= 16;
-	  /* This obscure formula seems to be what terminals actually
-	     do.  */
-	  int component = value / 36;
-	  rgb[0] = component == 0 ? 0 : (55 + component * 40);
-	  value %= 36;
-	  component = value / 6;
-	  rgb[1] = component == 0 ? 0 : (55 + component * 40);
-	  value %= 6;
-	  rgb[2] = value == 0 ? 0 : (55 + value * 40);
-	}
-      else if (m_value >= 232)
-	{
-	  uint8_t v = (m_value - 232) * 10 + 8;
-	  rgb[0] = v;
-	  rgb[1] = v;
-	  rgb[2] = v;
-	}
-      else
-	gdb_assert_not_reached ("get_rgb called on invalid color");
+      char s[64];
+      snprintf (s, sizeof s, "#%02X%02X%02X", m_red, m_green, m_blue);
+      return s;
     }
+  else if (is_none () || is_basic ())
+    return basic_color_name (m_value);
   else
+    return std::to_string (get_value ());
+}
+
+/* See ui-style.h.  */
+
+void
+ui_file_style::color::get_rgb (uint8_t *rgb) const
+{
+  if (m_color_space == color_space::RGB_24BIT)
     {
       rgb[0] = m_red;
       rgb[1] = m_green;
       rgb[2] = m_blue;
     }
+  else if (m_color_space == color_space::ANSI_8COLOR
+	   && 0 <= m_value && m_value <= 7)
+    memcpy (rgb, palette_8colors[m_value], 3 * sizeof (uint8_t));
+  else if (m_color_space == color_space::AIXTERM_16COLOR
+	   && 0 <= m_value && m_value <= 15)
+    memcpy (rgb, palette_16colors[m_value], 3 * sizeof (uint8_t));
+  else if (m_color_space != color_space::XTERM_256COLOR)
+    gdb_assert_not_reached ("get_rgb called on invalid color");
+  else if (0 <= m_value && m_value <= 15)
+    memcpy (rgb, palette_16colors[m_value], 3 * sizeof (uint8_t));
+  else if (m_value >= 16 && m_value <= 231)
+    {
+      int value = m_value;
+      value -= 16;
+      /* This obscure formula seems to be what terminals actually
+	 do.  */
+      int component = value / 36;
+      rgb[0] = component == 0 ? 0 : (55 + component * 40);
+      value %= 36;
+      component = value / 6;
+      rgb[1] = component == 0 ? 0 : (55 + component * 40);
+      value %= 6;
+      rgb[2] = value == 0 ? 0 : (55 + value * 40);
+    }
+  else if (232 <= m_value && m_value <= 255)
+    {
+      uint8_t v = (m_value - 232) * 10 + 8;
+      rgb[0] = v;
+      rgb[1] = v;
+      rgb[2] = v;
+    }
+  else
+    gdb_assert_not_reached ("get_rgb called on invalid color");
+}
+
+/* See ui-style.h.  */
+
+ui_file_style::color
+ui_file_style::color::approximate (const std::vector<color_space> &spaces) const
+{
+  if (spaces.empty () || is_none ())
+    return NONE;
+
+  color_space target_space = color_space::MONOCHROME;
+  for (color_space sp : spaces)
+    if (sp == m_color_space)
+      return *this;
+    else if (sp > target_space)
+      target_space = sp;
+
+  if (target_space == color_space::RGB_24BIT)
+    {
+      uint8_t rgb[3];
+      get_rgb (rgb);
+      return color (rgb[0], rgb[1], rgb[2]);
+    }
+
+  int target_size = 0;
+  switch (target_space)
+    {
+    case color_space::ANSI_8COLOR:
+      target_size = 8;
+      break;
+    case color_space::AIXTERM_16COLOR:
+      target_size = 16;
+      break;
+    case color_space::XTERM_256COLOR:
+      target_size = 256;
+      break;
+    }
+
+  if (is_simple() && m_value < target_size)
+    return color (target_space, m_value);
+
+  color result = NONE;
+  int best_distance = std::numeric_limits<int>::max ();
+  uint8_t rgb[3];
+  get_rgb (rgb);
+
+  for (int i = 0; i < target_size; ++i)
+    {
+      uint8_t c_rgb[3];
+      color c (target_space, i);
+      c.get_rgb (c_rgb);
+      int d_red = std::abs (rgb[0] - c_rgb[0]);
+      int d_green = std::abs (rgb[1] - c_rgb[1]);
+      int d_blue = std::abs (rgb[2] - c_rgb[2]);
+      int dist = d_red * d_red + d_green * d_green + d_blue * d_blue;
+      if (dist < best_distance)
+	{
+	  best_distance = dist;
+	  result = c;
+	}
+    }
+
+  return result;
 }
 
 /* See ui-style.h.  */
@@ -139,26 +278,23 @@  std::string
 ui_file_style::to_ansi () const
 {
   std::string result ("\033[");
-  bool need_semi = m_foreground.append_ansi (true, &result);
-  if (!m_background.is_none ())
+  if (!is_default ())
     {
+      bool need_semi = m_foreground.append_ansi (true, &result);
       if (need_semi)
 	result.push_back (';');
-      m_background.append_ansi (false, &result);
-      need_semi = true;
-    }
-  if (m_intensity != NORMAL)
-    {
+      need_semi = m_background.append_ansi (false, &result);
       if (need_semi)
 	result.push_back (';');
-      result.append (std::to_string (m_intensity));
-      need_semi = true;
-    }
-  if (m_reverse)
-    {
-      if (need_semi)
-	result.push_back (';');
-      result.push_back ('7');
+      if (m_intensity == NORMAL)
+	result.append ("22");
+      else
+	result.append (std::to_string (m_intensity));
+      result.push_back (';');
+      if (m_reverse)
+	result.push_back ('7');
+      else
+	result.append ("27");
     }
   result.push_back ('m');
   return result;
@@ -315,9 +451,11 @@  ui_file_style::parse (const char *buf, size_t *n_read)
 	    case 35:
 	    case 36:
 	    case 37:
+	      m_foreground = color (value - 30);
+	      break;
 	      /* Note: not 38.  */
 	    case 39:
-	      m_foreground = color (value - 30);
+	      m_foreground = NONE;
 	      break;
 
 	    case 40:
@@ -328,9 +466,11 @@  ui_file_style::parse (const char *buf, size_t *n_read)
 	    case 45:
 	    case 46:
 	    case 47:
+	      m_background = color (value - 40);
+	      break;
 	      /* Note: not 48.  */
 	    case 49:
-	      m_background = color (value - 40);
+	      m_background = NONE;
 	      break;
 
 	    case 90:
@@ -412,3 +552,44 @@  _initialize_ui_style ()
      error.  */
   gdb_assert (code == 0);
 }
+
+/* See ui-style.h.  */
+
+const std::vector<color_space> &
+colorsupport ()
+{
+  static const std::vector<color_space> value = []
+    {
+      std::vector<color_space> result = {color_space::MONOCHROME};
+
+      int colors = tgetnum ((char *)"Co");
+      if (colors >= 8)
+	result.push_back (color_space::ANSI_8COLOR);
+      if (colors >= 16)
+	result.push_back (color_space::AIXTERM_16COLOR);
+      if (colors >= 256)
+	result.push_back (color_space::XTERM_256COLOR);
+
+      const char *colorterm = getenv ("COLORTERM");
+      if (colorterm && (!strcmp (colorterm, "truecolor")
+	  || !strcmp (colorterm, "24bit")))
+	result.push_back (color_space::RGB_24BIT);
+
+      return result;
+    } ();
+  return value;
+}
+
+const char *
+color_space_name (color_space c)
+{
+  switch (c)
+    {
+    case color_space::MONOCHROME: return "monochrome";
+    case color_space::ANSI_8COLOR: return "ansi_8color";
+    case color_space::AIXTERM_16COLOR: return "aixterm_16color";
+    case color_space::XTERM_256COLOR: return "xterm_256color";
+    case color_space::RGB_24BIT: return "rgb_24bit";
+    }
+  error (_("Color space %d has no name."), static_cast<int> (c));
+}
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
index 1b7b5fafb9d..a13618dfce5 100644
--- a/gdb/ui-style.h
+++ b/gdb/ui-style.h
@@ -19,6 +19,26 @@ 
 #ifndef UI_STYLE_H
 #define UI_STYLE_H
 
+/* One of the color spaces that usually supported by terminals.  */
+enum class color_space
+{
+  MONOCHROME, // one default terminal color
+  ANSI_8COLOR, // foreground colors \e[30m ... \e[37m,
+	       // background colors \e[40m ... \e[47m
+  AIXTERM_16COLOR, // foreground colors \e[30m ... \e[37m, \e[90m ... \e[97m
+		   // background colors \e[40m ... \e[47m, \e[100m ... \e107m
+  XTERM_256COLOR, // foreground colors \e[38;5;0m ... \e[38;5;255m
+		  // background colors \e[48;5;0m ... \e[48;5;255m
+  RGB_24BIT // foreground colors \e[38;2;0;0;0m ... \e[38;2;255;255;255m
+	    // background colors \e[48;2;0;0;0m ... \e[48;2;255;255;255m
+};
+
+/* Color spaces supported by terminal.  */
+extern const std::vector<color_space> & colorsupport ();
+
+/* Textual representation of C.  */
+extern const char * color_space_name (color_space c);
+
 /* Styles that can be applied to a ui_file.  */
 struct ui_file_style
 {
@@ -43,20 +63,61 @@  struct ui_file_style
   public:
 
     color (basic_color c)
-      : m_simple (true),
+      : m_color_space (c == NONE ? color_space::MONOCHROME
+				 : color_space::ANSI_8COLOR),
 	m_value (c)
     {
     }
 
     color (int c)
-      : m_simple (true),
+      : m_value (c)
+    {
+      if (c < -1 || c > 255)
+	error (_("Palette color index %d is out of range."), c);
+      if (c == -1)
+	m_color_space = color_space::MONOCHROME;
+      else if (c <= 7)
+	m_color_space = color_space::ANSI_8COLOR;
+      else if (c <= 15)
+	m_color_space = color_space::AIXTERM_16COLOR;
+      else
+	m_color_space = color_space::XTERM_256COLOR;
+    }
+
+    color (color_space cs, int c)
+      : m_color_space (cs),
 	m_value (c)
     {
-      gdb_assert (c >= -1 && c <= 255);
+      if (c < -1 || c > 255)
+	error (_("Palette color index %d is out of range."), c);
+
+      std::pair<int, int> range;
+      switch (cs)
+	{
+	case color_space::MONOCHROME:
+	  range = {-1, -1};
+	  break;
+	case color_space::ANSI_8COLOR:
+	  range = {0, 7};
+	  break;
+	case color_space::AIXTERM_16COLOR:
+	  range = {0, 15};
+	  break;
+	case color_space::XTERM_256COLOR:
+	  range = {0, 255};
+	  break;
+	default:
+	  error (_("Color space %d is incompatible with indexed colors."),
+		 static_cast<int> (cs));
+	}
+
+      if (c < range.first || c > range.second)
+	error (_("Color %d is out of range [%d, %d] of color space %d."),
+	       c, range.first, range.second, static_cast<int> (cs));
     }
 
     color (uint8_t r, uint8_t g, uint8_t b)
-      : m_simple (false),
+      : m_color_space (color_space::RGB_24BIT),
 	m_red (r),
 	m_green (g),
 	m_blue (b)
@@ -65,19 +126,24 @@  struct ui_file_style
 
     bool operator== (const color &other) const
     {
-      if (m_simple != other.m_simple)
+      if (m_color_space != other.m_color_space)
 	return false;
-      if (m_simple)
+      if (is_simple ())
 	return m_value == other.m_value;
       return (m_red == other.m_red && m_green == other.m_green
 	      && m_blue == other.m_blue);
     }
 
+    bool operator!= (const color &other) const
+    {
+      return ! (*this == other);
+    }
+
     bool operator< (const color &other) const
     {
-      if (m_simple != other.m_simple)
-	return m_simple < other.m_simple;
-      if (m_simple)
+      if (m_color_space != other.m_color_space)
+	return m_color_space < other.m_color_space;
+      if (is_simple ())
 	return m_value < other.m_value;
       if (m_red < other.m_red)
 	return true;
@@ -91,23 +157,54 @@  struct ui_file_style
       return false;
     }
 
+    color_space colorspace () const
+    {
+      return m_color_space;
+    }
+
     /* Return true if this is the "NONE" color, false otherwise.  */
     bool is_none () const
     {
-      return m_simple && m_value == NONE;
+      return m_color_space == color_space::MONOCHROME && m_value == NONE;
     }
 
     /* Return true if this is one of the basic colors, false
        otherwise.  */
     bool is_basic () const
     {
-      return m_simple && m_value >= BLACK && m_value <= WHITE;
+      if (m_color_space == color_space::ANSI_8COLOR
+	  || m_color_space == color_space::AIXTERM_16COLOR)
+	return BLACK <= m_value && m_value <= WHITE;
+      else
+	return false;
+    }
+
+    /* Return true if this is one of the colors, stored as int, false
+       otherwise.  */
+    bool is_simple () const
+    {
+      return m_color_space != color_space::RGB_24BIT;
+    }
+
+    /* Return true if this is one of the indexed colors, false
+       otherwise.  */
+    bool is_indexed () const
+    {
+      return m_color_space != color_space::RGB_24BIT
+	     && m_color_space != color_space::MONOCHROME;
     }
 
-    /* Return the value of a basic color.  */
+    /* Return true if this is one of the direct colors (RGB, CMY, CMYK), false
+       otherwise.  */
+    bool is_direct () const
+    {
+      return m_color_space == color_space::RGB_24BIT;
+    }
+
+    /* Return the value of a simple color.  */
     int get_value () const
     {
-      gdb_assert (is_basic ());
+      gdb_assert (is_simple ());
       return m_value;
     }
 
@@ -119,13 +216,23 @@  struct ui_file_style
     /* Append the ANSI terminal escape sequence for this color to STR.
        IS_FG indicates whether this is a foreground or background
        color.  Returns true if any characters were written; returns
-       false otherwise (which can only happen for the "NONE"
-       color).  */
+       false otherwise.  */
     bool append_ansi (bool is_fg, std::string *str) const;
 
+    /* Return the ANSI escape sequence for this color.
+       IS_FG indicates whether this is a foreground or background color.  */
+    std::string to_ansi (bool is_fg) const;
+
+    /* Returns text representation of this object.
+       It is "none", name of a basic color, number or a #RRGGBB hex triplet.  */
+    std::string to_string () const;
+
+    /* Approximates THIS color by closest one from SPACES.  */
+    color approximate (const std::vector<color_space> &spaces) const;
+
   private:
 
-    bool m_simple;
+    color_space m_color_space;
     union
     {
       int m_value;
@@ -235,6 +342,9 @@  struct ui_file_style
     return this;
   }
 
+  /* nullptr-terminated list of names corresponding to enum basic_color.  */
+  static const std::vector<const char *> basic_color_enums;
+
 private:
 
   color m_foreground = NONE;
diff --git a/gdb/unittests/style-selftests.c b/gdb/unittests/style-selftests.c
index 4dc3c842c47..72bc878582a 100644
--- a/gdb/unittests/style-selftests.c
+++ b/gdb/unittests/style-selftests.c
@@ -58,7 +58,7 @@  run_tests ()
   SELF_CHECK (style.get_background ().is_none ());
   SELF_CHECK (style.get_intensity () == ui_file_style::NORMAL);
   SELF_CHECK (style.is_reverse ());
-  SELF_CHECK (style.to_ansi () == "\033[7m");
+  SELF_CHECK (style.to_ansi () == "\033[39;49;22;7m");
 
   style = ui_file_style ();
   SELF_CHECK (style.parse ("\033[32;1m", &n_read));
@@ -68,7 +68,7 @@  run_tests ()
   SELF_CHECK (style.get_background ().is_none ());
   SELF_CHECK (style.get_intensity () == ui_file_style::BOLD);
   SELF_CHECK (!style.is_reverse ());
-  SELF_CHECK (style.to_ansi () == "\033[32;1m");
+  SELF_CHECK (style.to_ansi () == "\033[32;49;1;27m");
 
   style = ui_file_style ();
   SELF_CHECK (style.parse ("\033[38;5;112;48;5;249m", &n_read));
@@ -81,7 +81,7 @@  run_tests ()
   CHECK_RGB (0xb2, 0xb2, 0xb2);
   SELF_CHECK (style.get_intensity () == ui_file_style::NORMAL);
   SELF_CHECK (!style.is_reverse ());
-  SELF_CHECK (style.to_ansi () == "\033[38;5;112;48;5;249m");
+  SELF_CHECK (style.to_ansi () == "\033[38;5;112;48;5;249;22;27m");
 
   style = ui_file_style ();
   SELF_CHECK (style.parse ("\033[38;2;83;84;85;48;2;0;1;254;2;7m", &n_read));