[PATCHv3 1/2] gdb/python: add gdb.Style class

Andrew Burgess
2025-06-07T10:46:49Z

This commit adds a new gdb.Style class. This class represents a
complete style within GDB. A complete style is a collection of
foreground color, background color, and an intensity.

A gdb.Style comes in two flavours, named, and unnamed.

A named style is one that is based on an existing style within GDB.
For example, we have 'set style filename ...', the name of this style
is 'filename'. We also have 'set style disassembler mnemonic ...',
the name of this style is 'disassembler mnemonic'. A named style is
created by passing the name of the style, like this:

(gdb) python s1 = gdb.Style("filename")
(gdb) python s2 = gdb.Style("disassembler mnemonic")

The other type of style is an unnamed style. An unnamed style is
created using a foreground and background color, along with an
intensity. Colors are specified using gdb.Color objects. An example
of creating an unnamed style is:

(gdb) python s3 = gdb.Style(foreground=gdb.Color('red'),
background=gdb.Color('green'),
intensity=gdb.INTENSITY_BOLD)

We can see here an example of the new intensity constants that have
been added in this commit, there is gdb.INTENSITY_NORMAL,
gdb.INTENSITY_BOLD, and gdb.INTENSITY_DIM. All of the arguments are
optional, the default for the colors is gdb.Color(), which will apply
the terminal default, and the default intensity is
gdb.INTENSITY_NORMAL.

Having created a gdb.Style object there are two ways that it can be
used to style GDB's output. The Style.escape_sequence() method
returns the escape sequence needed to apply this style, this can be
used as in:

(gdb) python print(s1.escape_sequence() + "Filename Style")

The problem with this approach is that it is the users responsibility
to restore the style to the default when they are done. In the above
example, all output after the escape sequence is printed, including
the next GDB prompt, will be in the s1 (filename) style. Which is why
the Style.apply method exists. This method takes a string and returns
the same string with escape sequences added before and after. The
before sequence switches to the style, while the after escape sequence
restores the terminal default style. This can be used like:

(gdb) python print(s1.apply("Filename Style"))

Now only the 'Filename Style' text will be styled. The next GDB
prompt will be in the default terminal style. Personally, I think the
apply method is the more useful, but having 'escape_sequence' matches
what gdb.Color offers, though if/when this patch is merged, I might
propose a similar 'apply' type method for the gdb.Color class.

The gdb.Style class has 'foreground', 'background', and 'intensity'
attributes which, when read, return the obvious values. These
attributes can also be written too.

When writing to an attribute of an unnamed Style object then the Style
object itself is updated, as you might expect.

When writing to an attribute of a named Style then the style setting
itself is updated as the following example shows:

(gdb) python s1 = gdb.Style("filename")
(gdb) python print(s1.foreground)
green
(gdb) show style filename foreground
The "filename" style foreground color is: green
(gdb) python s1.foreground=gdb.Color("red")
(gdb) python print(s1.foreground)
red
(gdb) show style filename foreground
The "filename" style foreground color is: red
(gdb)

We can see that a gdb.Style object is connected to the underlying
style settings, it doesn't take a copy of the style settings at
creation time. And the relationship works both ways. Continuing the
above example:

(gdb) set style filename foreground blue
(gdb) python print(s1.foreground)
blue
(gdb)

Here we see that changing the setting value causes the gdb.Style
object to update. And this is what you would want. I imagine this
being used in a Python extension to GDB where a user might create
global objects for some named styles, and then use these globals to
format output from some custom commands. If a user of an extension
changes a style setting then the extension wants to adapt to that
change.

Both the Style.escape_sequence and Style.apply methods take the global
style enabled setting into consideration. If styling is disabled then
Style.escape_sequence will return an empty string, and Style.apply
will return an unmodified copy of the original string object (actually
the input object with Py_INCREF applied).

There is also support for representing a gdb.Style as a string:

(gdb) python s1 = gdb.Style("filename")
(gdb) python print(s1)
<gdb.Style name='filename', fg=green, bg=none, intensity=normal>
(gdb)

Unnamed styles are similar, but don't have a 'name' field.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
gdb/Makefile.in | 1 +
gdb/NEWS | 7 +
gdb/doc/python.texi | 140 +++++
gdb/python/py-style.c | 797 ++++++++++++++++++++++++++
gdb/testsuite/gdb.python/py-style.exp | 339 +++++++++++
gdb/ui-style.h | 6 +
6 files changed, 1290 insertions(+)
create mode 100644 gdb/python/py-style.c
create mode 100644 gdb/testsuite/gdb.python/py-style.exp

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 998203ce1e2..f010ee1cad0 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -430,6 +430,7 @@ SUBDIR_PYTHON_SRCS = \
python/py-registers.c \
python/py-signalevent.c \
python/py-stopevent.c \
+ python/py-style.c \
python/py-symbol.c \
python/py-symtab.c \
python/py-threadevent.c \
diff --git a/gdb/NEWS b/gdb/NEWS
index 4dcc344b072..e0b1efd6787 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -131,6 +131,13 @@ info threads [-gid] [-stopped] [-running] [ID]...
** New constant gdb.PARAM_COLOR represents color type of a
gdb.Parameter.value. Parameter's value is gdb.Color instance.

+ ** New class gdb.Style for representing styles, a collection of
+ foreground and background gdb.Color objects, and an intensity.
+
+ ** New constants gdb.INTENSITY_NORMAL, gdb.INTENSITY_BOLD, and
+ gdb.INTENSITY_DIM for use with gdb.Style when representing
+ intensities.
+
** The memory_source argument (the second argument) has been removed
from gdb.disassembler.builtin_disassemble. This argument was
never used by GDB, and was added by mistake. The unused argument
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 8f1da601476..db50dc5f635 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -226,6 +226,7 @@ Python API
using Python.
* Lazy Strings In Python:: Python representation of lazy strings.
* Colors In Python:: Python representation of colors.
+* Styles In Python:: Python representation of styles.
* Architectures In Python:: Python representation of architectures.
* Registers In Python:: Python representation of registers.
* Connections In Python:: Python representation of connections.
@@ -7251,6 +7252,145 @@ Colors In Python

It is not possible to sub-class the @code{Color} class.

