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.
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
--- /dev/null
+/* 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 style_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 style_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 (&style_object_type, gdb_module);
+}
+
+/* Free any resources help by SELF, and reset points to NULL. */
+
+static void
+stylepy_free_resources (PyObject *self)
+{
+ style_object *style = (style_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)
+{
+ style_object *style = (style_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)
+{
+ style_object *style = (style_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 (style_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)
+{
+ style_object *style_obj = (style_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)
+{
+ style_object *style_obj = (style_object *) self;
+
+ static const char *keywords[] = { "string", nullptr };
+ PyObject *input_obj;
+
+ /* Grab the incoming string as a Python object. In the case where
+ styling is not being applied we can just return this object with the
+ reference count incremented. */
+ 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_host_string (input_obj));
+
+ std::string output
+ = style->to_ansi () + input.get () + ui_file_style ().to_ansi ();
+
+ 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)
+{
+ style_object *style_obj = (style_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;
+ }
+
+ style_object *style_obj = (style_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)
+{
+ style_object *style_obj = (style_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;
+ }
+
+ style_object *style_obj = (style_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)
+{
+ style_object *style_obj = (style_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)
+{
+ style_object *style_obj = (style_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;
+ std::optional<ui_file_style> style
+ = stylepy_style_from_name (style_obj->style_name, &has_intensity);
+ if (!style.has_value ())
+ return -1;
+ 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)
+{
+ style_object *style_obj = (style_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 style_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 style_object_type =
+{
+ PyVarObject_HEAD_INIT (nullptr, 0)
+ "gdb.Style", /*tp_name*/
+ sizeof (style_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 */
+ style_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);
--- /dev/null
+# 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;23;24;27m" \
+ "print escape sequence when styling is on"
+gdb_test "python print(filename_style.apply('xxx'))" \
+ "\033\\\[34;41;2;23;24;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"
+
+# The precise error message, about 'None' not being an integer, varies
+# with Python version. We just check that we get a TypeError and
+# assume that this is related to the argument type.
+gdb_test "python my_style_4 = gdb.Style(intensity = None)" \
+ [multi_line \
+ "Python Exception <class 'TypeError'>: \[^\r\n\]+" \
+ "Error occurred in Python: \[^\r\n\]+"] \
+ "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"