+@node Styles In Python
+@subsubsection Python representation of styles
+
+@cindex styles in python
+@tindex gdb.Style
+
+A style object contains the foreground and background colors
+(@pxref{Colors In Python}), along with an intensity, and can be used
+to apply this styling to output produced from Python.
+
+@value{GDBN} has many styles builtin (@pxref{Output Styling}), and
+style objects can be created that apply these builtin styles to Python
+output.
+
+The style class is called @code{gdb.Style}, and has the following
+methods and attributes:
+
+@defun Style.__init__ (style_name)
+Create a @code{gdb.Style} that represents a builtin named style. The
+@var{style_name} must be a non-empty string that names a style that
+exists as a setting in @value{GDBN} within @samp{set style @dots{}}. For
+example, the string @samp{"filename"} can be used to create a
+@code{gdb.Style} object representing the @samp{set style filename}
+style.
+
+If @var{style_name} names an unknown style then a @code{RuntimeError}
+exception is raised.
+@end defun
+
+@defun Style.__init__ (foreground=@code{None}, background=@code{None}, intensity=gdb.INTENSITY_NORMAL)
+Create a custom @code{gdb.Style}, manually specifying the three
+individual components. All of the arguments are optional. By
+default, if no arguments are given, then the default style will be
+created, this will produce output with the default terminal foreground
+and background colors, along with the normal level of intensity
+(i.e.@: neither bold, nor dim).
+
+The @var{foreground} and @var{background} arguments should either be
+@code{None}, in which case the terminal default colors are used, or a
+@code{gdb.Color} object (@pxref{Colors In Python}). Any other object
+type will result in a @code{TypeError} exception being raised.
+
+The @var{intensity} argument should be one of the intensity constants
+defined below (@pxref{Style Intensities}). Passing a none integer
+value results in a @code{TypeError} exception, and passing an invalid
+constant results in a @code{ValueError} exception.
+@end defun
+
+@defun Style.escape_sequence ()
+If styling is enabled (@pxref{Output Styling}, then this method
+returns the string which is the terminal escape sequence necessary to
+apply this @code{gdb.Style}.
+
+If styling is disabled then this method returns the empty string.
+
+If this is a named style, which for some reason can no longer be read,
+then a @code{RuntimeError} exception is raised.
+@end defun
+
+@defun Style.apply (string)
+This method returns @var{string}, which must be a @code{str} object,
+with an escape sequence at both the start, and at the end. The escape
+sequence at the start applies this style, while the escape sequence at
+the end applies the terminal's default style.
+
+The effect of this is that, printing the result of this method, will
+print @var{string} with this style applied. Future output will be
+printed with the terminal's default style.
+
+If styling is currently disabled (@pxref{Output Styling}), then
+@code{Style.apply} just returns a copy of @var{string} with no escape
+sequences added.
+
+If this is a named style, which for some reason can no longer be read,
+then a @code{RuntimeError} exception is raised.
+@end defun
+
+@defvar Style.foreground
+This read/write attribute contains the @code{gdb.Color} object
+representing this style's foreground color. Writing to this attribute
+updates the style's foreground color.
+
+If the @code{gdb.Style} object was created using a named style (i.e.@:
+using the single argument @var{style_name} @code{__init__} method),
+then the underlying setting will be updated.
+
+For unnamed styles, only the @code{gdb.Style} object will change.
+@end defvar
+
+@defvar Style.background
+This read/write attribute contains the @code{gdb.Color} object
+representing this style's background color. Writing to this attribute
+updates the style's background color.
+
+If the @code{gdb.Style} object was created using a named style (i.e.@:
+using the single argument @var{style_name} @code{__init__} method),
+then the underlying setting will be updated.
+
+For unnamed styles, only the @code{gdb.Style} object will change.
+@end defvar
+
+@defvar Style.intensity
+This read/write attribute contains the intensity for this style, and
+will be one of the constants defined below (@pxref{Style
+Intensities}). Writing to this attribute updates the style's
+intensity.
+
+It is also possible to write @code{None} to this attribute, this is
+equivalent of writing @code{gdb.INTENSITY_NORMAL}. This attribute
+will never read as @code{None}.
+
+If the @code{gdb.Style} object was created using a named style (i.e.@:
+using the single argument @var{style_name} @code{__init__} method),
+then the underlying setting will be updated.
+
+For unnamed styles, only the @code{gdb.Style} object will change.
+@end defvar
+
+@anchor{Style Intensities}
+The following constants are defined to represent the different style
+intensities:
+
+@table @code
+@findex INTENSITY_NORMAL
+@findex gdb.INTENSITY_NORMAL
+@item gdb.INTENSITY_NORMAL
+This is the terminal's default intensity.
+
+@findex INTENSITY_BOLD
+@findex gdb.INTENSITY_BOLD
+@item gdb.INTENSITY_BOLD
+This is for bold text.
+
+@findex INTENSITY_DIM
+@findex gdb.INTENSITY_DIM
+@item gdb.INTENSITY_DIM
+This is for dim text.
+@end table
+
@node Architectures In Python
@subsubsection Python representation of architectures
@cindex Python architectures
diff --git a/gdb/python/py-style.c b/gdb/python/py-style.c
new file mode 100644
index 00000000000..235fa65add7
--- /dev/null
+++ b/gdb/python/py-style.c
@@ -0,0 +1,797 @@
+/* Python interface to ui_file_style objects.
+
+ Copyright (C) 2025 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 "ui-style.h"
+#include "py-color.h"
+#include "cli/cli-decode.h"
+#include "cli/cli-style.h"
+#include "top.h"
+
+/* Intensity constants and their values. */
+static struct {
+ const char *name;
+ ui_file_style::intensity value;
+} intensity_constants[] =
+{
+ { "INTENSITY_NORMAL", ui_file_style::NORMAL },
+ { "INTENSITY_DIM", ui_file_style::DIM },
+ { "INTENSITY_BOLD", ui_file_style::BOLD }
+};
+
+/* A style. */
+struct stylepy_object
+{
+ PyObject_HEAD
+
+ /* Underlying style, only valid when STYLE_NAME is NULL. */
+ ui_file_style style;
+
+ /* The name of the style. Can be NULL, in which case STYLE holds the
+ style value. */
+ char *style_name;
+};
+
+extern PyTypeObject stylepy_object_type;
+
+/* Initialize the 'style' module. */
+
+static int
+gdbpy_initialize_style ()
+{
+ for (auto &pair : intensity_constants)
+ if (PyModule_AddIntConstant (gdb_module, pair.name,
+ static_cast<long> (pair.value)) < 0)
+ return -1;
+
+ return gdbpy_type_ready (&stylepy_object_type, gdb_module);
+}
+
+/* Free any resources help by SELF, and reset points to NULL. */
+
+static void
+stylepy_free_resources (PyObject *self)
+{
+ stylepy_object *style = (stylepy_object *) self;
+
+ xfree (style->style_name);
+ style->style_name = nullptr;
+}
+
+/* gdb.Style deallocation method. */
+
+static void
+stylepy_dealloc (PyObject *self)
+{
+ stylepy_free_resources (self);
+ Py_TYPE (self)->tp_free (self);
+}
+
+/* Find style NAME and return it. If NAME cannot be found then an empty
+ optional is returned, and a Python error will be set.
+
+ If HAS_INTENSITY_PTR is not NULL, then, if NAME is found,
+ *HAS_INTENSITY_PTR will be set true if NAME has an "intensity"
+ sub-command, and set false otherwise. If NAME is not found then
+ *HAS_INTENSITY_PTR is left unchanged.
+
+ If FOUND_CMD_PTR is not NULL, then, if NAME is found, *FOUND_CMD_PTR is
+ set to point to the prefix command matching NAME. If NAME is a
+ multi-word style name (e.g. 'disassembler comment') then *FOUND_CMD_PTR
+ will point to the prefix command for the last word (e.g. 'comment'). */
+
+static std::optional<ui_file_style>
+stylepy_style_from_name (const char *name, bool *has_intensity_ptr = nullptr,
+ const cmd_list_element **found_cmd_ptr = nullptr)
+{
+ std::string cmd_str = std::string ("show style ") + name;
+
+ struct cmd_list_element *cmd = nullptr;
+ struct cmd_list_element *alias = nullptr;
+ int found;
+ try
+ {
+ struct cmd_list_element *prefix;
+ found = lookup_cmd_composition (cmd_str.c_str (), &alias, &prefix, &cmd);
+ }
+ catch (const gdb_exception &ex)
+ {
+ PyErr_Format (PyExc_RuntimeError,
+ _("style '%s' cannot be found."), name);
+ return {};
+ }
+
+ gdb_assert (!found || cmd != nullptr);
+
+ if (!found || cmd == CMD_LIST_AMBIGUOUS || !cmd->is_prefix ())
+ {
+ PyErr_Format (PyExc_RuntimeError,
+ _("style '%s' cannot be found."), name);
+ return {};
+ }
+
+ ui_file_style style;
+ bool has_fg = false;
+ bool has_bg = false;
+ bool has_intensity = false;
+ for (cmd_list_element *sub = *cmd->subcommands;
+ sub != nullptr;
+ sub = sub->next)
+ {
+ if (!sub->var.has_value ())
+ continue;
+
+ if (strcmp (sub->name, "foreground") == 0)
+ {
+ const ui_file_style::color &color
+ = sub->var->get<ui_file_style::color> ();
+ style.set_fg (color);
+ has_fg = true;
+ }
+ else if (strcmp (sub->name, "background") == 0)
+ {
+ const ui_file_style::color &color
+ = sub->var->get<ui_file_style::color> ();
+ style.set_bg (color);
+ has_bg = true;
+ }
+ else if (strcmp (sub->name, "intensity") == 0
+ && sub->var->type () == var_enum)
+ {
+ const char *intensity_str = sub->var->get<const char *> ();
+ ui_file_style::intensity intensity = ui_file_style::NORMAL;
+ if (strcmp (intensity_str, "bold") == 0)
+ intensity = ui_file_style::BOLD;
+ else if (strcmp (intensity_str, "dim") == 0)
+ intensity = ui_file_style::DIM;
+ style.set_intensity (intensity);
+ has_intensity = true;
+ }
+ }
+
+ /* All styles should have a foreground and background, but the intensity
+ is optional. */
+ if (!has_fg || !has_bg)
+ {
+ PyErr_Format (PyExc_RuntimeError,
+ _("style '%s' missing '%s' component."), name,
+ (has_fg ? "background" : "foreground"));
+ return {};
+ }
+
+ if (has_intensity_ptr != nullptr)
+ *has_intensity_ptr = has_intensity;
+
+ /* If NAME identified an alias then use that instead of CMD, this means
+ the style's cached name will better match the string the user used to
+ initialise the style. */
+ if (found_cmd_ptr != nullptr)
+ *found_cmd_ptr = alias != nullptr ? alias : cmd;
+
+ return style;
+}
+
+/* Initialise a gdb.Style object from a named style. We only store the
+ style name within SELF, each time the style is used, we look up its
+ current value, this allows the style to change if the user adjusts the
+ settings. */
+
+static int
+stylepy_init_from_style_name (PyObject *self, const char *style_name)
+{
+ stylepy_object *style = (stylepy_object *) self;
+ gdb_assert (style->style_name == nullptr);
+
+ gdb_assert (style_name != nullptr);
+
+ const cmd_list_element *cmd = nullptr;
+ std::optional<ui_file_style> maybe_style
+ = stylepy_style_from_name (style_name, nullptr, &cmd);
+ if (!maybe_style.has_value ())
+ return -1;
+
+ /* If we found a style then we must have found a prefix command. */
+ gdb_assert (cmd != nullptr);
+ gdb_assert (cmd->is_prefix ());
+
+ /* Get the components of this command. */
+ std::vector<std::string> components = cmd->command_components ();
+ gdb_assert (components.size () > 2);
+ gdb_assert (components[0] == "show");
+ gdb_assert (components[1] == "style");
+
+ /* And build the components into a string, but without the 'show style'
+ part at the start. */
+ std::string expanded_style_name (components[2]);
+ for (int i = 3; i < components.size (); ++i)
+ expanded_style_name += " " + components[i];
+
+ style->style_name = xstrdup (expanded_style_name.c_str ());
+
+ return 0;
+}
+
+/* Convert INTENSITY_VALUE to a valid intensity enum value, if possible,
+ and return the enum value. If INTENSITY_VALUE is not a valid intensity
+ value then set a Python error, and return an empty optional. */
+
+static std::optional<ui_file_style::intensity>
+stylepy_long_to_intensity (long intensity_value)
+{
+ ui_file_style::intensity intensity
+ = static_cast<ui_file_style::intensity> (intensity_value);
+ switch (intensity)
+ {
+ case ui_file_style::NORMAL:
+ case ui_file_style::DIM:
+ case ui_file_style::BOLD:
+ break;
+
+ default:
+ PyErr_Format
+ (PyExc_ValueError, _("invalid 'intensity' value %d."),
+ intensity_value);
+ return {};
+ }
+
+ return intensity;
+}
+
+/* Initialise a gdb.Style object from a foreground and background
+ gdb.Color object, and an intensity. */
+
+static int
+stylepy_init_from_parts (PyObject *self, PyObject *fg, PyObject *bg,
+ int intensity_value)
+{
+ stylepy_object *style = (stylepy_object *) self;
+ gdb_assert (style->style_name == nullptr);
+
+ if (fg == Py_None)
+ fg = nullptr;
+
+ if (bg == Py_None)
+ bg = nullptr;
+
+ if (fg != nullptr && !gdbpy_is_color (fg))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'foreground' argument must be gdb.Color or None, not %s."),
+ Py_TYPE (fg)->tp_name);
+ return -1;
+ }
+
+ if (bg != nullptr && !gdbpy_is_color (bg))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("'background' argument must be gdb.Color or None, not %s."),
+ Py_TYPE (bg)->tp_name);
+ return -1;
+ }
+
+ if (fg != nullptr)
+ style->style.set_fg (gdbpy_get_color (fg));
+ else
+ style->style.set_fg (ui_file_style::color (ui_file_style::NONE));
+
+ if (bg != nullptr)
+ style->style.set_bg (gdbpy_get_color (bg));
+ else
+ style->style.set_bg (ui_file_style::color (ui_file_style::NONE));
+
+ /* Convert INTENSITY_VALUE into the enum. */
+ std::optional<ui_file_style::intensity> intensity
+ = stylepy_long_to_intensity (intensity_value);
+ if (!intensity.has_value ())
+ return -1;
+ style->style.set_intensity (intensity.value ());
+
+ return 0;
+}
+
+/* gdb.Style object initializer. Either:
+ gdb.Style.__init__("style name")
+ gdb.Style.__init__(fg, bg, intensity)
+
+ This init function supports two possible sets of arguments. Both
+ options are different enough that we can distinguish the two by the
+ argument types. Dispatch to one of the two stylepy_init_from_*
+ functions above. */
+
+static int
+stylepy_init (PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ /* If this object was previously initialised, then clear it out now. */
+ stylepy_free_resources (self);
+
+ /* Try to parse the incoming arguments as a string, this is a style
+ name. */
+ const char *style_name = nullptr;
+ static const char *keywords_style[] = { "style", nullptr };
+ if (gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "s", keywords_style,
+ &style_name))
+ return stylepy_init_from_style_name (self, style_name);
+
+ /* That didn't work, discard any errors. */
+ PyErr_Clear ();
+
+ /* Try to parse the incoming arguments as a list of parts, this is an
+ unnamed style. */
+ PyObject *foreground_color = nullptr;
+ PyObject *background_color = nullptr;
+ int intensity_value = static_cast<int> (ui_file_style::NORMAL);
+ static const char *keywords_parts[]
+ = { "foreground", "background", "intensity", nullptr };
+ if (gdb_PyArg_ParseTupleAndKeywords (args, kwargs, "|OOi", keywords_parts,
+ &foreground_color,
+ &background_color,
+ &intensity_value))
+ return stylepy_init_from_parts (self, foreground_color,
+ background_color, intensity_value);
+
+ /* Return the error gdb_PyArg_ParseTupleAndKeywords set. */
+ return -1;
+}
+
+\f
+
+/* Return the ui_file_style for STYLEPY. If the style cannot be found,
+ then return an empty optional, and set a Python error. */
+
+static std::optional<ui_file_style>
+stylepy_to_style (stylepy_object *stylepy)
+{
+ std::optional<ui_file_style> style;
+
+ if (stylepy->style_name != nullptr)
+ style = stylepy_style_from_name (stylepy->style_name);
+ else
+ style.emplace (stylepy->style);
+
+ return style;
+}
+
+/* Implementation of gdb.Style.escape_sequence(). Return the escape
+ sequence to apply Style. If styling is turned off, then this returns
+ the empty string. Can raise an exception if a named style can no longer
+ be read. */
+
+static PyObject *
+stylepy_escape_sequence (PyObject *self, PyObject *args)
+{
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ std::optional<ui_file_style> style = stylepy_to_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ std::string style_str;
+ if (term_cli_styling ())
+ style_str = style->to_ansi ();
+
+ return host_string_to_python_string (style_str.c_str ()).release ();
+}
+
+/* Implement gdb.Style.apply(STR). Return a new string which is STR with
+ escape sequences added so that STR is formatted in this style. A
+ trailing escape sequence is added to restore the default style.
+
+ If styling is currently disabled ('set style enabled off'), then no
+ escape sequences are added, but all the checks for the validity of the
+ current style are still performed, and a new string, a copy of the
+ input string is returned.
+
+ Can raise a Python exception and return NULL if the argument types are
+ wrong, or if a named style can no longer be read. */
+
+static PyObject *
+stylepy_apply (PyObject *self, PyObject *args, PyObject *kw)
+{
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ static const char *keywords[] = { "string", nullptr };
+ PyObject *input_obj;
+
+ if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "O!", keywords,
+ &PyUnicode_Type, &input_obj))
+ return nullptr;
+
+ std::optional<ui_file_style> style = stylepy_to_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ if (gdb_stdout->can_emit_style_escape ())
+ {
+ gdb_assert (gdbpy_is_string (input_obj));
+ gdb::unique_xmalloc_ptr<char>
+ input (python_string_to_target_string (input_obj));
+
+ std::string output
+ = string_printf ("%s%s%s", style->to_ansi ().c_str (), input.get (),
+ ui_file_style ().to_ansi ().c_str ());
+
+ return host_string_to_python_string (output.c_str ()).release ();
+ }
+ else
+ {
+ /* Return an unmodified "copy" of INPUT_OBJ by just incrementing the
+ reference count. */
+ Py_INCREF (input_obj);
+ return input_obj;
+ }
+}
+
+\f
+
+/* Implement reading the gdb.Style.foreground attribute. */
+
+static PyObject *
+stylepy_get_foreground (PyObject *self, void *closure)
+{
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ std::optional<ui_file_style> style = stylepy_to_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ gdbpy_ref<> color = create_color_object (style->get_foreground ());
+ if (color == nullptr)
+ return nullptr;
+
+ return color.release ();
+}
+
+/* Implement writing the gdb.Style.foreground attribute. */
+
+static int
+stylepy_set_foreground (PyObject *self, PyObject *newvalue, void *closure)
+{
+ if (!gdbpy_is_color (newvalue))
+ {
+ PyErr_Format (PyExc_TypeError, _("value must be gdb.Color, not %s"),
+ Py_TYPE (newvalue)->tp_name);
+ return -1;
+ }
+
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ /* Handle unnamed styles. This is easy, just update the embedded style
+ object. */
+ if (style_obj->style_name == nullptr)
+ {
+ style_obj->style.set_fg (gdbpy_get_color (newvalue));
+ return 0;
+ }
+
+ /* Handle named styles. This is harder, we need to write to the actual
+ parameter. */
+ std::string cmd_string
+ = string_printf ("set style %s foreground %s",
+ style_obj->style_name,
+ gdbpy_get_color (newvalue).to_string ().c_str ());
+ try
+ {
+ execute_command (cmd_string.c_str (), 0);
+ }
+ catch (const gdb_exception &except)
+ {
+ return gdbpy_handle_gdb_exception (-1, except);
+ }
+
+ return 0;
+}
+
+/* Implement reading the gdb.Style.background attribute. */
+
+static PyObject *
+stylepy_get_background (PyObject *self, void *closure)
+{
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ std::optional<ui_file_style> style = stylepy_to_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ gdbpy_ref<> color = create_color_object (style->get_background ());
+ if (color == nullptr)
+ return nullptr;
+
+ return color.release ();
+}
+
+/* Implement writing the gdb.Style.background attribute. */
+
+static int
+stylepy_set_background (PyObject *self, PyObject *newvalue, void *closure)
+{
+ if (!gdbpy_is_color (newvalue))
+ {
+ PyErr_Format (PyExc_TypeError, _("value must be gdb.Color, not %s"),
+ Py_TYPE (newvalue)->tp_name);
+ return -1;
+ }
+
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ /* Handle unnamed styles. This is easy, just update the embedded style
+ object. */
+ if (style_obj->style_name == nullptr)
+ {
+ style_obj->style.set_bg (gdbpy_get_color (newvalue));
+ return 0;
+ }
+
+ /* Handle named styles. This is harder, we need to write to the actual
+ parameter. */
+ std::string cmd_string
+ = string_printf ("set style %s background %s",
+ style_obj->style_name,
+ gdbpy_get_color (newvalue).to_string ().c_str ());
+ try
+ {
+ execute_command (cmd_string.c_str (), 0);
+ }
+ catch (const gdb_exception &except)
+ {
+ return gdbpy_handle_gdb_exception (-1, except);
+ }
+
+ return 0;
+}
+
+/* Implement reading the gdb.Style.intensity attribute. */
+
+static PyObject *
+stylepy_get_intensity (PyObject *self, void *closure)
+{
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ std::optional<ui_file_style> style = stylepy_to_style (style_obj);
+ if (!style.has_value ())
+ return nullptr;
+
+ ui_file_style::intensity intensity = style->get_intensity ();
+ return PyLong_FromLong (static_cast<long> (intensity));
+}
+
+/* Return a string representing INTENSITY. */
+
+static const char *
+stylepy_intensity_to_string (ui_file_style::intensity intensity)
+{
+ const char *intensity_str = nullptr;
+ switch (intensity)
+ {
+ case ui_file_style::NORMAL:
+ intensity_str = "normal";
+ break;
+ case ui_file_style::BOLD:
+ intensity_str = "bold";
+ break;
+ case ui_file_style::DIM:
+ intensity_str = "dim";
+ break;
+ }
+
+ gdb_assert (intensity_str != nullptr);
+ return intensity_str;
+}
+
+/* Implement writing the gdb.Style.intensity attribute. */
+
+static int
+stylepy_set_intensity (PyObject *self, PyObject *newvalue, void *closure)
+{
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ if (!PyLong_Check (newvalue))
+ {
+ PyErr_Format
+ (PyExc_TypeError,
+ _("value must be a Long (a gdb.INTENSITY constant), not %s"),
+ Py_TYPE (newvalue)->tp_name);
+ return -1;
+ }
+
+ /* Convert the Python object to a value we can use. */
+ long intensity_value;
+ if (!gdb_py_int_as_long (newvalue, &intensity_value))
+ return -1;
+
+ std::optional<ui_file_style::intensity> intensity
+ = stylepy_long_to_intensity (intensity_value);
+ if (!intensity.has_value ())
+ return -1;
+
+ /* Handle unnamed styles. This is easy, just update the embedded style
+ object. */
+ if (style_obj->style_name == nullptr)
+ {
+ style_obj->style.set_intensity (intensity.value ());
+ return 0;
+ }
+
+ /* Handle named styles. This is harder, we need to write to the actual
+ parameter. First though, look up the named style to see if it has an
+ intensity. HAS_INTENSITY will be set true only if the style exists,
+ and has an 'intensity' setting. */
+
+ bool has_intensity = false;
+ (void) stylepy_style_from_name (style_obj->style_name, &has_intensity);
+ if (!has_intensity)
+ {
+ PyErr_Format
+ (PyExc_ValueError, "the intensity of style '%s' is not writable.",
+ style_obj->style_name);
+ return -1;
+ }
+
+ const char *intensity_str = stylepy_intensity_to_string (intensity.value ());
+ gdb_assert (intensity_str != nullptr);
+
+ std::string cmd_string
+ = string_printf ("set style %s intensity %s",
+ style_obj->style_name, intensity_str);
+ try
+ {
+ execute_command (cmd_string.c_str (), 0);
+ }
+ catch (const gdb_exception &except)
+ {
+ return gdbpy_handle_gdb_exception (-1, except);
+ }
+
+ return 0;
+}
+
+\f
+
+/* Call FETCH_ATTR, passing in SELF, to get a PyObject*, then convert it to
+ a Python string, and finally into a C++ managed string. Will return
+ nullptr and set a Python error if something goes wrong.
+
+ The FETCH_ATTR function will be a function that returns an attribute
+ from SELF. */
+
+static gdb::unique_xmalloc_ptr<char>
+stylepy_attribute_to_string
+ (PyObject *self,
+ gdb::function_view<PyObject * (PyObject *, void *)> fetch_attr)
+{
+ PyObject *attr_obj = fetch_attr (self, nullptr);
+ if (attr_obj == nullptr)
+ return nullptr;
+
+ PyObject *str_obj = PyObject_Str (attr_obj);
+ if (str_obj == nullptr)
+ return nullptr;
+
+ return python_string_to_host_string (str_obj);
+}
+
+/* __repr__ implementation for gdb.Style. */
+
+static PyObject *
+stylepy_repr (PyObject *self)
+{
+ stylepy_object *style_obj = (stylepy_object *) self;
+
+ gdb::unique_xmalloc_ptr<char> fg_str
+ (stylepy_attribute_to_string (self, stylepy_get_foreground));
+ gdb::unique_xmalloc_ptr<char> bg_str
+ (stylepy_attribute_to_string (self, stylepy_get_background));
+
+ PyObject *intensity_obj = stylepy_get_intensity (self, nullptr);
+ if (intensity_obj == nullptr)
+ return nullptr;
+ gdb_assert (PyLong_Check (intensity_obj));
+ long intensity_value;
+ if (!gdb_py_int_as_long (intensity_obj, &intensity_value))
+ return nullptr;
+ std::optional<ui_file_style::intensity> intensity
+ = stylepy_long_to_intensity (intensity_value);
+ if (!intensity.has_value ())
+ return nullptr;
+ const char *intensity_str = stylepy_intensity_to_string (intensity.value ());
+ gdb_assert (intensity_str != nullptr);
+
+ if (style_obj->style_name == nullptr)
+ return PyUnicode_FromFormat ("<%s fg=%s, bg=%s, intensity=%s>",
+ Py_TYPE (self)->tp_name,
+ fg_str.get (), bg_str.get (),
+ intensity_str);
+ else
+ return PyUnicode_FromFormat ("<%s name='%s', fg=%s, bg=%s, intensity=%s>",
+ Py_TYPE (self)->tp_name,
+ style_obj->style_name, fg_str.get (),
+ bg_str.get (), intensity_str);
+}
+
+\f
+
+/* Style methods. */
+
+static PyMethodDef stylepy_methods[] =
+{
+ { "escape_sequence", stylepy_escape_sequence, METH_NOARGS,
+ "escape_sequence () -> str.\n\
+Return the ANSI escape sequence for this style."},
+ { "apply", (PyCFunction) stylepy_apply, METH_VARARGS | METH_KEYWORDS,
+ "apply(String) -> String.\n\
+Apply this style to the input string. Return an updated string."},
+ {nullptr}
+};
+
+/* Attribute get/set Python definitions. */
+
+static gdb_PyGetSetDef stylepy_object_getset[] = {
+ { "foreground", stylepy_get_foreground, stylepy_set_foreground,
+ "The gdb.Color for the foreground of this style.", NULL },
+ { "background", stylepy_get_background, stylepy_set_background,
+ "The gdb.Color for the background of this style.", NULL },
+ { "intensity", stylepy_get_intensity, stylepy_set_intensity,
+ "The Str for the intensity of this style.", NULL },
+ { nullptr } /* Sentinel. */
+};
+
+PyTypeObject stylepy_object_type =
+{
+ PyVarObject_HEAD_INIT (nullptr, 0)
+ "gdb.Style", /*tp_name*/
+ sizeof (stylepy_object), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ stylepy_dealloc, /*tp_dealloc*/
+ 0, /*tp_print*/
+ 0, /*tp_getattr*/
+ 0, /*tp_setattr*/
+ 0, /*tp_compare*/
+ stylepy_repr, /*tp_repr*/
+ 0, /*tp_as_number*/
+ 0, /*tp_as_sequence*/
+ 0, /*tp_as_mapping*/
+ 0, /*tp_hash */
+ 0, /*tp_call*/
+ 0, /*tp_str*/
+ 0, /*tp_getattro*/
+ 0, /*tp_setattro*/
+ 0, /*tp_as_buffer*/
+ Py_TPFLAGS_DEFAULT, /*tp_flags*/
+ "GDB style object", /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ stylepy_methods, /* tp_methods */
+ 0, /* tp_members */
+ stylepy_object_getset, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ stylepy_init, /* tp_init */
+ 0, /* tp_alloc */
+ PyType_GenericNew /* tp_new */
+};
+
+GDBPY_INITIALIZE_FILE (gdbpy_initialize_style);
diff --git a/gdb/testsuite/gdb.python/py-style.exp b/gdb/testsuite/gdb.python/py-style.exp
new file mode 100644
index 00000000000..b7240a50b3a
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-style.exp
@@ -0,0 +1,339 @@
+# Copyright (C) 2025 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.Style.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+# Start GDB, allow styling.
+with_ansi_styling_terminal {
+ clean_restart
+}
+
+# Check the error for unknown style names.
+foreach unknown_style { "unknown-style" "disassembler unknown-style" } {
+ gdb_test "python filename_style = gdb.Style('$unknown_style')" \
+ [multi_line \
+ "Python Exception <class 'RuntimeError'>: style '$unknown_style' cannot be found\\." \
+ "Error occurred in Python: style '$unknown_style' cannot be found\\."] \
+ "attempt to create named style for '$unknown_style'"
+}
+
+# Check we can create a style using an abbreviated style name.
+gdb_test_no_output "python abbrev_style = gdb.Style('high')" \
+ "create style using abbreviated name 'high'"
+gdb_test "python print(abbrev_style)" \
+ "^<gdb.Style name='highlight', fg=red, bg=none, intensity=normal>" \
+ "print the 'highlight' style"
+
+gdb_test_no_output "python abbrev_style = gdb.Style('disas mnem')" \
+ "create style using abbreviated name 'disas mnem'"
+gdb_test "python print(abbrev_style)" \
+ "^<gdb.Style name='disassembler mnemonic', fg=green, bg=none, intensity=normal>" \
+ "print the 'disassembler mnemonic' style"
+
+# Creating a style using an ambiguous abbreviated name will give an error.
+gdb_test "python abbrev_style = gdb.Style('f')" \
+ [multi_line \
+ "Python Exception <class 'RuntimeError'>: style 'f' cannot be found\\." \
+ "Error occurred in Python: style 'f' cannot be found\\."] \
+ "create style using abbreviated name 'f'"
+
+# Check a couple of different styles can be read. The 'tui-border' is
+# interesting as there is no 'intensity' for this one, the gdb.Style
+# object will show this as gdb.INTENSITY_NORMAL. The disassembler
+# styles are interesting because they are two word style names, and
+# the comment style has a foreground and intensity set.
+foreach style_check { { "filename" green none NORMAL } \
+ { "title" none none BOLD } \
+ { "tui-border" cyan none NORMAL } \
+ { "disassembler address" blue none NORMAL } \
+ { "disassembler comment" white none DIM } } {
+ set style_name [lindex $style_check 0]
+ set fg [lindex $style_check 1]
+ set bg [lindex $style_check 2]
+ set intensity [lindex $style_check 3]
+
+ with_test_prefix "check style $style_name" {
+ gdb_test_no_output "python style_obj = gdb.Style('$style_name')" \
+ "create named style for $style_name"
+
+ gdb_test "python print(style_obj.foreground)" "^$fg" \
+ "print foreground color"
+ gdb_test "python print(style_obj.background)" "^$bg" \
+ "print background color"
+ gdb_test "python print(style_obj.intensity == gdb.INTENSITY_$intensity)" \
+ "^True" "print intensity"
+
+ gdb_test "python print(style_obj)" \
+ "^<gdb.Style name='$style_name', fg=$fg, bg=$bg, intensity=[string tolower $intensity]>" \
+ "print string representation"
+ }
+}
+
+# Check that the intensity of a named style with no intensity
+# (tui-border) cannot be changed.
+gdb_test_no_output "python tui_border_style = gdb.Style('tui-border')" \
+ "create named style for 'tui-border'"
+gdb_test "python tui_border_style.intensity = gdb.INTENSITY_BOLD" \
+ [multi_line \
+ "Python Exception <class 'ValueError'>: the intensity of style 'tui-border' is not writable\\." \
+ "Error occurred in Python: the intensity of style 'tui-border' is not writable\\."]
+
+# Change the attributes of a named style, check the settings update as
+# expected.
+gdb_test_no_output "python filename_style = gdb.Style('filename')" \
+ "create named style for 'filename'"
+gdb_test_no_output "python filename_style.foreground = gdb.Color('blue')" \
+ "assign blue to filename foreground color"
+gdb_test_no_output "python filename_style.background = gdb.Color('red')" \
+ "assign red to filename background color"
+gdb_test_no_output "python filename_style.intensity = gdb.INTENSITY_BOLD"
+
+# Use 'with style enabled off' so that there are no escape sequences
+# in the output.
+gdb_test "with style enabled off -- show style filename" \
+ [multi_line \
+ "style filename background: The \"filename\" style background color is: red" \
+ "style filename foreground: The \"filename\" style foreground color is: blue" \
+ "style filename intensity: The \"filename\" style display intensity is: bold"]
+
+gdb_test "python print(filename_style)" \
+ "^<gdb.Style name='filename', fg=blue, bg=red, intensity=bold>" \
+ "print string representation of filename_style"
+
+# Check some attempts to set the gdb.Style attributes to invalid types.
+foreach attr { foreground background } {
+ gdb_test "python filename_style.$attr = None" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: value must be gdb.Color, not NoneType" \
+ "Error occurred in Python: value must be gdb.Color, not NoneType"]
+
+ gdb_test "python filename_style.$attr = list()" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: value must be gdb.Color, not list" \
+ "Error occurred in Python: value must be gdb.Color, not list"]
+
+ gdb_test "python filename_style.$attr = 'red'" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: value must be gdb.Color, not str" \
+ "Error occurred in Python: value must be gdb.Color, not str"]
+}
+
+gdb_test "python filename_style.intensity = None" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: value must be a Long \\(a gdb.INTENSITY constant\\), not NoneType" \
+ "Error occurred in Python: value must be a Long \\(a gdb.INTENSITY constant\\), not NoneType"]
+
+gdb_test "python filename_style.intensity = 'dim'" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: value must be a Long \\(a gdb.INTENSITY constant\\), not str" \
+ "Error occurred in Python: value must be a Long \\(a gdb.INTENSITY constant\\), not str"]
+
+# Check attempts to set the intensity to an integer value work as
+# expected. This is mostly about testing invalid integer values, but
+# we do also check that 0, 1, and 2 work as expected, though it is bad
+# practice to use the raw integer value rather than the defined
+# constants.
+set intensity_str { NORMAL BOLD DIM }
+for { set i -3 } { $i < 6 } { incr i } {
+ if { $i < 0 || $i > 2 } {
+ gdb_test "python filename_style.intensity = $i" \
+ [multi_line \
+ "Python Exception <class 'ValueError'>: invalid 'intensity' value $i\\." \
+ "Error occurred in Python: invalid 'intensity' value $i\\."]
+ } else {
+ gdb_test_no_output "python filename_style.intensity = $i"
+
+ set str [lindex $intensity_str $i]
+ gdb_test "python print(filename_style.intensity == gdb.INTENSITY_$str)" \
+ "^True" "check filename_style intensity is $str"
+ }
+}
+
+# Check the filename style has changed as expected.
+gdb_test "python print(filename_style)" \
+ "^<gdb.Style name='filename', fg=blue, bg=red, intensity=dim>" \
+ "check filename_style is now dim"
+
+# Check Style.escape_sequence and Style.apply when styling is disabled.
+gdb_test_no_output "with style enabled off -- python print(filename_style.escape_sequence(), end='')" \
+ "print escape sequence when styling is off"
+gdb_test "with style enabled off -- python print(filename_style.apply('xxx'))" "^xxx" \
+ "apply style to a string when styling is off"
+
+# Now check the escape sequences are emitted as expected.
+gdb_test "python print(filename_style.escape_sequence())" \
+ "\033\\\[34;41;2;27m" \
+ "print escape sequence when styling is on"
+gdb_test "python print(filename_style.apply('xxx'))" \
+ "\033\\\[34;41;2;27mxxx\033\\\[m" \
+ "apply a style when styling is on"
+
+# Test creating a style from component parts.
+gdb_test_no_output "python my_style_1 = gdb.Style(gdb.Color('red'), gdb.Color('blue'), gdb.INTENSITY_BOLD)" \
+ "create my_style_1"
+gdb_test "python print(my_style_1)" \
+ "^<gdb.Style fg=red, bg=blue, intensity=bold>" \
+ "check my_style_1"
+
+gdb_test_no_output "python my_style_2 = gdb.Style(background = gdb.Color('blue'))" \
+ "create my_style_2"
+gdb_test "python print(my_style_2)" \
+ "^<gdb.Style fg=none, bg=blue, intensity=normal>" \
+ "check my_style_2"
+
+gdb_test_no_output "python my_style_3 = gdb.Style(intensity = gdb.INTENSITY_DIM)" \
+ "create my_style_3"
+gdb_test "python print(my_style_3)" \
+ "^<gdb.Style fg=none, bg=none, intensity=dim>" \
+ "check my_style_3"
+
+gdb_test "python my_style_4 = gdb.Style(intensity = None)" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: an integer is required \\(got type NoneType\\)" \
+ "Error occurred in Python: an integer is required \\(got type NoneType\\)"] \
+ "attempt to create my_style_4"
+
+gdb_test "python my_style_5 = gdb.Style(foreground = list())" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'foreground' argument must be gdb.Color or None, not list\\." \
+ "Error occurred in Python: 'foreground' argument must be gdb.Color or None, not list\\."] \
+ "attempt to create my_style_5"
+
+gdb_test "python my_style_6 = gdb.Style(background = list())" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: 'background' argument must be gdb.Color or None, not list\\." \
+ "Error occurred in Python: 'background' argument must be gdb.Color or None, not list\\."] \
+ "attempt to create my_style_6"
+
+# Adjust the attributes of an unnamed style.
+gdb_test_no_output "python my_style_1.foreground = gdb.Color('green')" \
+ "change my_style_1.foreground to green"
+gdb_test_no_output "python my_style_1.background = gdb.Color('cyan')" \
+ "change my_style_1.background to cyan"
+gdb_test_no_output "python my_style_1.intensity = gdb.INTENSITY_DIM" \
+ "change my_style_1.intensity to dim"
+gdb_test "python print(my_style_1)" \
+ "^<gdb.Style fg=green, bg=cyan, intensity=dim>" \
+ "check my_style_1 after changes."
+
+# Setup some prefix commands under 'set/show style ...'. Each prefix
+# command is called 'new-style-X' where X is an integer; 1, 2, 3, or 4.
+for { set i 1 } { $i < 5 } { incr i } {
+ gdb_test_no_output "python gdb.Command('set style new-style-$i', gdb.COMMAND_NONE, prefix = True)" \
+ "create set style new-style-$i"
+ gdb_test_no_output "python gdb.Command('show style new-style-$i', gdb.COMMAND_NONE, prefix = True)" \
+ "create show style new-style-$i"
+}
+
+# A helper class for creating color settings under the 'new-style-X'
+# prefix commands. The NUM to the __init__ supplies the value of
+# 'X', and NAME is either 'foreground' or 'background'.
+gdb_test_multiline "Class to create color parameters" \
+ "python" "" \
+ "class color_param(gdb.Parameter):" "" \
+ " def __init__(self, num, name):" "" \
+ " super().__init__(f\"style new-style-{num} {name}\", gdb.COMMAND_NONE, gdb.PARAM_COLOR)" "" \
+ " self.value = gdb.Color()" "" \
+ "end" ""
+
+# A helper class for creating intensity settings under the new
+# 'new-style-X' prefix commands. The NUM in the __init__ supplies the
+# value of 'X'.
+gdb_test_multiline "Class to create intensity parameters" \
+ "python" "" \
+ "class intensity_param(gdb.Parameter):" "" \
+ " def __init__(self, num):" "" \
+ " super().__init__(f\"style new-style-{num} intensity\", gdb.COMMAND_NONE, gdb.PARAM_ENUM," "" \
+ " \[\"normal\", \"bold\", \"dim\"\])" "" \
+ " self.value = \"normal\"" "" \
+ "end" ""
+
+# Within 'style new-style-1' we only have a 'foreground' setting.
+# This will not be usable as a gdb.Style.
+gdb_test_no_output "python color_param(1, 'foreground')" \
+ "setup new-style-1 foreground"
+
+# Within 'style new-style-2' we only have a 'background' setting.
+# This will not be usable as a gdb.Style.
+gdb_test_no_output "python color_param(2, 'background')" \
+ "setup new-style-2 background"
+
+# Within 'style new-style-3' we have both a 'foreground' and
+# 'background' setting. Even though 'intensity' is missing, this is
+# still usable as a style.
+gdb_test_no_output "python color_param(3, 'foreground')" \
+ "setup new-style-3 foreground"
+gdb_test_no_output "python color_param(3, 'background')" \
+ "setup new-style-3 background"
+
+# Within 'style new-style-4' we have a 'foreground', 'background', and
+# 'intensity' setting. This is a complete style setting group.
+gdb_test_no_output "python color_param(4, 'foreground')" \
+ "setup new-style-4 foreground"
+gdb_test_no_output "python color_param(4, 'background')" \
+ "setup new-style-4 background"
+gdb_test_no_output "python intensity_param(4)" \
+ "setup new-style-4 intensity"
+
+# Trying to create a style for 'new-style-1' should fail.
+gdb_test "python my_style_7 = gdb.Style('new-style-1')" \
+ [multi_line \
+ "Python Exception <class 'RuntimeError'>: style 'new-style-1' missing 'background' component\\." \
+ "Error occurred in Python: style 'new-style-1' missing 'background' component\\."] \
+ "try to create style for new-style-1"
+
+# Trying to create a style for 'new-style-2' should fail.
+gdb_test "python my_style_8 = gdb.Style('new-style-2')" \
+ [multi_line \
+ "Python Exception <class 'RuntimeError'>: style 'new-style-2' missing 'foreground' component\\." \
+ "Error occurred in Python: style 'new-style-2' missing 'foreground' component\\."] \
+ "try to create style for new-style-2"
+
+# Trying to create a style for 'new-style-3' should succeed.
+gdb_test_no_output "python my_style_9 = gdb.Style('new-style-3')" \
+ "create a style for new-style-3"
+gdb_test "python print(my_style_9)" \
+ "^<gdb.Style name='new-style-3', fg=none, bg=none, intensity=normal>" \
+ "print my_style_9"
+
+# Trying to create a style for 'new-style-4' should succeed too.
+gdb_test_no_output "python my_style_10 = gdb.Style('new-style-4')" \
+ "create a style for new-style-4"
+gdb_test "python print(my_style_10)" \
+ "^<gdb.Style name='new-style-4', fg=none, bg=none, intensity=normal>" \
+ "print my_style_10"
+
+# Adjust the settings directly, and check the gdb.Style updates.
+gdb_test_no_output "set style new-style-4 intensity bold"
+gdb_test_no_output "set style new-style-4 foreground red"
+gdb_test_no_output "set style new-style-4 background blue"
+gdb_test "python print(my_style_10)" \
+ "^<gdb.Style name='new-style-4', fg=red, bg=blue, intensity=bold>" \
+ "print my_style_10, intensity updated"
+
+# Check the reference counting on 'gdb.Style.apply' when global styling is disabled.
+gdb_test_no_output "python input_text = \"this is a unique string that is unlikely to appear elsewhere 12345\"" \
+ "setup an input string"
+gdb_test_no_output "with style enabled off -- python output_text = filename_style.apply(input_text)" \
+ "create an ouput string by applying filename_style"
+gdb_test_no_output "python input_text = \"a totally different string that is also, hopefully, unique\"" \
+ "replace the input string"
+gdb_test "python print(output_text)" \
+ "^this is a unique string that is unlikely to appear elsewhere 12345" \
+ "check the output_text is still valid"
diff --git a/gdb/ui-style.h b/gdb/ui-style.h
index 77a175d73ed..f2df0aa51c4 100644
--- a/gdb/ui-style.h
+++ b/gdb/ui-style.h
@@ -330,6 +330,12 @@ struct ui_file_style
return m_intensity;
}

+ /* Set the intensity of this style. */
+ void set_intensity (intensity i)
+ {
+ m_intensity = i;
+ }
+
/* Parse an ANSI escape sequence in BUF, modifying this style. BUF
must begin with an ESC character. Return true if an escape
sequence was successfully parsed; false otherwise. In either
--
2.47.1